Files
vault/ui/lib/core/addon/utils/client-count-utils.js
claire bontempo d8e6ed1c5b Add acme_clients to mirage and client count utils (#26232)
* remove yarn script that no longer works in yarn 3

* delete other deprecated yarn script

* add acme_clients to mirage handler and utils

* consolidate client names

* revert changes to homogenizeClientNaming, wait until confirmation from backend

* remove flattenDataset helper

* revert deleting flattendataset method (done in separate PR)

* move response to helper file

* cleanup utils based on test changes

* add acme_clients to tests

* rename variables and add comments!

* refactor homogenizeClientNaming and rename

* move by_namespace to test helper as well

* add comments and finally delete flattenDataset

* add more comments and update response to match no mounts shape

* update test selector

* finish updates for removing clients: null from serialized response

* final comments!

* remove arrayOfCounts helper

* Update ui/tests/integration/components/clients/page/sync-test.js

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
2024-04-03 20:21:24 +00:00

214 lines
7.3 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { compareAsc, getUnixTime, isWithinInterval } from 'date-fns';
// add new types here
export const CLIENT_TYPES = [
'acme_clients',
'clients', // summation of total clients
'entity_clients',
'non_entity_clients',
'secret_syncs',
];
// returns array of VersionHistoryModels for noteworthy upgrades: 1.9, 1.10
// that occurred between timestamps (i.e. queried activity data)
export const filterVersionHistory = (versionHistory, start, end) => {
if (versionHistory) {
const upgrades = versionHistory.reduce((array, upgradeData) => {
const includesVersion = (v) =>
// only add first match, disregard subsequent patch releases of the same version
upgradeData.version.match(v) && !array.some((d) => d.version.match(v));
['1.9', '1.10'].forEach((v) => {
if (includesVersion(v)) array.push(upgradeData);
});
return array;
}, []);
// if there are noteworthy upgrades, only return those during queried date range
if (upgrades.length) {
const startDate = parseAPITimestamp(start);
const endDate = parseAPITimestamp(end);
return upgrades.filter(({ timestampInstalled }) => {
const upgradeDate = parseAPITimestamp(timestampInstalled);
return isWithinInterval(upgradeDate, { start: startDate, end: endDate });
});
}
}
return [];
};
export const formatDateObject = (dateObj, isEnd) => {
if (dateObj) {
const { year, monthIdx } = dateObj;
// day=0 for Date.UTC() returns the last day of the month before
// increase monthIdx by one to get last day of queried month
const utc = isEnd ? Date.UTC(year, monthIdx + 1, 0) : Date.UTC(year, monthIdx, 1);
return getUnixTime(utc);
}
};
export const formatByMonths = (monthsArray) => {
// the monthsArray will always include a timestamp of the month and either new/total client data or counts = null
if (!Array.isArray(monthsArray)) return monthsArray;
const sortedPayload = sortMonthsByTimestamp(monthsArray);
return sortedPayload?.map((m) => {
const month = parseAPITimestamp(m.timestamp, 'M/yy');
const totalClientsByNamespace = formatByNamespace(m.namespaces);
const newClientsByNamespace = formatByNamespace(m.new_clients?.namespaces);
return {
month,
timestamp: m.timestamp,
...destructureClientCounts(m?.counts),
namespaces: formatByNamespace(m.namespaces) || [],
namespaces_by_key: namespaceArrayToObject(
totalClientsByNamespace,
newClientsByNamespace,
month,
m.timestamp
),
new_clients: {
month,
timestamp: m.timestamp,
...destructureClientCounts(m?.new_clients?.counts),
namespaces: formatByNamespace(m.new_clients?.namespaces) || [],
},
};
});
};
export const formatByNamespace = (namespaceArray) => {
if (!Array.isArray(namespaceArray)) return namespaceArray;
return namespaceArray?.map((ns) => {
// i.e. 'namespace_path' is an empty string for 'root', so use namespace_id
const label = ns.namespace_path === '' ? ns.namespace_id : ns.namespace_path;
// data prior to adding mount granularity will still have a mounts key,
// but with the value: "no mount accessor (pre-1.10 upgrade?)" (ref: vault/activity_log_util_common.go)
// transform to an empty array for type consistency
let mounts = [];
if (Array.isArray(ns.mounts)) {
mounts = ns.mounts.map((m) => ({ label: m['mount_path'], ...destructureClientCounts(m?.counts) }));
}
return {
label,
...destructureClientCounts(ns.counts),
mounts,
};
});
};
// In 1.10 'distinct_entities' changed to 'entity_clients' and 'non_entity_tokens' to 'non_entity_clients'
// these deprecated keys still exist on the response, so only return relevant keys here
// when querying historical data the response will always contain the latest client type keys because the activity log is
// constructed based on the version of Vault the user is on (key values will be 0)
export const destructureClientCounts = (verboseObject) => {
if (!verboseObject) return;
return CLIENT_TYPES.reduce((newObj, clientType) => {
newObj[clientType] = verboseObject[clientType];
return newObj;
}, {});
};
export const sortMonthsByTimestamp = (monthsArray) => {
const sortedPayload = [...monthsArray];
return sortedPayload.sort((a, b) =>
compareAsc(parseAPITimestamp(a.timestamp), parseAPITimestamp(b.timestamp))
);
};
export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByNamespace, month, timestamp) => {
if (!totalClientsByNamespace) return {}; // return if no data for that month
// all 'new_client' data resides within a separate key of each month (see data structure below)
// FIRST: iterate and nest respective 'new_clients' data within each namespace and mount object
// note: this is happening within the month object
const nestNewClientsWithinNamespace = totalClientsByNamespace?.map((ns) => {
const newNamespaceCounts = newClientsByNamespace?.find((n) => n.label === ns.label);
if (newNamespaceCounts) {
const newClientsByMount = [...newNamespaceCounts.mounts];
const nestNewClientsWithinMounts = ns.mounts?.map((mount) => {
const new_clients = newClientsByMount?.find((m) => m.label === mount.label) || {};
return {
...mount,
new_clients,
};
});
return {
...ns,
new_clients: {
label: ns.label,
...destructureClientCounts(newNamespaceCounts),
mounts: newClientsByMount,
},
mounts: [...nestNewClientsWithinMounts],
};
}
return {
...ns,
new_clients: {},
};
});
// SECOND: create a new object (namespace_by_key) in which each namespace label is a key
const namespaces_by_key = {};
nestNewClientsWithinNamespace?.forEach((namespaceObject) => {
// THIRD: make another object within the namespace where each mount label is a key
const mounts_by_key = {};
namespaceObject.mounts.forEach((mountObject) => {
mounts_by_key[mountObject.label] = {
month,
timestamp,
...mountObject,
new_clients: { month, ...mountObject.new_clients },
};
});
const { label, new_clients } = namespaceObject;
namespaces_by_key[label] = {
month,
timestamp,
...destructureClientCounts(namespaceObject),
new_clients: { month, ...new_clients },
mounts_by_key,
};
});
return namespaces_by_key;
/*
structure of object returned
namespace_by_key: {
"namespace_label": {
month: "3/22",
clients: 32,
entity_clients: 16,
non_entity_clients: 16,
new_clients: {
month: "3/22",
clients: 5,
entity_clients: 2,
non_entity_clients: 3,
mounts: [...array of this namespace's mounts and their new client counts],
},
mounts_by_key: {
"mount_label": {
month: "3/22",
clients: 3,
entity_clients: 2,
non_entity_clients: 1,
new_clients: {
month: "3/22",
clients: 5,
entity_clients: 2,
non_entity_clients: 3,
},
},
},
},
};
*/
};