From 7257da888c77dff6cff449f69ed2ef1eb4d69a9b Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:01:16 -0500 Subject: [PATCH] UI - Update Client Count filtering (#28036) --- changelog/28036.txt | 3 + ui/app/adapters/application.js | 2 +- ui/app/adapters/clients/activity.js | 18 +- ui/app/components/clients/activity.ts | 121 ++--- ui/app/components/clients/attribution.hbs | 106 +---- ui/app/components/clients/attribution.js | 143 ++---- ui/app/components/clients/chart-container.hbs | 6 +- ui/app/components/clients/charts/line.hbs | 129 ------ ui/app/components/clients/charts/line.ts | 146 ------ .../clients/charts/vertical-bar-grouped.hbs | 133 ++++++ .../clients/charts/vertical-bar-grouped.ts | 179 ++++++++ ui/app/components/clients/date-range.hbs | 5 +- .../{export-button.hbs => page-header.hbs} | 56 ++- .../{export-button.js => page-header.js} | 45 +- ui/app/components/clients/page/acme.hbs | 14 - ui/app/components/clients/page/counts.hbs | 40 +- ui/app/components/clients/page/counts.ts | 92 ++-- ui/app/components/clients/page/overview.hbs | 19 +- ui/app/components/clients/page/overview.ts | 15 + ui/app/components/clients/page/sync.hbs | 14 - ui/app/components/clients/page/token.hbs | 9 - ui/app/components/clients/page/token.ts | 4 - ui/app/components/clients/running-total.hbs | 13 +- ui/app/components/clients/running-total.ts | 23 + ui/app/routes/vault/cluster/clients/counts.ts | 29 +- ui/app/styles/components/chart-container.scss | 7 - ui/app/styles/core/charts-lineal.scss | 10 + ui/app/styles/core/charts.scss | 14 +- ui/lib/core/addon/utils/client-count-utils.ts | 226 +++++----- ui/mirage/handlers/clients.js | 93 +++- ui/tests/acceptance/clients/counts-test.js | 9 +- .../acceptance/clients/counts/acme-test.js | 112 ++--- .../clients/counts/overview-test.js | 112 +++-- ui/tests/acceptance/dashboard-test.js | 13 +- .../helpers/clients/client-count-helpers.js | 426 +----------------- .../helpers/clients/client-count-selectors.ts | 7 +- .../charts/vertical-bar-basic-test.js | 5 +- .../components/clients/attribution-test.js | 279 +++++------- .../charts/vertical-bar-grouped-test.js | 249 ++++++++++ .../components/clients/line-chart-test.js | 397 ---------------- ...ort-button-test.js => page-header-test.js} | 153 ++++--- .../components/clients/page/acme-test.js | 15 +- .../components/clients/page/counts-test.js | 4 +- .../components/clients/page/overview-test.js | 110 +++++ .../components/clients/page/sync-test.js | 29 +- .../components/clients/page/token-test.js | 13 +- .../components/clients/running-total-test.js | 19 +- .../utils/client-count-utils-test.js | 264 ++++++----- ui/tests/unit/adapters/application-test.js | 33 ++ .../unit/adapters/clients-activity-test.js | 20 + 50 files changed, 1832 insertions(+), 2151 deletions(-) create mode 100644 changelog/28036.txt delete mode 100644 ui/app/components/clients/charts/line.hbs delete mode 100644 ui/app/components/clients/charts/line.ts create mode 100644 ui/app/components/clients/charts/vertical-bar-grouped.hbs create mode 100644 ui/app/components/clients/charts/vertical-bar-grouped.ts rename ui/app/components/clients/{export-button.hbs => page-header.hbs} (57%) rename ui/app/components/clients/{export-button.js => page-header.js} (62%) create mode 100644 ui/tests/integration/components/clients/charts/vertical-bar-grouped-test.js delete mode 100644 ui/tests/integration/components/clients/line-chart-test.js rename ui/tests/integration/components/clients/{export-button-test.js => page-header-test.js} (69%) create mode 100644 ui/tests/integration/components/clients/page/overview-test.js create mode 100644 ui/tests/unit/adapters/application-test.js diff --git a/changelog/28036.txt b/changelog/28036.txt new file mode 100644 index 0000000000..f47891e46c --- /dev/null +++ b/changelog/28036.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Update the client count dashboard to use API namespace filtering and other UX improvements +``` \ No newline at end of file diff --git a/ui/app/adapters/application.js b/ui/app/adapters/application.js index 917262d0da..24416aa8e2 100644 --- a/ui/app/adapters/application.js +++ b/ui/app/adapters/application.js @@ -92,7 +92,7 @@ export default RESTAdapter.extend({ controlGroup.deleteControlGroupToken(controlGroupToken.accessor); } const [resp] = args; - if (resp && resp.warnings) { + if (resp && resp.warnings && !options.skipWarnings) { const flash = this.flashMessages; resp.warnings.forEach((message) => { flash.info(message); diff --git a/ui/app/adapters/clients/activity.js b/ui/app/adapters/clients/activity.js index 7a85c9505d..317444c137 100644 --- a/ui/app/adapters/clients/activity.js +++ b/ui/app/adapters/clients/activity.js @@ -39,8 +39,15 @@ export default class ActivityAdapter extends ApplicationAdapter { queryRecord(store, type, query) { const url = `${this.buildURL()}/internal/counters/activity`; - const queryParams = this.formatQueryParams(query); - return this.ajax(url, 'GET', { data: queryParams }).then((resp) => { + const options = { + data: this.formatQueryParams(query), + }; + + if (query?.namespace) { + options.namespace = query.namespace; + } + + return this.ajax(url, 'GET', options).then((resp) => { const response = resp || {}; response.id = response.request_id || 'no-data'; return response; @@ -71,11 +78,12 @@ export default class ActivityAdapter extends ApplicationAdapter { } } - urlForFindRecord(id) { - // debug reminder so model is stored in Ember data with the same id for consistency + // Only dashboard uses findRecord, the client count dashboard uses queryRecord + findRecord(store, type, id) { if (id !== 'clients/activity') { debug(`findRecord('clients/activity') should pass 'clients/activity' as the id, you passed: '${id}'`); } - return `${this.buildURL()}/internal/counters/activity`; + const url = `${this.buildURL()}/internal/counters/activity`; + return this.ajax(url, 'GET', { skipWarnings: true }); } } diff --git a/ui/app/components/clients/activity.ts b/ui/app/components/clients/activity.ts index 10a419b2e9..b64ff76f6c 100644 --- a/ui/app/components/clients/activity.ts +++ b/ui/app/components/clients/activity.ts @@ -10,7 +10,13 @@ import Component from '@glimmer/component'; import { isSameMonth } from 'date-fns'; import { parseAPITimestamp } from 'core/utils/date-formatters'; import { calculateAverage } from 'vault/utils/chart-helpers'; -import { filterVersionHistory, hasMountsKey, hasNamespacesKey } from 'core/utils/client-count-utils'; +import { + filterByMonthDataForMount, + filteredTotalForMount, + filterVersionHistory, +} from 'core/utils/client-count-utils'; +import { service } from '@ember/service'; +import { sanitizePath } from 'core/utils/sanitize-path'; import type ClientsActivityModel from 'vault/models/clients/activity'; import type ClientsVersionHistoryModel from 'vault/models/clients/version-history'; @@ -19,7 +25,9 @@ import type { MountNewClients, NamespaceByKey, NamespaceNewClients, + TotalClients, } from 'core/utils/client-count-utils'; +import type NamespaceService from 'vault/services/namespace'; interface Args { activity: ClientsActivityModel; @@ -31,6 +39,8 @@ interface Args { } export default class ClientsActivityComponent extends Component { + @service declare readonly namespace: NamespaceService; + average = ( data: | (ByMonthNewClients | NamespaceNewClients | MountNewClients | undefined)[] @@ -40,48 +50,27 @@ export default class ClientsActivityComponent extends Component { return calculateAverage(data, key); }; + // path of the filtered namespace OR current one, for filtering relevant data + get namespacePathForFilter() { + const { namespace } = this.args; + const currentNs = this.namespace.currentNamespace; + return sanitizePath(namespace || currentNs || 'root'); + } + get byMonthActivityData() { - const { activity, namespace } = this.args; - return namespace ? this.filteredActivityByMonth : activity.byMonth; + const { activity, mountPath } = this.args; + const nsPath = this.namespacePathForFilter; + if (mountPath) { + // only do client-side filtering if we have a mountPath filter set + return filterByMonthDataForMount(activity.byMonth, nsPath, mountPath); + } + return activity.byMonth; } get byMonthNewClients() { return this.byMonthActivityData ? this.byMonthActivityData?.map((m) => m?.new_clients) : []; } - get filteredActivityByMonth() { - const { namespace, mountPath, activity } = this.args; - if (!namespace && !mountPath) { - return activity.byMonth; - } - const namespaceData = activity.byMonth - ?.map((m) => m.namespaces_by_key[namespace]) - .filter((d) => d !== undefined); - - if (!mountPath) { - return namespaceData || []; - } - - const mountData = namespaceData - ?.map((namespace) => namespace?.mounts_by_key[mountPath]) - .filter((d) => d !== undefined); - - return mountData || []; - } - - get filteredActivityByNamespace() { - const { namespace, activity } = this.args; - return activity.byNamespace.find((ns) => ns.label === namespace); - } - - get filteredActivityByAuthMount() { - return this.filteredActivityByNamespace?.mounts?.find((mount) => mount.label === this.args.mountPath); - } - - get filteredActivity() { - return this.args.mountPath ? this.filteredActivityByAuthMount : this.filteredActivityByNamespace; - } - get isCurrentMonth() { const { activity } = this.args; const current = parseAPITimestamp(activity.responseTimestamp) as Date; @@ -99,62 +88,18 @@ export default class ClientsActivityComponent extends Component { } // (object) top level TOTAL client counts for given date range - get totalUsageCounts() { - const { namespace, activity } = this.args; - return namespace ? this.filteredActivity : activity.total; + get totalUsageCounts(): TotalClients { + const { namespace, activity, mountPath } = this.args; + // only do this if we have a mountPath filter. + // namespace is filtered on API layer + if (activity?.byNamespace && namespace && mountPath) { + return filteredTotalForMount(activity.byNamespace, namespace, mountPath); + } + return activity?.total; } get upgradesDuringActivity() { const { versionHistory, activity } = this.args; return filterVersionHistory(versionHistory, activity.startTime, activity.endTime); } - - // (object) single month new client data with total counts and array of - // either namespaces or mounts - get newClientCounts() { - if (this.isDateRange || this.byMonthActivityData.length === 0) { - return null; - } - - return this.byMonthActivityData[0]?.new_clients; - } - - // total client data for horizontal bar chart in attribution component - get totalClientAttribution() { - const { namespace, activity } = this.args; - if (namespace) { - return this.filteredActivityByNamespace?.mounts || null; - } else { - return activity.byNamespace || null; - } - } - - // new client data for horizontal bar chart - get newClientAttribution() { - // new client attribution only available in a single, historical month (not a date range or current month) - if (this.isDateRange || this.isCurrentMonth || !this.newClientCounts) return null; - - const newCounts = this.newClientCounts; - if (this.args.namespace && hasMountsKey(newCounts)) return newCounts?.mounts; - - if (hasNamespacesKey(newCounts)) return newCounts?.namespaces; - - return null; - } - - get hasAttributionData() { - const { mountPath, namespace } = this.args; - if (!mountPath) { - if (namespace) { - const mounts = this.filteredActivityByNamespace?.mounts?.map((mount) => ({ - id: mount.label, - name: mount.label, - })); - return mounts && mounts.length > 0; - } - return !!this.totalClientAttribution && this.totalUsageCounts && this.totalUsageCounts.clients !== 0; - } - - return false; - } } diff --git a/ui/app/components/clients/attribution.hbs b/ui/app/components/clients/attribution.hbs index 311ba5dd9e..385f033461 100644 --- a/ui/app/components/clients/attribution.hbs +++ b/ui/app/components/clients/attribution.hbs @@ -3,97 +3,29 @@ SPDX-License-Identifier: BUSL-1.1 ~}} -{{! only show side-by-side horizontal bar charts if data is from a single, historical month }} -
-