Skip to content

Commit

Permalink
backport of UI: Fix client counts bug when no new clients (#27385)
Browse files Browse the repository at this point in the history
Co-authored-by: Chelsea Shaw <[email protected]>
  • Loading branch information
1 parent b0ea89b commit f05ee83
Show file tree
Hide file tree
Showing 5 changed files with 639 additions and 85 deletions.
3 changes: 3 additions & 0 deletions changelog/27352.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: fix issue where a month without new clients breaks the client count dashboard
```
122 changes: 81 additions & 41 deletions ui/lib/core/addon/utils/client-count-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ export const formatDateObject = (dateObj: { monthIdx: number; year: number }, is
return getUnixTime(utc);
};

export const formatByMonths = (monthsArray: ActivityMonthBlock[] | EmptyActivityMonthBlock[]) => {
export const formatByMonths = (
monthsArray: (ActivityMonthBlock | EmptyActivityMonthBlock | NoNewClientsActivityMonthBlock)[]
) => {
const sortedPayload = sortMonthsByTimestamp(monthsArray);
return sortedPayload?.map((m) => {
const month = parseAPITimestamp(m.timestamp, 'M/yy') as string;
Expand All @@ -95,23 +97,28 @@ export const formatByMonths = (monthsArray: ActivityMonthBlock[] | EmptyActivity
if (m.counts) {
const totalClientsByNamespace = formatByNamespace(m.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 {
month,
timestamp,
...destructureClientCounts(m.counts),
namespaces: formatByNamespace(m.namespaces) || [],
namespaces: formatByNamespace(m.namespaces),
namespaces_by_key: namespaceArrayToObject(
totalClientsByNamespace,
newClientsByNamespace,
month,
m.timestamp
),
new_clients: {
month,
timestamp,
...destructureClientCounts(m?.new_clients?.counts),
namespaces: formatByNamespace(m.new_clients?.namespaces) || [],
},
new_clients: newClients,
};
}
// empty month
Expand All @@ -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) => {
// 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;
Expand Down Expand Up @@ -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];
return sortedPayload.sort((a, b) =>
compareAsc(parseAPITimestamp(a.timestamp) as Date, parseAPITimestamp(b.timestamp) as Date)
Expand All @@ -168,44 +178,53 @@ export const sortMonthsByTimestamp = (monthsArray: ActivityMonthBlock[] | EmptyA
export const namespaceArrayToObject = (
monthTotals: ByNamespaceClients[],
// 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,
timestamp: string
) => {
// namespaces_by_key is used to filter monthly activity data by namespace
// 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
const namespaces_by_key = monthTotals.reduce((nsObject: { [key: string]: NamespaceByKey }, ns) => {
const keyedNs: NamespaceByKey = {
...destructureClientCounts(ns),
timestamp,
month,
mounts_by_key: {},
new_clients: {
month,
timestamp,
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) {
// 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
const mounts_by_key = ns.mounts.reduce(
(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,
new_clients: { month, timestamp, ...newNsClients },
mounts_by_key,
};
keyedNs.new_clients = { month, timestamp, ...newNsClients };
}
nsObject[ns.label] = keyedNs;
return nsObject;
}, {});

Expand Down Expand Up @@ -239,6 +258,15 @@ export interface TotalClients {
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 {
label: string;
mounts: MountClients[];
Expand All @@ -255,7 +283,9 @@ export interface ByMonthClients extends TotalClients {
namespaces_by_key: { [key: string]: NamespaceByKey };
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;
timestamp: string;
namespaces: ByNamespaceClients[];
Expand All @@ -268,7 +298,7 @@ export interface NamespaceByKey extends TotalClients {
new_clients: NamespaceNewClients;
}

export interface NamespaceNewClients extends TotalClients {
export interface NamespaceNewClients extends TotalClientsSometimes {
month: string;
timestamp: string;
label: string;
Expand All @@ -282,7 +312,7 @@ export interface MountByKey extends TotalClients {
new_clients: MountNewClients;
}

export interface MountNewClients extends TotalClients {
export interface MountNewClients extends TotalClientsSometimes {
month: string;
timestamp: string;
label: string;
Expand All @@ -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 {
timestamp: string; // YYYY-MM-01T00:00:00Z (always the first day of the month)
counts: null;
Expand Down
Loading

0 comments on commit f05ee83

Please sign in to comment.