diff --git a/ui/README.md b/ui/README.md index 794b5dcf25..c0d10bfcc8 100644 --- a/ui/README.md +++ b/ui/README.md @@ -88,7 +88,7 @@ _All of the commands below assume you're in the `ui/` directory._ [Mirage](https://miragejs.com/docs/getting-started/introduction/) can be helpful for mocking backend endpoints. Look in [mirage/handlers](mirage/handlers/) for existing mocked backends. -Run yarn with mirage: `yarn start:mirage handlername` +Run yarn with mirage: `export MIRAGE_DEV_HANDLER= yarn start` Where `handlername` is one of the options exported in [mirage/handlers/index](mirage/handlers/index.js) diff --git a/ui/app/serializers/clients/activity.js b/ui/app/serializers/clients/activity.js index 90996265ba..7229ed9382 100644 --- a/ui/app/serializers/clients/activity.js +++ b/ui/app/serializers/clients/activity.js @@ -5,8 +5,11 @@ import ApplicationSerializer from '../application'; import { formatISO } from 'date-fns'; -import { formatByMonths, formatByNamespace, homogenizeClientNaming } from 'core/utils/client-count-utils'; +import { formatByMonths, formatByNamespace, destructureClientCounts } from 'core/utils/client-count-utils'; import timestamp from 'core/utils/timestamp'; + +// see tests/helpers/clients for sample API response (ACTIVITY_RESPONSE_STUB) +// and transformed by_namespace and by_month examples (SERIALIZED_ACTIVITY_RESPONSE) export default class ActivitySerializer extends ApplicationSerializer { normalizeResponse(store, primaryModelClass, payload, id, requestType) { if (payload.id === 'no-data') { @@ -18,7 +21,7 @@ export default class ActivitySerializer extends ApplicationSerializer { response_timestamp, by_namespace: formatByNamespace(payload.data.by_namespace), by_month: formatByMonths(payload.data.months), - total: homogenizeClientNaming(payload.data.total), + total: destructureClientCounts(payload.data.total), }; delete payload.data.by_namespace; delete payload.data.months; @@ -26,47 +29,3 @@ export default class ActivitySerializer extends ApplicationSerializer { return super.normalizeResponse(store, primaryModelClass, transformedPayload, id, requestType); } } -/* -SAMPLE PAYLOAD BEFORE/AFTER: - -payload.data.by_namespace = [ - { - namespace_id: '5SWT8', - namespace_path: 'namespacelonglonglong4/', - counts: { - entity_clients: 171, - non_entity_clients: 20, - clients: 191, - }, - mounts: [ - { - mount_path: 'auth/method/uMGBU', - "counts":{ - "distinct_entities":0, - "entity_clients":0, - "non_entity_tokens":0, - "non_entity_clients":10, - "clients":10 - } - }, - ], - }, -]; - -transformedPayload.by_namespace = [ - { - label: 'namespacelonglonglong4/', - entity_clients: 171, - non_entity_clients: 20, - clients: 191, - mounts: [ - { - label: 'auth/method/uMGBU', - entity_clients: 20, - non_entity_clients: 15, - clients: 35, - }, - ], - }, -] -*/ diff --git a/ui/lib/core/addon/utils/client-count-utils.js b/ui/lib/core/addon/utils/client-count-utils.js index c04905d2e1..39fd01aa6c 100644 --- a/ui/lib/core/addon/utils/client-count-utils.js +++ b/ui/lib/core/addon/utils/client-count-utils.js @@ -6,6 +6,15 @@ 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) => { @@ -54,87 +63,57 @@ export const formatByMonths = (monthsArray) => { const month = parseAPITimestamp(m.timestamp, 'M/yy'); const totalClientsByNamespace = formatByNamespace(m.namespaces); const newClientsByNamespace = formatByNamespace(m.new_clients?.namespaces); - if (Object.keys(m).includes('counts')) { - const totalCounts = flattenDataset(m); - const newCounts = m.new_clients ? flattenDataset(m.new_clients) : {}; - return { + 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, - ...totalCounts, - namespaces: formatByNamespace(m.namespaces) || [], - namespaces_by_key: namespaceArrayToObject( - totalClientsByNamespace, - newClientsByNamespace, - month, - m.timestamp - ), - new_clients: { - month, - timestamp: m.timestamp, - ...newCounts, - namespaces: formatByNamespace(m.new_clients?.namespaces) || [], - }, - }; - } + ...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) => { - // 'namespace_path' is an empty string for root - if (ns['namespace_id'] === 'root') ns['namespace_path'] = 'root'; - const label = ns['namespace_path']; - const flattenedNs = flattenDataset(ns); - // if no mounts, mounts will be an empty array - flattenedNs.mounts = []; - if (ns?.mounts && ns.mounts.length > 0) { - flattenedNs.mounts = ns.mounts.map((mount) => { - return { - label: mount['mount_path'], - ...flattenDataset(mount), - }; - }); + // 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, - ...flattenedNs, + ...destructureClientCounts(ns.counts), + mounts, }; }); }; -// In 1.10 'distinct_entities' changed to 'entity_clients' and -// 'non_entity_tokens' to 'non_entity_clients' -export const homogenizeClientNaming = (object) => { - // if new key names exist, only return those key/value pairs - if (Object.keys(object).includes('entity_clients')) { - const { clients, entity_clients, non_entity_clients, secret_syncs } = object; - return { - clients, - entity_clients, - non_entity_clients, - secret_syncs, - }; - } - // if object only has outdated key names, update naming - if (Object.keys(object).includes('distinct_entities')) { - const { clients, distinct_entities, non_entity_tokens } = object; - return { - clients, - entity_clients: distinct_entities, - non_entity_clients: non_entity_tokens, - }; - } - return object; -}; - -export const flattenDataset = (object) => { - if (object?.counts) { - const flattenedObject = {}; - Object.keys(object['counts']).forEach((key) => (flattenedObject[key] = object['counts'][key])); - return homogenizeClientNaming(flattenedObject); - } - return object; +// 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) => { @@ -152,7 +131,6 @@ export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByName const nestNewClientsWithinNamespace = totalClientsByNamespace?.map((ns) => { const newNamespaceCounts = newClientsByNamespace?.find((n) => n.label === ns.label); if (newNamespaceCounts) { - const { label, clients, entity_clients, non_entity_clients, secret_syncs } = newNamespaceCounts; const newClientsByMount = [...newNamespaceCounts.mounts]; const nestNewClientsWithinMounts = ns.mounts?.map((mount) => { const new_clients = newClientsByMount?.find((m) => m.label === mount.label) || {}; @@ -164,11 +142,8 @@ export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByName return { ...ns, new_clients: { - label, - clients, - entity_clients, - non_entity_clients, - secret_syncs, + label: ns.label, + ...destructureClientCounts(newNamespaceCounts), mounts: newClientsByMount, }, mounts: [...nestNewClientsWithinMounts], @@ -193,14 +168,11 @@ export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByName }; }); - const { label, clients, entity_clients, non_entity_clients, secret_syncs, new_clients } = namespaceObject; + const { label, new_clients } = namespaceObject; namespaces_by_key[label] = { month, timestamp, - clients, - entity_clients, - non_entity_clients, - secret_syncs, + ...destructureClientCounts(namespaceObject), new_clients: { month, ...new_clients }, mounts_by_key, }; @@ -239,70 +211,3 @@ export const namespaceArrayToObject = (totalClientsByNamespace, newClientsByName }; */ }; - -/* -API RESPONSE STRUCTURE: -data: { - ** by_namespace organized in descending order of client count number ** - by_namespace: [ - { - namespace_id: '96OwG', - namespace_path: 'test-ns/', - counts: {}, - mounts: [{ mount_path: 'path-1', counts: {} }], - }, - ], - ** months organized in ascending order of timestamps, oldest to most recent - months: [ - { - timestamp: '2022-03-01T00:00:00Z', - counts: {}, - namespaces: [ - { - namespace_id: 'root', - namespace_path: '', - counts: {}, - mounts: [{ mount_path: 'auth/up2/', counts: {} }], - }, - ], - new_clients: { - counts: {}, - namespaces: [ - { - namespace_id: 'root', - namespace_path: '', - counts: {}, - mounts: [{ mount_path: 'auth/up2/', counts: {} }], - }, - ], - }, - }, - { - timestamp: '2022-04-01T00:00:00Z', - counts: {}, - namespaces: [ - { - namespace_id: 'root', - namespace_path: '', - counts: {}, - mounts: [{ mount_path: 'auth/up2/', counts: {} }], - }, - ], - new_clients: { - counts: {}, - namespaces: [ - { - namespace_id: 'root', - namespace_path: '', - counts: {}, - mounts: [{ mount_path: 'auth/up2/', counts: {} }], - }, - ], - }, - }, - ], - start_time: 'start timestamp string', - end_time: 'end timestamp string', - total: { clients: 300, non_entity_clients: 100, entity_clients: 400} , -} -*/ diff --git a/ui/mirage/handlers/clients.js b/ui/mirage/handlers/clients.js index 5669fd322b..6293542bd1 100644 --- a/ui/mirage/handlers/clients.js +++ b/ui/mirage/handlers/clients.js @@ -17,7 +17,14 @@ import { subMonths, } from 'date-fns'; import { parseAPITimestamp } from 'core/utils/date-formatters'; +import { CLIENT_TYPES } from 'core/utils/client-count-utils'; +/* +HOW TO ADD NEW TYPES: +1. add key to CLIENT_TYPES +2. Find "ADD NEW CLIENT TYPES HERE" comment below and add type to destructuring array +3. Add generateMounts() for that client type to the mounts array +*/ export const LICENSE_START = new Date('2023-07-02T00:00:00Z'); export const STATIC_NOW = new Date('2024-01-25T23:59:59Z'); const COUNTS_START = subMonths(STATIC_NOW, 12); // user started Vault cluster on 2023-01-25 @@ -29,13 +36,16 @@ function getSum(array, key) { } function getTotalCounts(array) { + const counts = CLIENT_TYPES.reduce((obj, key) => { + obj[key] = getSum(array, key); + return obj; + }, {}); + + // add deprecated keys return { - distinct_entities: getSum(array, 'entity_clients'), - entity_clients: getSum(array, 'entity_clients'), - non_entity_tokens: getSum(array, 'non_entity_clients'), - non_entity_clients: getSum(array, 'non_entity_clients'), - secret_syncs: getSum(array, 'secret_syncs'), - clients: getSum(array, 'clients'), + ...counts, + distinct_entities: counts.entity_clients, + non_entity_tokens: counts.non_entity_clients, }; } @@ -43,15 +53,23 @@ function randomBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } -function arrayOfCounts(max, arrayLength) { - var result = []; - var sum = 0; - for (var i = 0; i < arrayLength - 1; i++) { - result[i] = randomBetween(1, max - (arrayLength - i - 1) - sum); - sum += result[i]; - } - result[arrayLength - 1] = max - sum; - return result.sort((a, b) => b - a); +function generateMounts(pathPrefix, counts) { + const baseObject = CLIENT_TYPES.reduce((obj, key) => { + obj[key] = 0; + return obj; + }, {}); + return Array.from(Array(5)).map((mount, index) => { + return { + mount_path: `${pathPrefix}${index}`, + counts: { + ...baseObject, + distinct_entities: 0, + non_entity_tokens: 0, + // object contains keys for which 0-values of base object to overwrite + ...counts, + }, + }; + }); } function generateNamespaceBlock(idx = 0, isLowerCounts = false, ns) { @@ -63,41 +81,23 @@ function generateNamespaceBlock(idx = 0, isLowerCounts = false, ns) { counts: {}, mounts: {}, }; - const mounts = []; - Array.from(Array(5)).forEach((mount, index) => { - const [secretSyncs] = arrayOfCounts(randomBetween(min, max), 1); - mounts.push({ - mount_path: `kvv2-engine-${index}`, - counts: { - clients: secretSyncs, - // TODO test with live backend to confirm entity keys are present (and 0) for kv mounts - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: secretSyncs, - }, - }); - }); + // * ADD NEW CLIENT TYPES HERE and pass to a new generateMounts() function below + const [acme_clients, entity_clients, non_entity_clients, secret_syncs] = CLIENT_TYPES.map(() => + randomBetween(min, max) + ); + + // each mount type generates a different type of client + const mounts = [ + ...generateMounts('auth/authid/', { + clients: non_entity_clients + entity_clients, + non_entity_clients, + entity_clients, + }), + ...generateMounts('kvv2-engine-', { clients: secret_syncs, secret_syncs }), + ...generateMounts('pki-engine-', { clients: acme_clients, acme_clients }), + ]; - // generate auth mounts array - Array.from(Array(10)).forEach((mount, index) => { - const mountClients = randomBetween(min, max); - const [nonEntity, entity] = arrayOfCounts(mountClients, 2); - mounts.push({ - mount_path: `auth/authid${index}`, - counts: { - clients: mountClients, - entity_clients: entity, - non_entity_clients: nonEntity, - distinct_entities: entity, - non_entity_tokens: nonEntity, - // TODO test with live backend to confirm this key is present (and 0) for auth mounts (non-kv mounts) - secret_syncs: 0, - }, - }); - }); mounts.sort((a, b) => b.counts.clients - a.counts.clients); nsBlock.mounts = mounts; nsBlock.counts = getTotalCounts(mounts); diff --git a/ui/package.json b/ui/package.json index b00d636537..690612e824 100644 --- a/ui/package.json +++ b/ui/package.json @@ -27,14 +27,12 @@ "start": "VAULT_ADDR=http://127.0.0.1:8200; ember server --proxy=$VAULT_ADDR", "start2": "ember server --proxy=http://127.0.0.1:8202 --port=4202", "start:chroot": "ember server --proxy=http://127.0.0.1:8300 --port=4300", - "start:mirage": "start () { MIRAGE_DEV_HANDLER=$1 yarn run start; }; start", "test": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,vault \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/start-vault.js {@}\" --", "test:enos": "concurrently --kill-others-on-fail -P -c \"auto\" -n lint:js,lint:hbs,enos \"yarn:lint:js:quiet\" \"yarn:lint:hbs:quiet\" \"node scripts/enos-test-ember.js {@}\" --", "test:oss": "yarn run test -f='!enterprise' --split=8 --preserve-test-name --parallel", "test:quick": "node scripts/start-vault.js --split=8 --preserve-test-name --parallel", "test:quick-oss": "node scripts/start-vault.js -f='!enterprise' --split=8 --preserve-test-name --parallel", "test:filter": "node scripts/start-vault.js --server -f='!enterprise'", - "types:declare": "declare () { yarn tsc $1 --declaration --allowJs --emitDeclarationOnly --experimentalDecorators --outDir $2; }; declare", "vault": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8200 vault server -log-level=error -dev -dev-root-token-id=root -dev-ha -dev-transactional", "vault:cluster": "VAULT_REDIRECT_ADDR=http://127.0.0.1:8202 vault server -log-level=error -dev -dev-root-token-id=root -dev-listen-address=127.0.0.1:8202 -dev-ha -dev-transactional" }, diff --git a/ui/tests/helpers/clients.js b/ui/tests/helpers/clients.js index 467ef57603..38dea95fd7 100644 --- a/ui/tests/helpers/clients.js +++ b/ui/tests/helpers/clients.js @@ -7,6 +7,9 @@ import { Response } from 'miragejs'; import { SELECTORS as GENERAL } from 'vault/tests/helpers/general-selectors'; import { click } from '@ember/test-helpers'; +import { LICENSE_START } from 'vault/mirage/handlers/clients'; +import { addMonths } from 'date-fns'; + /** Scenarios Config off, no data Config on, no data @@ -132,3 +135,841 @@ export async function dateDropdownSelect(month, year) { await click(dateDropdown.selectYear(year)); await click(dateDropdown.submit); } + +export const ACTIVITY_RESPONSE_STUB = { + start_time: '2023-08-01T00:00:00Z', + end_time: '2023-09-30T23:59:59Z', // is always the last day and hour of the month queried + by_namespace: [ + { + namespace_id: 'root', + namespace_path: '', + counts: { + distinct_entities: 1033, + entity_clients: 1033, + non_entity_tokens: 1924, + non_entity_clients: 1924, + secret_syncs: 2397, + acme_clients: 75, + clients: 5429, + }, + mounts: [ + { + mount_path: 'auth/authid0', + counts: { + clients: 2957, + entity_clients: 1033, + non_entity_clients: 1924, + distinct_entities: 1033, + non_entity_tokens: 1924, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 2397, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 2397, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 75, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 75, + }, + }, + ], + }, + { + namespace_id: '81ry61', + namespace_path: 'ns/1', + counts: { + distinct_entities: 783, + entity_clients: 783, + non_entity_tokens: 1193, + non_entity_clients: 1193, + secret_syncs: 275, + acme_clients: 125, + clients: 2376, + }, + mounts: [ + { + mount_path: 'auth/authid0', + counts: { + clients: 1976, + entity_clients: 783, + non_entity_clients: 1193, + distinct_entities: 783, + non_entity_tokens: 1193, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 275, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 275, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 125, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 125, + }, + }, + ], + }, + ], + months: [ + { + timestamp: '2023-08-01T00:00:00Z', + counts: null, + namespaces: null, + new_clients: null, + }, + { + timestamp: '2023-09-01T00:00:00Z', + counts: { + distinct_entities: 1329, + entity_clients: 1329, + non_entity_tokens: 1738, + non_entity_clients: 1738, + secret_syncs: 5525, + acme_clients: 200, + clients: 8792, + }, + namespaces: [ + { + namespace_id: 'root', + namespace_path: '', + counts: { + distinct_entities: 1279, + entity_clients: 1279, + non_entity_tokens: 1598, + non_entity_clients: 1598, + secret_syncs: 2755, + acme_clients: 75, + clients: 5707, + }, + mounts: [ + { + mount_path: 'auth/authid0', + counts: { + clients: 2877, + entity_clients: 1279, + non_entity_clients: 1598, + distinct_entities: 1279, + non_entity_tokens: 1598, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 2755, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 2755, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 75, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 75, + }, + }, + ], + }, + { + namespace_id: '81ry61', + namespace_path: 'ns/1', + counts: { + distinct_entities: 50, + entity_clients: 50, + non_entity_tokens: 140, + non_entity_clients: 140, + secret_syncs: 2770, + acme_clients: 125, + clients: 3085, + }, + mounts: [ + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 2770, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 2770, + acme_clients: 0, + }, + }, + { + mount_path: 'auth/authid0', + counts: { + clients: 190, + entity_clients: 50, + non_entity_clients: 140, + distinct_entities: 50, + non_entity_tokens: 140, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 125, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 125, + }, + }, + ], + }, + ], + new_clients: { + counts: { + distinct_entities: 39, + entity_clients: 39, + non_entity_tokens: 81, + non_entity_clients: 81, + secret_syncs: 166, + acme_clients: 50, + clients: 336, + }, + namespaces: [ + { + namespace_id: '81ry61', + namespace_path: 'ns/1', + counts: { + distinct_entities: 30, + entity_clients: 30, + non_entity_tokens: 62, + non_entity_clients: 62, + secret_syncs: 100, + acme_clients: 30, + clients: 222, + }, + mounts: [ + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 100, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 100, + acme_clients: 0, + }, + }, + { + mount_path: 'auth/authid0', + counts: { + clients: 92, + entity_clients: 30, + non_entity_clients: 62, + distinct_entities: 30, + non_entity_tokens: 62, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 30, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 30, + }, + }, + ], + }, + { + namespace_id: 'root', + namespace_path: '', + counts: { + distinct_entities: 9, + entity_clients: 9, + non_entity_tokens: 19, + non_entity_clients: 19, + secret_syncs: 66, + acme_clients: 20, + clients: 114, + }, + mounts: [ + { + mount_path: 'kvv2-engine-0', + counts: { + clients: 66, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 66, + acme_clients: 0, + }, + }, + { + mount_path: 'auth/authid0', + counts: { + clients: 28, + entity_clients: 9, + non_entity_clients: 19, + distinct_entities: 9, + non_entity_tokens: 19, + secret_syncs: 0, + acme_clients: 0, + }, + }, + { + mount_path: 'pki-engine-0', + counts: { + clients: 20, + entity_clients: 0, + non_entity_clients: 0, + distinct_entities: 0, + non_entity_tokens: 0, + secret_syncs: 0, + acme_clients: 20, + }, + }, + ], + }, + ], + }, + }, + ], + total: { + distinct_entities: 1816, + entity_clients: 1816, + non_entity_tokens: 3117, + non_entity_clients: 3117, + secret_syncs: 2672, + acme_clients: 200, + clients: 7805, + }, +}; + +// format returned by model hook in routes/vault/cluster/clients.ts +export const VERSION_HISTORY = [ + { + version: '1.9.0', + previousVersion: null, + timestampInstalled: LICENSE_START.toISOString(), + }, + { + version: '1.9.1', + previousVersion: '1.9.0', + timestampInstalled: addMonths(LICENSE_START, 1).toISOString(), + }, + { + version: '1.10.1', + previousVersion: '1.9.1', + timestampInstalled: addMonths(LICENSE_START, 2).toISOString(), + }, + { + version: '1.14.4', + previousVersion: '1.10.1', + timestampInstalled: addMonths(LICENSE_START, 3).toISOString(), + }, + { + version: '1.16.0', + previousVersion: '1.14.4', + timestampInstalled: addMonths(LICENSE_START, 4).toISOString(), + }, +]; + +// order of this array matters because index 0 is a month without data +export const SERIALIZED_ACTIVITY_RESPONSE = { + by_namespace: [ + { + label: 'root', + clients: 5429, + entity_clients: 1033, + non_entity_clients: 1924, + secret_syncs: 2397, + acme_clients: 75, + mounts: [ + { + acme_clients: 0, + clients: 2957, + entity_clients: 1033, + label: 'auth/authid0', + non_entity_clients: 1924, + secret_syncs: 0, + }, + { + acme_clients: 0, + clients: 2397, + entity_clients: 0, + label: 'kvv2-engine-0', + non_entity_clients: 0, + secret_syncs: 2397, + }, + { + acme_clients: 75, + clients: 75, + entity_clients: 0, + label: 'pki-engine-0', + non_entity_clients: 0, + secret_syncs: 0, + }, + ], + }, + { + label: 'ns/1', + clients: 2376, + entity_clients: 783, + non_entity_clients: 1193, + secret_syncs: 275, + acme_clients: 125, + mounts: [ + { + label: 'auth/authid0', + clients: 1976, + entity_clients: 783, + non_entity_clients: 1193, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'kvv2-engine-0', + clients: 275, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 275, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + acme_clients: 125, + clients: 125, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + }, + ], + }, + ], + by_month: [ + { + month: '8/23', + timestamp: '2023-08-01T00:00:00Z', + namespaces: [], + new_clients: { + month: '8/23', + timestamp: '2023-08-01T00:00:00Z', + namespaces: [], + }, + namespaces_by_key: {}, + }, + { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + clients: 8592, + entity_clients: 1329, + non_entity_clients: 1738, + secret_syncs: 5525, + namespaces: [ + { + label: 'root', + clients: 5707, + entity_clients: 1279, + non_entity_clients: 1598, + secret_syncs: 2755, + acme_clients: 75, + mounts: [ + { + label: 'auth/authid0', + clients: 2877, + entity_clients: 1279, + non_entity_clients: 1598, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'kvv2-engine-0', + clients: 2755, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 2755, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + acme_clients: 75, + clients: 75, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + }, + ], + }, + { + label: 'ns/1', + clients: 3085, + entity_clients: 50, + non_entity_clients: 140, + secret_syncs: 2770, + acme_clients: 125, + mounts: [ + { + label: 'kvv2-engine-0', + clients: 2770, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 2770, + acme_clients: 0, + }, + { + label: 'auth/authid0', + clients: 190, + entity_clients: 50, + non_entity_clients: 140, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + acme_clients: 125, + clients: 125, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + }, + ], + }, + ], + namespaces_by_key: { + root: { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + clients: 5707, + entity_clients: 1279, + non_entity_clients: 1598, + secret_syncs: 2755, + acme_clients: 75, + new_clients: { + month: '9/23', + label: 'root', + clients: 114, + entity_clients: 9, + non_entity_clients: 19, + secret_syncs: 66, + acme_clients: 20, + mounts: [ + { + label: 'kvv2-engine-0', + clients: 66, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 66, + acme_clients: 0, + }, + { + label: 'auth/authid0', + clients: 28, + entity_clients: 9, + non_entity_clients: 19, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + clients: 20, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + acme_clients: 20, + }, + ], + }, + mounts_by_key: { + 'auth/authid0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + label: 'auth/authid0', + clients: 2877, + entity_clients: 1279, + non_entity_clients: 1598, + secret_syncs: 0, + acme_clients: 0, + new_clients: { + month: '9/23', + label: 'auth/authid0', + clients: 28, + entity_clients: 9, + non_entity_clients: 19, + secret_syncs: 0, + acme_clients: 0, + }, + }, + 'kvv2-engine-0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + label: 'kvv2-engine-0', + clients: 2755, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 2755, + acme_clients: 0, + new_clients: { + month: '9/23', + label: 'kvv2-engine-0', + clients: 66, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 66, + acme_clients: 0, + }, + }, + 'pki-engine-0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + label: 'pki-engine-0', + clients: 75, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + acme_clients: 75, + new_clients: { + month: '9/23', + label: 'pki-engine-0', + acme_clients: 20, + clients: 20, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + }, + }, + }, + }, + 'ns/1': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + clients: 3085, + entity_clients: 50, + non_entity_clients: 140, + secret_syncs: 2770, + acme_clients: 125, + new_clients: { + month: '9/23', + label: 'ns/1', + clients: 222, + entity_clients: 30, + non_entity_clients: 62, + secret_syncs: 100, + acme_clients: 30, + mounts: [ + { + label: 'kvv2-engine-0', + clients: 100, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 100, + acme_clients: 0, + }, + { + label: 'auth/authid0', + clients: 92, + entity_clients: 30, + non_entity_clients: 62, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + acme_clients: 30, + clients: 30, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + }, + ], + }, + mounts_by_key: { + 'kvv2-engine-0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + label: 'kvv2-engine-0', + clients: 2770, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 2770, + acme_clients: 0, + new_clients: { + month: '9/23', + label: 'kvv2-engine-0', + clients: 100, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 100, + acme_clients: 0, + }, + }, + 'auth/authid0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + label: 'auth/authid0', + clients: 190, + entity_clients: 50, + non_entity_clients: 140, + secret_syncs: 0, + acme_clients: 0, + new_clients: { + month: '9/23', + label: 'auth/authid0', + clients: 92, + entity_clients: 30, + non_entity_clients: 62, + secret_syncs: 0, + acme_clients: 0, + }, + }, + 'pki-engine-0': { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + clients: 125, + acme_clients: 125, + entity_clients: 0, + label: 'pki-engine-0', + non_entity_clients: 0, + secret_syncs: 0, + new_clients: { + acme_clients: 30, + clients: 30, + entity_clients: 0, + label: 'pki-engine-0', + month: '9/23', + non_entity_clients: 0, + secret_syncs: 0, + }, + }, + }, + }, + }, + new_clients: { + month: '9/23', + timestamp: '2023-09-01T00:00:00Z', + clients: 336, + entity_clients: 39, + non_entity_clients: 81, + secret_syncs: 166, + acme_clients: 50, + namespaces: [ + { + label: 'ns/1', + clients: 222, + entity_clients: 30, + non_entity_clients: 62, + secret_syncs: 100, + acme_clients: 30, + mounts: [ + { + label: 'kvv2-engine-0', + clients: 100, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 100, + acme_clients: 0, + }, + { + label: 'auth/authid0', + clients: 92, + entity_clients: 30, + non_entity_clients: 62, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + clients: 30, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + acme_clients: 30, + }, + ], + }, + { + label: 'root', + clients: 114, + entity_clients: 9, + non_entity_clients: 19, + secret_syncs: 66, + acme_clients: 20, + mounts: [ + { + label: 'kvv2-engine-0', + clients: 66, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 66, + acme_clients: 0, + }, + { + label: 'auth/authid0', + clients: 28, + entity_clients: 9, + non_entity_clients: 19, + secret_syncs: 0, + acme_clients: 0, + }, + { + label: 'pki-engine-0', + clients: 20, + entity_clients: 0, + non_entity_clients: 0, + secret_syncs: 0, + acme_clients: 20, + }, + ], + }, + ], + }, + }, + ], +}; diff --git a/ui/tests/integration/components/clients/page/sync-test.js b/ui/tests/integration/components/clients/page/sync-test.js index 623fe9d723..0d75c9ae3b 100644 --- a/ui/tests/integration/components/clients/page/sync-test.js +++ b/ui/tests/integration/components/clients/page/sync-test.js @@ -84,7 +84,11 @@ module('Integration | Component | clients | Clients::Page::Sync', function (hook }); const dataBars = findAll(charts.dataBar).filter((b) => b.hasAttribute('height')); - assert.strictEqual(dataBars.length, this.activity.byMonth.filter((m) => m.counts !== null).length); + assert.strictEqual( + dataBars.length, + this.activity.byMonth.filter((m) => m.clients).length, + 'it renders a bar for each non-zero month' + ); }); test('it should render an empty state for no monthly data', async function (assert) { diff --git a/ui/tests/integration/components/clients/page/token-test.js b/ui/tests/integration/components/clients/page/token-test.js index f442790693..cee69399aa 100644 --- a/ui/tests/integration/components/clients/page/token-test.js +++ b/ui/tests/integration/components/clients/page/token-test.js @@ -89,8 +89,8 @@ module('Integration | Component | clients | Page::Token', function (hooks) { assert .dom(`${chart} ${ts.charts.bar.dataBar}`) .exists( - { count: this.activity.byMonth.filter((m) => m.counts !== null).length * 2 }, - 'renders correct number of data bars' + { count: this.activity.byMonth.filter((m) => m.clients).length * 2 }, + 'renders two stacked data bars of entity/non-entity clients for each month' ); const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { withTimeZone: true, @@ -127,8 +127,8 @@ module('Integration | Component | clients | Page::Token', function (hooks) { assert .dom(`${chart} ${ts.charts.bar.dataBar}`) .exists( - { count: this.activity.byMonth.filter((m) => m.counts !== null).length * 2 }, - 'renders correct number of data bars' + { count: this.activity.byMonth.filter((m) => m.clients).length * 2 }, + 'renders two stacked bars of new entity/non-entity clients for each month' ); const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { withTimeZone: true, diff --git a/ui/tests/integration/components/clients/running-total-test.js b/ui/tests/integration/components/clients/running-total-test.js index e8ca539693..fc42e32aef 100644 --- a/ui/tests/integration/components/clients/running-total-test.js +++ b/ui/tests/integration/components/clients/running-total-test.js @@ -104,7 +104,7 @@ module('Integration | Component | clients/running-total', function (hooks) { assert .dom(ts.charts.line.plotPoint) .exists( - { count: this.byMonthActivity.filter((m) => m.counts !== null).length }, + { count: this.byMonthActivity.filter((m) => m.clients).length }, 'renders correct number of plot points' ); }); diff --git a/ui/tests/integration/utils/client-count-utils-test.js b/ui/tests/integration/utils/client-count-utils-test.js index 1f2cb0d986..907f0be10f 100644 --- a/ui/tests/integration/utils/client-count-utils-test.js +++ b/ui/tests/integration/utils/client-count-utils-test.js @@ -9,311 +9,30 @@ import { filterVersionHistory, formatByMonths, formatByNamespace, - homogenizeClientNaming, + destructureClientCounts, namespaceArrayToObject, sortMonthsByTimestamp, } from 'core/utils/client-count-utils'; import { LICENSE_START } from 'vault/mirage/handlers/clients'; -import { addMonths } from 'date-fns'; - -const RESPONSE = { - start_time: '2023-08-01T00:00:00.000Z', - end_time: '2023-09-30T00:00:00.000Z', - by_namespace: [ - { - namespace_id: 'root', - namespace_path: '', - counts: { - distinct_entities: 1033, - entity_clients: 1033, - non_entity_tokens: 1924, - non_entity_clients: 1924, - secret_syncs: 2397, - clients: 5354, - }, - mounts: [ - { - mount_path: 'auth/authid0', - counts: { - clients: 2957, - entity_clients: 1033, - non_entity_clients: 1924, - distinct_entities: 1033, - non_entity_tokens: 1924, - secret_syncs: 0, - }, - }, - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 2397, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 2397, - }, - }, - ], - }, - { - namespace_id: '81ry61', - namespace_path: 'ns/1', - counts: { - distinct_entities: 783, - entity_clients: 783, - non_entity_tokens: 1193, - non_entity_clients: 1193, - secret_syncs: 275, - clients: 2251, - }, - mounts: [ - { - mount_path: 'auth/authid0', - counts: { - clients: 1976, - entity_clients: 783, - non_entity_clients: 1193, - distinct_entities: 783, - non_entity_tokens: 1193, - secret_syncs: 0, - }, - }, - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 275, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 275, - }, - }, - ], - }, - ], - months: [ - { - timestamp: '2023-08-01T00:00:00-07:00', - counts: null, - namespaces: null, - new_clients: null, - }, - { - timestamp: '2023-09-01T00:00:00-07:00', - counts: { - distinct_entities: 1329, - entity_clients: 1329, - non_entity_tokens: 1738, - non_entity_clients: 1738, - secret_syncs: 5525, - clients: 8592, - }, - namespaces: [ - { - namespace_id: 'root', - namespace_path: '', - counts: { - distinct_entities: 1279, - entity_clients: 1279, - non_entity_tokens: 1598, - non_entity_clients: 1598, - secret_syncs: 2755, - clients: 5632, - }, - mounts: [ - { - mount_path: 'auth/authid0', - counts: { - clients: 2877, - entity_clients: 1279, - non_entity_clients: 1598, - distinct_entities: 1279, - non_entity_tokens: 1598, - secret_syncs: 0, - }, - }, - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 2755, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 2755, - }, - }, - ], - }, - { - namespace_id: '81ry61', - namespace_path: 'ns/1', - counts: { - distinct_entities: 50, - entity_clients: 50, - non_entity_tokens: 140, - non_entity_clients: 140, - secret_syncs: 2770, - clients: 2960, - }, - mounts: [ - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 2770, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 2770, - }, - }, - { - mount_path: 'auth/authid0', - counts: { - clients: 190, - entity_clients: 50, - non_entity_clients: 140, - distinct_entities: 50, - non_entity_tokens: 140, - secret_syncs: 0, - }, - }, - ], - }, - ], - new_clients: { - counts: { - distinct_entities: 39, - entity_clients: 39, - non_entity_tokens: 81, - non_entity_clients: 81, - secret_syncs: 166, - clients: 286, - }, - namespaces: [ - { - namespace_id: '81ry61', - namespace_path: 'ns/1', - counts: { - distinct_entities: 30, - entity_clients: 30, - non_entity_tokens: 62, - non_entity_clients: 62, - secret_syncs: 100, - clients: 192, - }, - mounts: [ - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 100, - }, - }, - { - mount_path: 'auth/authid0', - counts: { - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - distinct_entities: 30, - non_entity_tokens: 62, - secret_syncs: 0, - }, - }, - ], - }, - { - namespace_id: 'root', - namespace_path: '', - counts: { - distinct_entities: 9, - entity_clients: 9, - non_entity_tokens: 19, - non_entity_clients: 19, - secret_syncs: 66, - clients: 94, - }, - mounts: [ - { - mount_path: 'kvv2-engine-0', - counts: { - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - distinct_entities: 0, - non_entity_tokens: 0, - secret_syncs: 66, - }, - }, - { - mount_path: 'auth/authid0', - counts: { - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - distinct_entities: 9, - non_entity_tokens: 19, - secret_syncs: 0, - }, - }, - ], - }, - ], - }, - }, - ], - total: { - distinct_entities: 1816, - entity_clients: 1816, - non_entity_tokens: 3117, - non_entity_clients: 3117, - secret_syncs: 2672, - clients: 7605, - }, -}; +import { + ACTIVITY_RESPONSE_STUB as RESPONSE, + VERSION_HISTORY, + SERIALIZED_ACTIVITY_RESPONSE, +} from 'vault/tests/helpers/clients'; +/* +formatByNamespace, formatByMonths, destructureClientCounts are utils +used to normalize the sys/counters/activity response in the clients/activity +serializer. these functions are tested individually here, instead of all at once +in a serializer test for easier debugging +*/ module('Integration | Util | client count utils', function (hooks) { setupTest(hooks); test('filterVersionHistory: returns version data for relevant upgrades that occurred during date range', async function (assert) { assert.expect(2); // LICENSE_START is '2023-07-02T00:00:00Z' - const versionHistory = [ - { - version: '1.9.0', - previousVersion: null, - timestampInstalled: LICENSE_START.toISOString(), - }, - { - version: '1.9.1', - previousVersion: '1.9.0', - timestampInstalled: addMonths(LICENSE_START, 1).toISOString(), - }, - { - version: '1.10.1', - previousVersion: '1.9.1', - timestampInstalled: addMonths(LICENSE_START, 2).toISOString(), - }, - { - version: '1.14.4', - previousVersion: '1.10.1', - timestampInstalled: addMonths(LICENSE_START, 3).toISOString(), - }, - { - version: '1.16.0', - previousVersion: '1.14.4', - timestampInstalled: addMonths(LICENSE_START, 4).toISOString(), - }, - ]; - const original = [...versionHistory]; + const original = [...VERSION_HISTORY]; const expected = [ { previousVersion: null, @@ -330,350 +49,92 @@ module('Integration | Util | client count utils', function (hooks) { const startTime = LICENSE_START.toISOString(); // same as license start to catch same day edge cases const endTime = '2024-03-04T16:14:21.000Z'; assert.propEqual( - filterVersionHistory(versionHistory, startTime, endTime), + filterVersionHistory(VERSION_HISTORY, startTime, endTime), expected, 'it only returns upgrades between given start and end times' ); - assert.propEqual(versionHistory, original, 'it does not modify original array'); + assert.propEqual(VERSION_HISTORY, original, 'it does not modify original array'); }); test('formatByMonths: formats the months array', async function (assert) { - assert.expect(2); + assert.expect(4); const original = [...RESPONSE.months]; - const expected = [ - { - month: '8/23', - timestamp: '2023-08-01T00:00:00-07:00', - counts: null, - namespaces: [], - new_clients: { - month: '8/23', - timestamp: '2023-08-01T00:00:00-07:00', - namespaces: [], - }, - namespaces_by_key: {}, - }, - { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - clients: 8592, - entity_clients: 1329, - non_entity_clients: 1738, - secret_syncs: 5525, - namespaces: [ - { - label: 'root', - clients: 5632, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 2755, - mounts: [ - { - label: 'auth/authid0', - clients: 2877, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 0, - }, - { - label: 'kvv2-engine-0', - clients: 2755, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2755, - }, - ], - }, - { - label: 'ns/1', - clients: 2960, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 2770, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 2770, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2770, - }, - { - label: 'auth/authid0', - clients: 190, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 0, - }, - ], - }, - ], - namespaces_by_key: { - root: { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - clients: 5632, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 2755, - new_clients: { - month: '9/23', - label: 'root', - clients: 94, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 66, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 66, - }, - { - label: 'auth/authid0', - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 0, - }, - ], - }, - mounts_by_key: { - 'auth/authid0': { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - label: 'auth/authid0', - clients: 2877, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 0, - new_clients: { - month: '9/23', - label: 'auth/authid0', - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 0, - }, - }, - 'kvv2-engine-0': { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - label: 'kvv2-engine-0', - clients: 2755, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2755, - new_clients: { - month: '9/23', - label: 'kvv2-engine-0', - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 66, - }, - }, - }, - }, - 'ns/1': { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - clients: 2960, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 2770, - new_clients: { - month: '9/23', - label: 'ns/1', - clients: 192, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 100, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 100, - }, - { - label: 'auth/authid0', - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 0, - }, - ], - }, - mounts_by_key: { - 'kvv2-engine-0': { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - label: 'kvv2-engine-0', - clients: 2770, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2770, - new_clients: { - month: '9/23', - label: 'kvv2-engine-0', - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 100, - }, - }, - 'auth/authid0': { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - label: 'auth/authid0', - clients: 190, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 0, - new_clients: { - month: '9/23', - label: 'auth/authid0', - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 0, - }, - }, - }, - }, - }, - new_clients: { - month: '9/23', - timestamp: '2023-09-01T00:00:00-07:00', - clients: 286, - entity_clients: 39, - non_entity_clients: 81, - secret_syncs: 166, - namespaces: [ - { - label: 'ns/1', - clients: 192, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 100, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 100, - }, - { - label: 'auth/authid0', - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 0, - }, - ], - }, - { - label: 'root', - clients: 94, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 66, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 66, - }, - { - label: 'auth/authid0', - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 0, - }, - ], - }, - ], - }, - }, - ]; - assert.propEqual(formatByMonths(RESPONSE.months), expected); + const [formattedNoData, formattedWithActivity] = formatByMonths(RESPONSE.months); + + // instead of asserting the whole expected response, broken up so tests are easier to debug + // but kept whole above to copy/paste updated response expectations in the future + const [expectedNoData, expectedWithActivity] = SERIALIZED_ACTIVITY_RESPONSE.by_month; + const { namespaces, new_clients } = expectedWithActivity; + + assert.propEqual(formattedNoData, expectedNoData, 'it formats months without data'); + assert.propEqual( + formattedWithActivity.namespaces, + namespaces, + 'it formats namespaces array for months with data' + ); + assert.propEqual( + formattedWithActivity.new_clients, + new_clients, + 'it formats new_clients block for months with data' + ); assert.propEqual(RESPONSE.months, original, 'it does not modify original months array'); }); - test('formatByNamespace: formats namespace arrays with and without mounts', async function (assert) { - assert.expect(2); + test('formatByNamespace: formats namespace array with mounts', async function (assert) { + assert.expect(3); const original = [...RESPONSE.by_namespace]; - const expected = [ - { - clients: 5354, - entity_clients: 1033, - label: 'root', - mounts: [ - { - clients: 2957, - entity_clients: 1033, - label: 'auth/authid0', - non_entity_clients: 1924, - secret_syncs: 0, - }, - { - clients: 2397, - entity_clients: 0, - label: 'kvv2-engine-0', - non_entity_clients: 0, - secret_syncs: 2397, - }, - ], - non_entity_clients: 1924, - secret_syncs: 2397, - }, - { - clients: 2251, - entity_clients: 783, - label: 'ns/1', - mounts: [ - { - clients: 1976, - entity_clients: 783, - label: 'auth/authid0', - non_entity_clients: 1193, - secret_syncs: 0, - }, - { - clients: 275, - entity_clients: 0, - label: 'kvv2-engine-0', - non_entity_clients: 0, - secret_syncs: 275, - }, - ], - non_entity_clients: 1193, - secret_syncs: 275, - }, - ]; - assert.propEqual(formatByNamespace(RESPONSE.by_namespace), expected); + const [formattedRoot, formattedNs1] = formatByNamespace(RESPONSE.by_namespace); + const [root, ns1] = SERIALIZED_ACTIVITY_RESPONSE.by_namespace; + + assert.propEqual(formattedRoot, root, 'it formats root namespace'); + assert.propEqual(formattedNs1, ns1, 'it formats ns1/ namespace'); assert.propEqual(RESPONSE.by_namespace, original, 'it does not modify original by_namespace array'); }); - test('homogenizeClientNaming: homogenizes key names when both old and new keys exist, or just old key names', async function (assert) { + test('formatByNamespace: formats namespace array with no mounts (activity log data < 1.10)', async function (assert) { + assert.expect(1); + const noMounts = [ + { + namespace_id: 'root', + namespace_path: '', + counts: { + distinct_entities: 10, + entity_clients: 10, + non_entity_tokens: 20, + non_entity_clients: 20, + secret_syncs: 0, + acme_clients: 0, + clients: 30, + }, + mounts: 'no mount accessor (pre-1.10 upgrade?)', + }, + ]; + const expected = [ + { + acme_clients: 0, + clients: 30, + entity_clients: 10, + label: 'root', + mounts: [], + non_entity_clients: 20, + secret_syncs: 0, + }, + ]; + assert.propEqual(formatByNamespace(noMounts), expected, 'it formats namespace without mounts'); + }); + + test('destructureClientCounts: homogenizes key names when both old and new keys exist, or just old key names', async function (assert) { assert.expect(2); const original = { ...RESPONSE.total }; const expected = { entity_clients: 1816, non_entity_clients: 3117, secret_syncs: 2672, - clients: 7605, + acme_clients: 200, + clients: 7805, }; - assert.propEqual(homogenizeClientNaming(RESPONSE.total), expected); + assert.propEqual(destructureClientCounts(RESPONSE.total), expected); assert.propEqual(RESPONSE.total, original, 'it does not modify original object'); }); @@ -687,158 +148,22 @@ module('Integration | Util | client count utils', function (hooks) { assert.propEqual(RESPONSE.months, original, 'it does not modify original array'); }); - test('namespaceArrayToObject: transforms data without modifying original', async function (assert) { - assert.expect(2); + test('namespaceArrayToObject: it generates namespaces_by_key without modifying original', async function (assert) { + assert.expect(3); + + // month at 0-index has no data so use second month in array const { namespaces, new_clients } = RESPONSE.months[1]; - const monthNamespaces = formatByNamespace(namespaces); - const newClients = formatByNamespace(new_clients.namespaces); + const original = { ...RESPONSE.months[1] }; const byNamespaceKeyObject = namespaceArrayToObject( - monthNamespaces, - newClients, + formatByNamespace(namespaces), + formatByNamespace(new_clients.namespaces), '9/23', - '2023-9-01T00:00:00Z' + '2023-09-01T00:00:00Z' ); - const expected = { - root: { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - clients: 5632, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 2755, - new_clients: { - month: '9/23', - label: 'root', - clients: 94, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 66, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 66, - }, - { - label: 'auth/authid0', - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 0, - }, - ], - }, - mounts_by_key: { - 'auth/authid0': { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - label: 'auth/authid0', - clients: 2877, - entity_clients: 1279, - non_entity_clients: 1598, - secret_syncs: 0, - new_clients: { - month: '9/23', - label: 'auth/authid0', - clients: 28, - entity_clients: 9, - non_entity_clients: 19, - secret_syncs: 0, - }, - }, - 'kvv2-engine-0': { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - label: 'kvv2-engine-0', - clients: 2755, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2755, - new_clients: { - month: '9/23', - label: 'kvv2-engine-0', - clients: 66, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 66, - }, - }, - }, - }, - 'ns/1': { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - clients: 2960, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 2770, - new_clients: { - month: '9/23', - label: 'ns/1', - clients: 192, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 100, - mounts: [ - { - label: 'kvv2-engine-0', - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 100, - }, - { - label: 'auth/authid0', - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 0, - }, - ], - }, - mounts_by_key: { - 'kvv2-engine-0': { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - label: 'kvv2-engine-0', - clients: 2770, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 2770, - new_clients: { - month: '9/23', - label: 'kvv2-engine-0', - clients: 100, - entity_clients: 0, - non_entity_clients: 0, - secret_syncs: 100, - }, - }, - 'auth/authid0': { - month: '9/23', - timestamp: '2023-9-01T00:00:00Z', - label: 'auth/authid0', - clients: 190, - entity_clients: 50, - non_entity_clients: 140, - secret_syncs: 0, - new_clients: { - month: '9/23', - label: 'auth/authid0', - clients: 92, - entity_clients: 30, - non_entity_clients: 62, - secret_syncs: 0, - }, - }, - }, - }, - }; + assert.propEqual( byNamespaceKeyObject, - expected, + SERIALIZED_ACTIVITY_RESPONSE.by_month[1].namespaces_by_key, 'it returns object with namespaces by key and includes mounts_by_key' ); assert.propEqual( @@ -846,5 +171,6 @@ module('Integration | Util | client count utils', function (hooks) { {}, 'returns an empty object when monthByNamespace = null' ); + assert.propEqual(RESPONSE.months[1], original, 'it does not modify original month data'); }); });