mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-03 12:07:54 +00:00
UI: Fix client counts bug when no new clients (#27352)
This commit is contained in:
3
changelog/27352.txt
Normal file
3
changelog/27352.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
ui: fix issue where a month without new clients breaks the client count dashboard
|
||||||
|
```
|
||||||
@@ -86,7 +86,9 @@ export const formatDateObject = (dateObj: { monthIdx: number; year: number }, is
|
|||||||
return getUnixTime(utc);
|
return getUnixTime(utc);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatByMonths = (monthsArray: ActivityMonthBlock[] | EmptyActivityMonthBlock[]) => {
|
export const formatByMonths = (
|
||||||
|
monthsArray: (ActivityMonthBlock | EmptyActivityMonthBlock | NoNewClientsActivityMonthBlock)[]
|
||||||
|
) => {
|
||||||
const sortedPayload = sortMonthsByTimestamp(monthsArray);
|
const sortedPayload = sortMonthsByTimestamp(monthsArray);
|
||||||
return sortedPayload?.map((m) => {
|
return sortedPayload?.map((m) => {
|
||||||
const month = parseAPITimestamp(m.timestamp, 'M/yy') as string;
|
const month = parseAPITimestamp(m.timestamp, 'M/yy') as string;
|
||||||
@@ -95,23 +97,28 @@ export const formatByMonths = (monthsArray: ActivityMonthBlock[] | EmptyActivity
|
|||||||
if (m.counts) {
|
if (m.counts) {
|
||||||
const totalClientsByNamespace = formatByNamespace(m.namespaces);
|
const totalClientsByNamespace = formatByNamespace(m.namespaces);
|
||||||
const newClientsByNamespace = formatByNamespace(m.new_clients?.namespaces);
|
const newClientsByNamespace = formatByNamespace(m.new_clients?.namespaces);
|
||||||
|
|
||||||
|
let newClients: ByMonthNewClients = { month, timestamp, namespaces: [] };
|
||||||
|
if (m.new_clients?.counts) {
|
||||||
|
newClients = {
|
||||||
|
month,
|
||||||
|
timestamp,
|
||||||
|
...destructureClientCounts(m?.new_clients?.counts),
|
||||||
|
namespaces: formatByNamespace(m.new_clients?.namespaces),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
month,
|
month,
|
||||||
timestamp,
|
timestamp,
|
||||||
...destructureClientCounts(m.counts),
|
...destructureClientCounts(m.counts),
|
||||||
namespaces: formatByNamespace(m.namespaces) || [],
|
namespaces: formatByNamespace(m.namespaces),
|
||||||
namespaces_by_key: namespaceArrayToObject(
|
namespaces_by_key: namespaceArrayToObject(
|
||||||
totalClientsByNamespace,
|
totalClientsByNamespace,
|
||||||
newClientsByNamespace,
|
newClientsByNamespace,
|
||||||
month,
|
month,
|
||||||
m.timestamp
|
m.timestamp
|
||||||
),
|
),
|
||||||
new_clients: {
|
new_clients: newClients,
|
||||||
month,
|
|
||||||
timestamp,
|
|
||||||
...destructureClientCounts(m?.new_clients?.counts),
|
|
||||||
namespaces: formatByNamespace(m.new_clients?.namespaces) || [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// empty month
|
// empty month
|
||||||
@@ -125,7 +132,8 @@ export const formatByMonths = (monthsArray: ActivityMonthBlock[] | EmptyActivity
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatByNamespace = (namespaceArray: NamespaceObject[]) => {
|
export const formatByNamespace = (namespaceArray: NamespaceObject[] | null): ByNamespaceClients[] => {
|
||||||
|
if (!Array.isArray(namespaceArray)) return [];
|
||||||
return namespaceArray.map((ns) => {
|
return namespaceArray.map((ns) => {
|
||||||
// i.e. 'namespace_path' is an empty string for 'root', so use namespace_id
|
// 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;
|
const label = ns.namespace_path === '' ? ns.namespace_id : ns.namespace_path;
|
||||||
@@ -158,7 +166,9 @@ export const destructureClientCounts = (verboseObject: Counts | ByNamespaceClien
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sortMonthsByTimestamp = (monthsArray: ActivityMonthBlock[] | EmptyActivityMonthBlock[]) => {
|
export const sortMonthsByTimestamp = (
|
||||||
|
monthsArray: (ActivityMonthBlock | EmptyActivityMonthBlock | NoNewClientsActivityMonthBlock)[]
|
||||||
|
) => {
|
||||||
const sortedPayload = [...monthsArray];
|
const sortedPayload = [...monthsArray];
|
||||||
return sortedPayload.sort((a, b) =>
|
return sortedPayload.sort((a, b) =>
|
||||||
compareAsc(parseAPITimestamp(a.timestamp) as Date, parseAPITimestamp(b.timestamp) as Date)
|
compareAsc(parseAPITimestamp(a.timestamp) as Date, parseAPITimestamp(b.timestamp) as Date)
|
||||||
@@ -168,7 +178,7 @@ export const sortMonthsByTimestamp = (monthsArray: ActivityMonthBlock[] | EmptyA
|
|||||||
export const namespaceArrayToObject = (
|
export const namespaceArrayToObject = (
|
||||||
monthTotals: ByNamespaceClients[],
|
monthTotals: ByNamespaceClients[],
|
||||||
// technically this arg (monthNew) is the same type as above, just nested inside monthly new clients
|
// technically this arg (monthNew) is the same type as above, just nested inside monthly new clients
|
||||||
monthNew: ByMonthClients['new_clients']['namespaces'],
|
monthNew: ByMonthClients['new_clients']['namespaces'] | null,
|
||||||
month: string,
|
month: string,
|
||||||
timestamp: string
|
timestamp: string
|
||||||
) => {
|
) => {
|
||||||
@@ -176,36 +186,45 @@ export const namespaceArrayToObject = (
|
|||||||
// it's an object in each month data block where the keys are namespace paths
|
// it's an object in each month data block where the keys are namespace paths
|
||||||
// and values include new and total client counts for that namespace in that month
|
// and values include new and total client counts for that namespace in that month
|
||||||
const namespaces_by_key = monthTotals.reduce((nsObject: { [key: string]: NamespaceByKey }, ns) => {
|
const namespaces_by_key = monthTotals.reduce((nsObject: { [key: string]: NamespaceByKey }, ns) => {
|
||||||
const newNsClients = monthNew?.find((n) => n.label === ns.label);
|
const keyedNs: NamespaceByKey = {
|
||||||
if (newNsClients) {
|
...destructureClientCounts(ns),
|
||||||
// mounts_by_key is is used to filter further in a namespace and get monthly activity by mount
|
timestamp,
|
||||||
// it's an object inside the namespace block where the keys are mount paths
|
month,
|
||||||
// and the values include new and total client counts for that mount in that month
|
mounts_by_key: {},
|
||||||
const mounts_by_key = ns.mounts.reduce(
|
new_clients: {
|
||||||
(mountObj: { [key: string]: MountByKey }, mount) => {
|
|
||||||
const newMountClients = newNsClients.mounts.find((m) => m.label === mount.label);
|
|
||||||
|
|
||||||
if (newMountClients) {
|
|
||||||
mountObj[mount.label] = {
|
|
||||||
...mount,
|
|
||||||
timestamp,
|
|
||||||
month,
|
|
||||||
new_clients: { month, timestamp, ...newMountClients },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return mountObj;
|
|
||||||
},
|
|
||||||
{} as { [key: string]: MountByKey }
|
|
||||||
);
|
|
||||||
|
|
||||||
nsObject[ns.label] = {
|
|
||||||
...destructureClientCounts(ns),
|
|
||||||
timestamp,
|
|
||||||
month,
|
month,
|
||||||
new_clients: { month, timestamp, ...newNsClients },
|
timestamp,
|
||||||
mounts_by_key,
|
label: ns.label,
|
||||||
};
|
mounts: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const newNsClients = monthNew?.find((n) => n.label === ns.label);
|
||||||
|
// mounts_by_key is is used to filter further in a namespace and get monthly activity by mount
|
||||||
|
// it's an object inside the namespace block where the keys are mount paths
|
||||||
|
// and the values include new and total client counts for that mount in that month
|
||||||
|
keyedNs.mounts_by_key = ns.mounts.reduce(
|
||||||
|
(mountObj: { [key: string]: MountByKey }, mount) => {
|
||||||
|
const mountNewClients = newNsClients ? newNsClients.mounts.find((m) => m.label === mount.label) : {};
|
||||||
|
mountObj[mount.label] = {
|
||||||
|
...mount,
|
||||||
|
timestamp,
|
||||||
|
month,
|
||||||
|
new_clients: {
|
||||||
|
timestamp,
|
||||||
|
month,
|
||||||
|
label: mount.label,
|
||||||
|
...mountNewClients,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return mountObj;
|
||||||
|
},
|
||||||
|
{} as { [key: string]: MountByKey }
|
||||||
|
);
|
||||||
|
if (newNsClients) {
|
||||||
|
keyedNs.new_clients = { month, timestamp, ...newNsClients };
|
||||||
}
|
}
|
||||||
|
nsObject[ns.label] = keyedNs;
|
||||||
return nsObject;
|
return nsObject;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
@@ -239,6 +258,15 @@ export interface TotalClients {
|
|||||||
acme_clients: number;
|
acme_clients: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extend this type when the counts are optional (eg for new clients)
|
||||||
|
interface TotalClientsSometimes {
|
||||||
|
clients?: number;
|
||||||
|
entity_clients?: number;
|
||||||
|
non_entity_clients?: number;
|
||||||
|
secret_syncs?: number;
|
||||||
|
acme_clients?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ByNamespaceClients extends TotalClients {
|
export interface ByNamespaceClients extends TotalClients {
|
||||||
label: string;
|
label: string;
|
||||||
mounts: MountClients[];
|
mounts: MountClients[];
|
||||||
@@ -255,7 +283,9 @@ export interface ByMonthClients extends TotalClients {
|
|||||||
namespaces_by_key: { [key: string]: NamespaceByKey };
|
namespaces_by_key: { [key: string]: NamespaceByKey };
|
||||||
new_clients: ByMonthNewClients;
|
new_clients: ByMonthNewClients;
|
||||||
}
|
}
|
||||||
export interface ByMonthNewClients extends TotalClients {
|
|
||||||
|
// clients numbers are only returned if month is of type ActivityMonthBlock
|
||||||
|
export interface ByMonthNewClients extends TotalClientsSometimes {
|
||||||
month: string;
|
month: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
namespaces: ByNamespaceClients[];
|
namespaces: ByNamespaceClients[];
|
||||||
@@ -268,7 +298,7 @@ export interface NamespaceByKey extends TotalClients {
|
|||||||
new_clients: NamespaceNewClients;
|
new_clients: NamespaceNewClients;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NamespaceNewClients extends TotalClients {
|
export interface NamespaceNewClients extends TotalClientsSometimes {
|
||||||
month: string;
|
month: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -282,7 +312,7 @@ export interface MountByKey extends TotalClients {
|
|||||||
new_clients: MountNewClients;
|
new_clients: MountNewClients;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MountNewClients extends TotalClients {
|
export interface MountNewClients extends TotalClientsSometimes {
|
||||||
month: string;
|
month: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -308,6 +338,16 @@ export interface ActivityMonthBlock {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NoNewClientsActivityMonthBlock {
|
||||||
|
timestamp: string; // YYYY-MM-01T00:00:00Z (always the first day of the month)
|
||||||
|
counts: Counts;
|
||||||
|
namespaces: NamespaceObject[];
|
||||||
|
new_clients: {
|
||||||
|
counts: null;
|
||||||
|
namespaces: null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface EmptyActivityMonthBlock {
|
export interface EmptyActivityMonthBlock {
|
||||||
timestamp: string; // YYYY-MM-01T00:00:00Z (always the first day of the month)
|
timestamp: string; // YYYY-MM-01T00:00:00Z (always the first day of the month)
|
||||||
counts: null;
|
counts: null;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function assertBarChart(assert, chartName, byMonthData, isStacked = false
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ACTIVITY_RESPONSE_STUB = {
|
export const ACTIVITY_RESPONSE_STUB = {
|
||||||
start_time: '2023-08-01T00:00:00Z',
|
start_time: '2023-06-01T00:00:00Z',
|
||||||
end_time: '2023-09-30T23:59:59Z', // is always the last day and hour of the month queried
|
end_time: '2023-09-30T23:59:59Z', // is always the last day and hour of the month queried
|
||||||
by_namespace: [
|
by_namespace: [
|
||||||
{
|
{
|
||||||
@@ -148,11 +148,209 @@ export const ACTIVITY_RESPONSE_STUB = {
|
|||||||
],
|
],
|
||||||
months: [
|
months: [
|
||||||
{
|
{
|
||||||
timestamp: '2023-08-01T00:00:00Z',
|
timestamp: '2023-06-01T00:00:00Z',
|
||||||
counts: null,
|
counts: null,
|
||||||
namespaces: null,
|
namespaces: null,
|
||||||
new_clients: null,
|
new_clients: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
namespace_id: 'root',
|
||||||
|
namespace_path: '',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
mount_path: 'pki-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'auth/authid/0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'kvv2-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
new_clients: {
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
namespace_id: 'root',
|
||||||
|
namespace_path: '',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
mount_path: 'pki-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'auth/authid/0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'kvv2-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
namespace_id: 'root',
|
||||||
|
namespace_path: '',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 100,
|
||||||
|
non_entity_tokens: 100,
|
||||||
|
},
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
mount_path: 'pki-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'auth/authid/0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mount_path: 'kvv2-engine-0',
|
||||||
|
counts: {
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
distinct_entities: 0,
|
||||||
|
non_entity_tokens: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
new_clients: {
|
||||||
|
counts: null,
|
||||||
|
namespaces: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
timestamp: '2023-09-01T00:00:00Z',
|
timestamp: '2023-09-01T00:00:00Z',
|
||||||
counts: {
|
counts: {
|
||||||
@@ -646,10 +844,323 @@ export const SERIALIZED_ACTIVITY_RESPONSE = {
|
|||||||
],
|
],
|
||||||
by_month: [
|
by_month: [
|
||||||
{
|
{
|
||||||
month: '8/23',
|
month: '6/23',
|
||||||
timestamp: '2023-08-01T00:00:00Z',
|
timestamp: '2023-06-01T00:00:00Z',
|
||||||
namespaces: [],
|
namespaces: [],
|
||||||
namespaces_by_key: {},
|
namespaces_by_key: {},
|
||||||
|
new_clients: {
|
||||||
|
month: '6/23',
|
||||||
|
timestamp: '2023-06-01T00:00:00Z',
|
||||||
|
namespaces: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
label: 'root',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
namespaces_by_key: {
|
||||||
|
root: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
month: '7/23',
|
||||||
|
new_clients: {
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
label: 'root',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
mounts_by_key: {
|
||||||
|
'pki-engine-0': {
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
month: '7/23',
|
||||||
|
new_clients: {
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'auth/authid/0': {
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
month: '7/23',
|
||||||
|
new_clients: {
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'kvv2-engine-0': {
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
month: '7/23',
|
||||||
|
new_clients: {
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new_clients: {
|
||||||
|
month: '7/23',
|
||||||
|
timestamp: '2023-07-01T00:00:00Z',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
label: 'root',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
month: '8/23',
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
namespaces: [
|
||||||
|
{
|
||||||
|
label: 'root',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
mounts: [
|
||||||
|
{
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
namespaces_by_key: {
|
||||||
|
root: {
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 100,
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
month: '8/23',
|
||||||
|
new_clients: {
|
||||||
|
label: 'root',
|
||||||
|
month: '8/23',
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
mounts: [],
|
||||||
|
},
|
||||||
|
mounts_by_key: {
|
||||||
|
'pki-engine-0': {
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
acme_clients: 100,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 0,
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
month: '8/23',
|
||||||
|
new_clients: {
|
||||||
|
label: 'pki-engine-0',
|
||||||
|
month: '8/23',
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'auth/authid/0': {
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 100,
|
||||||
|
non_entity_clients: 100,
|
||||||
|
secret_syncs: 0,
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
month: '8/23',
|
||||||
|
new_clients: {
|
||||||
|
label: 'auth/authid/0',
|
||||||
|
month: '8/23',
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'kvv2-engine-0': {
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
acme_clients: 0,
|
||||||
|
clients: 100,
|
||||||
|
entity_clients: 0,
|
||||||
|
non_entity_clients: 0,
|
||||||
|
secret_syncs: 100,
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
month: '8/23',
|
||||||
|
new_clients: {
|
||||||
|
label: 'kvv2-engine-0',
|
||||||
|
month: '8/23',
|
||||||
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
new_clients: {
|
new_clients: {
|
||||||
month: '8/23',
|
month: '8/23',
|
||||||
timestamp: '2023-08-01T00:00:00Z',
|
timestamp: '2023-08-01T00:00:00Z',
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
|
|||||||
assert
|
assert
|
||||||
.dom(CLIENT_COUNT.statText('Total'))
|
.dom(CLIENT_COUNT.statText('Total'))
|
||||||
.hasText(
|
.hasText(
|
||||||
`Total The number of clients in this billing period (Aug 2023 - Sep 2023). ${formatNumber([
|
`Total The number of clients in this billing period (Jun 2023 - Sep 2023). ${formatNumber([
|
||||||
total.clients,
|
total.clients,
|
||||||
])}`
|
])}`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -129,27 +129,29 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('formatByMonths: it formats the months array', async function (assert) {
|
test('formatByMonths: it formats the months array', async function (assert) {
|
||||||
assert.expect(5);
|
assert.expect(9);
|
||||||
const original = [...RESPONSE.months];
|
const original = [...RESPONSE.months];
|
||||||
|
|
||||||
const [formattedNoData, formattedWithActivity] = formatByMonths(RESPONSE.months);
|
const [formattedNoData, formattedWithActivity, formattedNoNew] = formatByMonths(RESPONSE.months);
|
||||||
|
|
||||||
// instead of asserting the whole expected response, broken up so tests are easier to debug
|
// 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
|
// but kept whole above to copy/paste updated response expectations in the future
|
||||||
const [expectedNoData, expectedWithActivity] = SERIALIZED_ACTIVITY_RESPONSE.by_month;
|
const [expectedNoData, expectedWithActivity, expectedNoNew] = SERIALIZED_ACTIVITY_RESPONSE.by_month;
|
||||||
const { namespaces, new_clients } = expectedWithActivity;
|
|
||||||
|
|
||||||
assert.propEqual(formattedNoData, expectedNoData, 'it formats months without data');
|
assert.propEqual(formattedNoData, expectedNoData, 'it formats months without data');
|
||||||
assert.propEqual(
|
['namespaces', 'new_clients', 'namespaces_by_key'].forEach((key) => {
|
||||||
formattedWithActivity.namespaces,
|
assert.propEqual(
|
||||||
namespaces,
|
formattedWithActivity[key],
|
||||||
'it formats namespaces array for months with data'
|
expectedWithActivity[key],
|
||||||
);
|
`it formats ${key} array for months with data`
|
||||||
assert.propEqual(
|
);
|
||||||
formattedWithActivity.new_clients,
|
assert.propEqual(
|
||||||
new_clients,
|
formattedNoNew[key],
|
||||||
'it formats new_clients block for months with data'
|
expectedNoNew[key],
|
||||||
);
|
`it formats the ${key} array for months with no new clients`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
assert.propEqual(RESPONSE.months, original, 'it does not modify original months array');
|
assert.propEqual(RESPONSE.months, original, 'it does not modify original months array');
|
||||||
assert.propEqual(formatByMonths([]), [], 'it returns an empty array if the months key is empty');
|
assert.propEqual(formatByMonths([]), [], 'it returns an empty array if the months key is empty');
|
||||||
});
|
});
|
||||||
@@ -187,7 +189,7 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||||||
test('sortMonthsByTimestamp: sorts timestamps chronologically, oldest to most recent', async function (assert) {
|
test('sortMonthsByTimestamp: sorts timestamps chronologically, oldest to most recent', async function (assert) {
|
||||||
assert.expect(2);
|
assert.expect(2);
|
||||||
// API returns them in order so this test is extra extra
|
// API returns them in order so this test is extra extra
|
||||||
const unOrdered = [RESPONSE.months[1], RESPONSE.months[0]]; // mixup order
|
const unOrdered = [RESPONSE.months[1], RESPONSE.months[0], RESPONSE.months[3], RESPONSE.months[2]]; // mixup order
|
||||||
const original = [...RESPONSE.months];
|
const original = [...RESPONSE.months];
|
||||||
const expected = RESPONSE.months;
|
const expected = RESPONSE.months;
|
||||||
assert.propEqual(sortMonthsByTimestamp(unOrdered), expected);
|
assert.propEqual(sortMonthsByTimestamp(unOrdered), expected);
|
||||||
@@ -195,32 +197,30 @@ module('Integration | Util | client count utils', function (hooks) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('namespaceArrayToObject: it returns namespaces_by_key and mounts_by_key', async function (assert) {
|
test('namespaceArrayToObject: it returns namespaces_by_key and mounts_by_key', async function (assert) {
|
||||||
assert.expect(5);
|
// namespaceArrayToObject only called when there are counts, so skip month 0 which has no counts
|
||||||
|
for (let i = 1; i < RESPONSE.months.length; i++) {
|
||||||
|
const original = { ...RESPONSE.months[i] };
|
||||||
|
const expectedObject = SERIALIZED_ACTIVITY_RESPONSE.by_month[i].namespaces_by_key;
|
||||||
|
const formattedTotal = formatByNamespace(RESPONSE.months[i].namespaces);
|
||||||
|
const testObject = namespaceArrayToObject(
|
||||||
|
formattedTotal,
|
||||||
|
formatByNamespace(RESPONSE.months[i].new_clients.namespaces),
|
||||||
|
`${i + 6}/23`,
|
||||||
|
original.timestamp
|
||||||
|
);
|
||||||
|
const { root } = testObject;
|
||||||
|
const { root: expectedRoot } = expectedObject;
|
||||||
|
|
||||||
// month at 0-index has no data so use second month in array, empty month format covered by formatByMonths test above
|
assert.propEqual(
|
||||||
const original = { ...RESPONSE.months[1] };
|
root?.new_clients,
|
||||||
const expectedObject = SERIALIZED_ACTIVITY_RESPONSE.by_month[1].namespaces_by_key;
|
expectedRoot?.new_clients,
|
||||||
const formattedTotal = formatByNamespace(RESPONSE.months[1].namespaces);
|
`it formats namespaces new_clients for ${original.timestamp}`
|
||||||
|
);
|
||||||
|
assert.propEqual(root.mounts_by_key, expectedRoot.mounts_by_key, 'it formats namespaces mounts_by_key');
|
||||||
|
assert.propContains(root, expectedRoot, 'namespace has correct keys');
|
||||||
|
|
||||||
const testObject = namespaceArrayToObject(
|
assert.propEqual(RESPONSE.months[i], original, 'it does not modify original month data');
|
||||||
formattedTotal,
|
}
|
||||||
formatByNamespace(RESPONSE.months[1].new_clients.namespaces),
|
|
||||||
'9/23',
|
|
||||||
'2023-09-01T00:00:00Z'
|
|
||||||
);
|
|
||||||
|
|
||||||
const { root } = testObject;
|
|
||||||
const { root: expectedRoot } = expectedObject;
|
|
||||||
assert.propEqual(root.new_clients, expectedRoot.new_clients, 'it formats namespaces new_clients');
|
|
||||||
assert.propEqual(root.mounts_by_key, expectedRoot.mounts_by_key, 'it formats namespaces mounts_by_key');
|
|
||||||
assert.propContains(root, expectedRoot, 'namespace has correct keys');
|
|
||||||
|
|
||||||
assert.propEqual(
|
|
||||||
namespaceArrayToObject(formattedTotal, formatByNamespace([]), '9/23', '2023-09-01T00:00:00Z'),
|
|
||||||
{},
|
|
||||||
'returns an empty object when there are no new clients '
|
|
||||||
);
|
|
||||||
assert.propEqual(RESPONSE.months[1], original, 'it does not modify original month data');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TESTS FOR COMBINED ACTIVITY DATA - no mount attribution < 1.10
|
// TESTS FOR COMBINED ACTIVITY DATA - no mount attribution < 1.10
|
||||||
|
|||||||
Reference in New Issue
Block a user