From cdbb0c49ccc9817d0fd274f31a63c0d34bfea8d2 Mon Sep 17 00:00:00 2001 From: lane-wetmore Date: Mon, 19 May 2025 15:57:32 -0500 Subject: [PATCH] UI: Vault update client count charts to show new clients only (#30506) * increase bar width, show new clients only, add timestamp to header, update bar color * remove extra timestamps, switch to basic bar chart * update docs and styling * remove unneeded timestamp args * show new client running totatls * initial test updates * update test * clean up new client total calc into util fn * bits of clean up and todos * update tests * update to avoid activity call when in CE and missing either start or end time * update todos * update tests * tidying * move new client total onto payload for easier access * update more tests to align with copy changes and new client totals * remove addressed TODOs * Update comment * add changelog entry * revert to using total, update tests and clean up * Update ui/app/components/clients/page/counts.hbs Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * remove duplicate charts and update descriptions * update tests after removing extra charts * tidy * update instances of byMonthActivityData to use byMonthNewClients and update tests * Update ui/app/components/clients/running-total.ts Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> * update chart styles --------- Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> --- changelog/30506.txt | 3 + ui/app/components/clients/activity.ts | 15 ++--- ui/app/components/clients/attribution.hbs | 6 -- ui/app/components/clients/attribution.js | 2 - ui/app/components/clients/chart-container.hbs | 7 --- .../clients/charts/vertical-bar-stacked.ts | 2 +- ui/app/components/clients/page-header.hbs | 7 +++ ui/app/components/clients/page-header.js | 7 ++- ui/app/components/clients/page/acme.hbs | 47 ++------------- ui/app/components/clients/page/acme.ts | 4 +- ui/app/components/clients/page/counts.hbs | 12 ++-- ui/app/components/clients/page/counts.ts | 4 +- ui/app/components/clients/page/overview.hbs | 5 +- ui/app/components/clients/page/sync.hbs | 40 +------------ ui/app/components/clients/page/sync.ts | 2 +- ui/app/components/clients/page/token.hbs | 52 +--------------- ui/app/components/clients/running-total.hbs | 48 ++++----------- ui/app/components/clients/running-total.ts | 15 ++--- ui/app/routes/vault/cluster/clients/counts.ts | 5 +- ui/app/styles/components/chart-container.scss | 10 +--- ui/app/styles/core/charts-lineal.scss | 6 +- ui/app/styles/core/charts.scss | 2 +- ui/app/utils/chart-helpers.js | 2 +- ui/lib/core/addon/utils/client-count-utils.ts | 2 +- ui/tests/acceptance/clients/counts-test.js | 8 +-- .../acceptance/clients/counts/acme-test.js | 11 +--- .../components/clients/attribution-test.js | 7 --- .../components/clients/page/acme-test.js | 30 +++------- .../components/clients/page/counts-test.js | 6 +- .../components/clients/page/overview-test.js | 8 +++ .../components/clients/page/sync-test.js | 23 ++----- .../components/clients/page/token-test.js | 60 +------------------ .../components/clients/running-total-test.js | 35 +++-------- .../utils/client-count-utils-test.js | 1 + 34 files changed, 110 insertions(+), 384 deletions(-) create mode 100644 changelog/30506.txt diff --git a/changelog/30506.txt b/changelog/30506.txt new file mode 100644 index 0000000000..e724b5aad0 --- /dev/null +++ b/changelog/30506.txt @@ -0,0 +1,3 @@ +```release-note:change +ui: Client counting "running total" charts now reflect new clients only +``` diff --git a/ui/app/components/clients/activity.ts b/ui/app/components/clients/activity.ts index b64ff76f6c..47f12053b4 100644 --- a/ui/app/components/clients/activity.ts +++ b/ui/app/components/clients/activity.ts @@ -57,18 +57,15 @@ export default class ClientsActivityComponent extends Component { return sanitizePath(namespace || currentNs || 'root'); } - get byMonthActivityData() { + get byMonthNewClients() { 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) : []; + const data = mountPath + ? filterByMonthDataForMount(activity.byMonth, nsPath, mountPath) + : activity.byMonth; + + return data ? data?.map((m) => m?.new_clients) : []; } get isCurrentMonth() { diff --git a/ui/app/components/clients/attribution.hbs b/ui/app/components/clients/attribution.hbs index db88b4fddc..10abd62946 100644 --- a/ui/app/components/clients/attribution.hbs +++ b/ui/app/components/clients/attribution.hbs @@ -36,10 +36,4 @@ {{/if}} -
- {{#if @responseTimestamp}} - Updated - {{date-format @responseTimestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}} - {{/if}} -
\ No newline at end of file diff --git a/ui/app/components/clients/attribution.js b/ui/app/components/clients/attribution.js index 06d83d633b..d12692ca74 100644 --- a/ui/app/components/clients/attribution.js +++ b/ui/app/components/clients/attribution.js @@ -14,13 +14,11 @@ import Component from '@glimmer/component'; * * * @param {string} noun - noun which reflects the type of data and used in title. Should be "namespace" (default) or "mount" * @param {array} attribution - array of objects containing a label and breakdown of client counts for total clients - * @param {string} responseTimestamp - ISO timestamp created in serializer to timestamp the response, renders in bottom left corner below attribution chart * @param {boolean} isSecretsSyncActivated - boolean reflecting if secrets sync is activated. Determines the labels and data shown */ diff --git a/ui/app/components/clients/chart-container.hbs b/ui/app/components/clients/chart-container.hbs index b6dee8e622..e05f688e75 100644 --- a/ui/app/components/clients/chart-container.hbs +++ b/ui/app/components/clients/chart-container.hbs @@ -48,11 +48,4 @@ {{yield to="emptyState"}} {{/if}} - - {{#if @timestamp}} -
- Updated - {{date-format @timestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}} -
- {{/if}} \ No newline at end of file diff --git a/ui/app/components/clients/charts/vertical-bar-stacked.ts b/ui/app/components/clients/charts/vertical-bar-stacked.ts index 2944e32516..b513d7cb42 100644 --- a/ui/app/components/clients/charts/vertical-bar-stacked.ts +++ b/ui/app/components/clients/charts/vertical-bar-stacked.ts @@ -47,7 +47,7 @@ type ChartDatum = DatumBase & { * @example * diff --git a/ui/app/components/clients/page-header.hbs b/ui/app/components/clients/page-header.hbs index ba997117c9..52e3c5e735 100644 --- a/ui/app/components/clients/page-header.hbs +++ b/ui/app/components/clients/page-header.hbs @@ -7,6 +7,13 @@ Client Usage + + {{#if @activityTimestamp}} + + Last Updated: + {{date-format @activityTimestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}} + + {{/if}} {{#if this.version.isEnterprise}}

diff --git a/ui/app/components/clients/page-header.js b/ui/app/components/clients/page-header.js index ef514027ea..12efd7399a 100644 --- a/ui/app/components/clients/page-header.js +++ b/ui/app/components/clients/page-header.js @@ -21,14 +21,15 @@ import { task } from 'ember-concurrency'; * ```js * * ``` - * @param {string} [billingStartTime] - ISO string timestamp of billing start date, to be passed to datepicker + * @param {string} [billingStartTime] - ISO timestamp of billing start date, to be passed to date picker + * @param {string} [activityTimestamp] - ISO timestamp created in serializer to timestamp the response to be displayed in page header * @param {string} [startTimestamp] - ISO timestamp of start time, to be passed to export request * @param {string} [endTimestamp] - ISO timestamp of end time, to be passed to export request - * @param {number} [retentionMonths=48] - number of months for historical billing, to be passed to datepicker + * @param {number} [retentionMonths = 48] - number of months for historical billing, to be passed to date picker * @param {string} [namespace] - namespace filter. Will be appended to the current namespace in the export request. * @param {string} [upgradesDuringActivity] - array of objects containing version history upgrade data * @param {boolean} [noData = false] - when true, export button will hide regardless of capabilities - * @param {function} onChange - callback when a new range is saved, to be passed to datepicker + * @param {function} [onChange] - callback when a new date range is saved, to be passed to date picker */ export default class ClientsPageHeaderComponent extends Component { @service download; diff --git a/ui/app/components/clients/page/acme.hbs b/ui/app/components/clients/page/acme.hbs index ae88b812a8..6797b169d5 100644 --- a/ui/app/components/clients/page/acme.hbs +++ b/ui/app/components/clients/page/acme.hbs @@ -5,8 +5,8 @@ {{! ACME clients added in 1.17 }} -{{#if (not this.byMonthActivityData)}} - {{! byMonthActivityData is an empty array if there is no monthly data (monthly breakdown was added in 1.11) +{{#if (not this.byMonthNewClients)}} + {{! byMonthNewClients is an empty array if there is no monthly data (monthly breakdown was added in 1.11) this means the user has queried dates before ACME clients existed. we render an empty state instead of "0 acme clients" (which is what the activity response returns) to be more explicit }} {{else if this.isDateRange}} - + <:subTitle> - - {{#if this.totalUsageCounts.acme_clients}} - {{! no need to render two empty charts! hide this one if there are no acme clients }} - - <:stats> - {{#let (this.average this.byMonthNewClients "acme_clients") as |avg|}} - {{#if avg}} - - {{/if}} - {{/let}} - - - <:chart> - - - - {{/if}} {{else}} diff --git a/ui/app/components/clients/page/acme.ts b/ui/app/components/clients/page/acme.ts index 9b4cb8fd0a..767be64be0 100644 --- a/ui/app/components/clients/page/acme.ts +++ b/ui/app/components/clients/page/acme.ts @@ -8,8 +8,6 @@ import ActivityComponent from '../activity'; export default class ClientsAcmePageComponent extends ActivityComponent { title = 'ACME usage'; get description() { - return `This data can be used to understand how many ACME clients have been used for the queried ${ - this.isDateRange ? 'date range' : 'month' - }. Each ACME request is counted as one client.`; + return 'ACME clients which interacted with Vault for the first time each month. Each bar represents the total new ACME clients for that month.'; } } diff --git a/ui/app/components/clients/page/counts.hbs b/ui/app/components/clients/page/counts.hbs index d3fb86fcbd..11c22673fb 100644 --- a/ui/app/components/clients/page/counts.hbs +++ b/ui/app/components/clients/page/counts.hbs @@ -6,6 +6,7 @@ {{else}} diff --git a/ui/app/components/clients/page/counts.ts b/ui/app/components/clients/page/counts.ts index 77b678d1bd..aeb83058a8 100644 --- a/ui/app/components/clients/page/counts.ts +++ b/ui/app/components/clients/page/counts.ts @@ -68,7 +68,7 @@ export default class ClientsCountsPageComponent extends Component { // passed into page-header for the export modal alert get upgradesDuringActivity() { const { versionHistory, activity } = this.args; - return filterVersionHistory(versionHistory, activity.startTime, activity.endTime); + return filterVersionHistory(versionHistory, activity?.startTime, activity?.endTime); } get upgradeExplanations() { @@ -128,7 +128,7 @@ export default class ClientsCountsPageComponent extends Component { } // duplicate of the method found in the activity component, so that we render the child only when there is activity to view - get totalUsageCounts(): TotalClients { + get totalUsageCounts(): TotalClients | undefined { const { namespace, mountPath, activity } = this.args; if (mountPath) { // only do this if we have a mountPath filter. diff --git a/ui/app/components/clients/page/overview.hbs b/ui/app/components/clients/page/overview.hbs index d3bb7c134d..816a02d273 100644 --- a/ui/app/components/clients/page/overview.hbs +++ b/ui/app/components/clients/page/overview.hbs @@ -5,12 +5,11 @@ @@ -18,14 +17,12 @@ {{#if this.namespaceMountAttribution}} {{/if}} diff --git a/ui/app/components/clients/page/sync.hbs b/ui/app/components/clients/page/sync.hbs index c9e1048138..0e20249579 100644 --- a/ui/app/components/clients/page/sync.hbs +++ b/ui/app/components/clients/page/sync.hbs @@ -3,8 +3,8 @@ SPDX-License-Identifier: BUSL-1.1 }} {{#if this.flags.secretsSyncIsActivated}} - {{#if (not this.byMonthActivityData)}} - {{! byMonthActivityData is an empty array if there is no monthly data (monthly breakdown was added in 1.11) + {{#if (not this.byMonthNewClients)}} + {{! byMonthNewClients is an empty array if there is no monthly data (monthly breakdown was added in 1.11) this means the user has queried dates before sync clients existed. we render an empty state instead of "0 sync clients" (which is what the activity response returns) to be more explicit }} @@ -33,45 +32,12 @@ <:chart> - - {{! no need to render two empty charts! hide this one if there is no sync data }} - {{#if this.totalUsageCounts.secret_syncs}} - - <:stats> - {{#let (this.average this.byMonthNewClients "secret_syncs") as |avg|}} - {{#if avg}} - - {{/if}} - {{/let}} - - - <:chart> - - - - {{/if}} {{else}} diff --git a/ui/app/components/clients/page/sync.ts b/ui/app/components/clients/page/sync.ts index 65aca2d4aa..58364b0762 100644 --- a/ui/app/components/clients/page/sync.ts +++ b/ui/app/components/clients/page/sync.ts @@ -11,5 +11,5 @@ export default class SyncComponent extends ActivityComponent { title = 'Secrets sync usage'; description = - 'This data can be used to understand how many secrets sync clients have been used for this date range. Each Vault secret that is synced to at least one destination counts as one Vault client.'; + 'Secrets sync clients which interacted with Vault for the first time each month. Each bar represents the total new sync clients for that month.'; } diff --git a/ui/app/components/clients/page/token.hbs b/ui/app/components/clients/page/token.hbs index dc145e4e4f..5d1c06b62c 100644 --- a/ui/app/components/clients/page/token.hbs +++ b/ui/app/components/clients/page/token.hbs @@ -3,11 +3,10 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{#if (and this.byMonthActivityData this.isDateRange)}} +{{#if (and this.byMonthNewClients this.isDateRange)}} @@ -24,58 +23,11 @@ <:chart> - - - - - <:stats> - - - - - - <:chart> - - - <:emptyState> - - {{else}} {{! Renders when viewing a single month or activity log data that predates the monthly breakdown added in 1.11 }} diff --git a/ui/app/components/clients/running-total.hbs b/ui/app/components/clients/running-total.hbs index 94750209d2..018bcb7e42 100644 --- a/ui/app/components/clients/running-total.hbs +++ b/ui/app/components/clients/running-total.hbs @@ -3,20 +3,15 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{#if (gt @byMonthActivityData.length 1)}} - +{{#if (gt @byMonthNewClients.length 1)}} + <:subTitle> @@ -36,12 +31,11 @@ <:chart> - @@ -50,32 +44,16 @@ {{capitalize l.label}} {{/each}} + {{else}} - {{#let (get @byMonthActivityData "0") as |singleMonthData|}} - {{#if (and @isHistoricalMonth singleMonthData.new_clients.clients)}} + {{#let (get @byMonthNewClients "0") as |singleMonthData|}} + {{#if (and @isHistoricalMonth singleMonthData.clients)}}

-
- - - - {{#if @isSecretsSyncActivated}} - - {{/if}} -
-
-
- {{#if @isSecretsSyncActivated}} - + {{/if}}
diff --git a/ui/app/components/clients/running-total.ts b/ui/app/components/clients/running-total.ts index dd09da32e6..b4e49948dc 100644 --- a/ui/app/components/clients/running-total.ts +++ b/ui/app/components/clients/running-total.ts @@ -4,12 +4,12 @@ */ import Component from '@glimmer/component'; -import type { ByMonthClients, TotalClients } from 'core/utils/client-count-utils'; -import ClientsVersionHistoryModel from 'vault/vault/models/clients/version-history'; +import type { ByMonthNewClients, TotalClients } from 'core/utils/client-count-utils'; +import type ClientsVersionHistoryModel from 'vault/vault/models/clients/version-history'; interface Args { isSecretsSyncActivated: boolean; - byMonthActivityData: ByMonthClients[]; + byMonthNewClients: ByMonthNewClients[]; isHistoricalMonth: boolean; isCurrentMonth: boolean; runningTotals: TotalClients; @@ -27,16 +27,13 @@ export default class RunningTotal extends Component { } get runningTotalData() { - return this.args.byMonthActivityData.map((monthly) => ({ + return this.args.byMonthNewClients.map((monthly) => ({ ...monthly, - new_clients: monthly.new_clients?.clients, + new_clients: monthly.clients, })); } get chartLegend() { - return [ - { key: 'clients', label: 'total clients' }, - { key: 'new_clients', label: 'new clients' }, - ]; + return [{ key: 'new_clients', label: 'new clients' }]; } } diff --git a/ui/app/routes/vault/cluster/clients/counts.ts b/ui/app/routes/vault/cluster/clients/counts.ts index 29a9228d20..2c87de494f 100644 --- a/ui/app/routes/vault/cluster/clients/counts.ts +++ b/ui/app/routes/vault/cluster/clients/counts.ts @@ -73,11 +73,10 @@ export default class ClientsCountsRoute extends Route { activityError?: AdapterError; }> { let activity, activityError; - // if CE without start time we want to skip the activity call + // if CE without both start time and end time, we want to skip the activity call // so that the user is forced to choose a date range - if (this.version.isEnterprise || params.start_time) { + if (this.version.isEnterprise || (this.version.isCommunity && params.start_time && params.end_time)) { const query: ActivityAdapterQuery = { - // start and end params are optional -- if not provided, will fallback to API default start_time: this.formatTimeQuery(params?.start_time), end_time: this.formatTimeQuery(params?.end_time), }; diff --git a/ui/app/styles/components/chart-container.scss b/ui/app/styles/components/chart-container.scss index 1ae07e1881..83635bfd03 100644 --- a/ui/app/styles/components/chart-container.scss +++ b/ui/app/styles/components/chart-container.scss @@ -20,7 +20,7 @@ .single-chart-grid { display: grid; grid-template-columns: 1fr 0.3fr 3.7fr; - grid-template-rows: 0.5fr 1fr 1fr 1fr 0.25fr; + grid-template-rows: 0.5fr 1fr 1fr 0.5fr 0.25fr; width: 100%; &.no-legend { grid-template-rows: 0.5fr 1fr 1fr 0.25fr; @@ -140,14 +140,6 @@ grid-row-start: 4; } -.timestamp { - grid-column: 1 / span 2; - grid-row-start: -1; - color: color_variables.$ui-gray-500; - font-size: size_variables.$size-9; - align-self: end; -} - .legend { grid-row-start: 5; grid-column-start: 2; diff --git a/ui/app/styles/core/charts-lineal.scss b/ui/app/styles/core/charts-lineal.scss index a5c4d2f2a7..d086e05a53 100644 --- a/ui/app/styles/core/charts-lineal.scss +++ b/ui/app/styles/core/charts-lineal.scss @@ -16,7 +16,7 @@ } } .lineal-chart-bar { - fill: var(--token-color-palette-blue-300); + fill: var(--token-color-palette-blue-500); } .lineal-axis { color: color_variables.$ui-gray-500; @@ -56,6 +56,6 @@ fill: var(--token-color-palette-blue-200); } .custom-bar-new_clients { - color: var(--token-color-palette-blue-100); - fill: var(--token-color-palette-blue-100); + fill: var(--token-color-palette-blue-500); + color: var(--token-color-palette-blue-500); } diff --git a/ui/app/styles/core/charts.scss b/ui/app/styles/core/charts.scss index 894f73d2ee..2128baf5c8 100644 --- a/ui/app/styles/core/charts.scss +++ b/ui/app/styles/core/charts.scss @@ -32,7 +32,7 @@ background-color: var(--token-color-palette-blue-200); } &.dot-new_clients { - background-color: var(--token-color-palette-blue-100); + background-color: var(--token-color-palette-blue-500); } } diff --git a/ui/app/utils/chart-helpers.js b/ui/app/utils/chart-helpers.js index 749d64592c..39bbebd565 100644 --- a/ui/app/utils/chart-helpers.js +++ b/ui/app/utils/chart-helpers.js @@ -15,7 +15,7 @@ export const GREY = '#EBEEF2'; export const TRANSLATE = { left: -11 }; export const SVG_DIMENSIONS = { height: 190, width: 500 }; -export const BAR_WIDTH = 7; // data bar width is 7 pixels +export const BAR_WIDTH = 17; // data bar width is 17 pixels // Reference for tickFormat https://www.youtube.com/watch?v=c3MCROTNN8g export function numericalAxisLabel(number) { diff --git a/ui/lib/core/addon/utils/client-count-utils.ts b/ui/lib/core/addon/utils/client-count-utils.ts index c4b85f27bb..502a296563 100644 --- a/ui/lib/core/addon/utils/client-count-utils.ts +++ b/ui/lib/core/addon/utils/client-count-utils.ts @@ -43,7 +43,7 @@ export const filterVersionHistory = ( start: string, end: string ) => { - if (versionHistory) { + if (versionHistory && start && end) { const upgrades = versionHistory.reduce((array: ClientsVersionHistoryModel[], upgradeData) => { const isRelevantHistory = (v: string) => { return ( diff --git a/ui/tests/acceptance/clients/counts-test.js b/ui/tests/acceptance/clients/counts-test.js index 3960cd7c0d..3fd21ef05a 100644 --- a/ui/tests/acceptance/clients/counts-test.js +++ b/ui/tests/acceptance/clients/counts-test.js @@ -30,12 +30,12 @@ module('Acceptance | clients | counts', function (hooks) { assert.expect(2); this.owner.lookup('service:version').type = 'community'; await visit('/vault/clients/counts/overview'); - assert.dom(GENERAL.emptyStateTitle).hasText('No start date found'); + assert + .dom(GENERAL.emptyStateTitle) + .hasText('Input the start and end dates to view client attribution by path.'); assert .dom(GENERAL.emptyStateMessage) - .hasText( - 'In order to get the most from this data, please enter a start month above. Vault will calculate new clients starting from that month.' - ); + .hasText('Only historical data may be queried. No data is available for the current month.'); }); test('it should redirect to counts overview route for transitions to parent', async function (assert) { diff --git a/ui/tests/acceptance/clients/counts/acme-test.js b/ui/tests/acceptance/clients/counts/acme-test.js index d4aaa7d378..8d7de1b9fc 100644 --- a/ui/tests/acceptance/clients/counts/acme-test.js +++ b/ui/tests/acceptance/clients/counts/acme-test.js @@ -66,8 +66,7 @@ module('Acceptance | clients | counts | acme', function (hooks) { test('it filters by mount data and renders charts', async function (assert) { const { nsTotals, nsMonthlyUsage } = this.expectedValues; - const nsMonthlyNew = nsMonthlyUsage.map((m) => m?.new_clients); - assert.expect(7 + nsMonthlyUsage.length + nsMonthlyNew.length); + assert.expect(4 + nsMonthlyUsage.length); await visit('/vault/clients/counts/acme'); await selectChoose(CLIENT_COUNT.nsFilter, this.nsPath); @@ -75,7 +74,6 @@ module('Acceptance | clients | counts | acme', function (hooks) { // each chart assertion count is data array length + 2 assertBarChart(assert, 'ACME usage', nsMonthlyUsage); - assertBarChart(assert, 'Monthly new', nsMonthlyNew); assert.strictEqual( currentURL(), `/vault/clients/counts/acme?mountPath=pki-engine-0&ns=${this.nsPath}`, @@ -85,13 +83,8 @@ module('Acceptance | clients | counts | acme', function (hooks) { .dom(CLIENT_COUNT.statText('Total ACME clients')) .hasTextContaining( `${formatNumber([nsTotals.acme_clients])}`, - 'renders total acme clients for namespace' + 'renders total new acme clients for namespace' ); - - // TODO: update this - assert - .dom(CLIENT_COUNT.statText('Average new ACME clients per month')) - .hasTextContaining(`13`, 'renders average acme clients for namespace'); }); /** diff --git a/ui/tests/integration/components/clients/attribution-test.js b/ui/tests/integration/components/clients/attribution-test.js index fdd3bd96ca..98eee690ba 100644 --- a/ui/tests/integration/components/clients/attribution-test.js +++ b/ui/tests/integration/components/clients/attribution-test.js @@ -39,7 +39,6 @@ module('Integration | Component | clients/attribution', function (hooks) { const mockNow = this.timestampStub(); this.mockNow = mockNow; this.startTimestamp = formatRFC3339(subMonths(mockNow, 6)); - this.timestamp = formatRFC3339(mockNow); this.selectedNamespace = null; this.namespaceAttribution = SERIALIZED_ACTIVITY_RESPONSE.by_namespace; this.authMountAttribution = SERIALIZED_ACTIVITY_RESPONSE.by_namespace.find( @@ -54,7 +53,6 @@ module('Integration | Component | clients/attribution', function (hooks) { assert.dom(GENERAL.emptyStateTitle).hasText('No data found'); assert.dom(CLIENTS_ATTRIBUTION.title).hasText('Namespace attribution', 'uses default noun'); - assert.dom(CLIENTS_ATTRIBUTION.timestamp).hasNoText(); }); test('it updates language based on noun', async function (assert) { @@ -63,10 +61,8 @@ module('Integration | Component | clients/attribution', function (hooks) { `); - assert.dom(CLIENTS_ATTRIBUTION.timestamp).includesText('Updated Apr 3'); // when noun is blank, uses default assert.dom(CLIENTS_ATTRIBUTION.title).hasText('Namespace attribution'); @@ -110,7 +106,6 @@ module('Integration | Component | clients/attribution', function (hooks) { await render(hbs` `); @@ -147,7 +142,6 @@ module('Integration | Component | clients/attribution', function (hooks) { await render(hbs` `); @@ -160,7 +154,6 @@ module('Integration | Component | clients/attribution', function (hooks) { await render(hbs` `); diff --git a/ui/tests/integration/components/clients/page/acme-test.js b/ui/tests/integration/components/clients/page/acme-test.js index 7df2e1a448..1b64f9cf53 100644 --- a/ui/tests/integration/components/clients/page/acme-test.js +++ b/ui/tests/integration/components/clients/page/acme-test.js @@ -14,8 +14,6 @@ import { getUnixTime } from 'date-fns'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { CLIENT_COUNT, CHARTS } from 'vault/tests/helpers/clients/client-count-selectors'; import { formatNumber } from 'core/helpers/format-number'; -import { calculateAverage } from 'vault/utils/chart-helpers'; -import { dateFormat } from 'core/helpers/date-format'; import { assertBarChart } from 'vault/tests/helpers/clients/client-count-helpers'; const START_TIME = getUnixTime(LICENSE_START); @@ -59,14 +57,10 @@ module('Integration | Component | clients | Clients::Page::Acme', function (hook test('it should render with full month activity data charts', async function (assert) { const monthCount = this.activity.byMonth.length; - assert.expect(7 + monthCount * 2); + assert.expect(3 + monthCount); + const expectedTotal = formatNumber([this.activity.total.acme_clients]); - const expectedNewAvg = formatNumber([ - calculateAverage( - this.activity.byMonth.map((m) => m?.new_clients), - 'acme_clients' - ), - ]); + await this.renderComponent(); assert .dom(statText('Total ACME clients')) @@ -74,31 +68,23 @@ module('Integration | Component | clients | Clients::Page::Acme', function (hook `Total ACME clients The total number of ACME requests made to Vault during this time period. ${expectedTotal}`, `renders correct total acme stat ${expectedTotal}` ); - assert.dom(statText('Average new ACME clients per month')).hasTextContaining(`${expectedNewAvg}`); - - const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { - withTimeZone: true, - }); - assert.dom(CHARTS.timestamp).hasText(`Updated ${formattedTimestamp}`, 'renders response timestamp'); assertBarChart(assert, 'ACME usage', this.activity.byMonth); - assertBarChart(assert, 'Monthly new', this.activity.byMonth); }); test('it should render stats without chart for a single month', async function (assert) { - assert.expect(4); + assert.expect(2); const activityQuery = { start_time: { timestamp: END_TIME }, end_time: { timestamp: END_TIME } }; this.activity = await this.store.queryRecord('clients/activity', activityQuery); - const expectedTotal = formatNumber([this.activity.total.acme_clients]); - await this.renderComponent(); + const expectedTotal = formatNumber([this.activity.total.acme_clients]); + + await this.renderComponent(); assert.dom(CHARTS.chart('ACME usage')).doesNotExist('total usage chart does not render'); - assert.dom(CHARTS.container('Monthly new')).doesNotExist('monthly new chart does not render'); - assert.dom(statText('Average new ACME clients per month')).doesNotExist(); assert .dom(usageStats('ACME usage')) .hasText( - `ACME usage Usage metrics tutorial This data can be used to understand how many ACME clients have been used for the queried month. Each ACME request is counted as one client. Total ACME clients ${expectedTotal}`, + `ACME usage Usage metrics tutorial ACME clients which interacted with Vault for the first time each month. Each bar represents the total new ACME clients for that month. Total ACME clients ${expectedTotal}`, 'it renders usage stats with single month copy' ); }); diff --git a/ui/tests/integration/components/clients/page/counts-test.js b/ui/tests/integration/components/clients/page/counts-test.js index 926f8f6319..817f2dc18f 100644 --- a/ui/tests/integration/components/clients/page/counts-test.js +++ b/ui/tests/integration/components/clients/page/counts-test.js @@ -260,14 +260,16 @@ module('Integration | Component | clients | Page::Counts', function (hooks) { ); }); - test('it should render empty state for no start when CE', async function (assert) { + test('it should render empty state for no start or no end when CE', async function (assert) { this.owner.lookup('service:version').type = 'community'; this.startTimestamp = null; this.activity = {}; await this.renderComponent(); - assert.dom(GENERAL.emptyStateTitle).hasText('No start date found', 'Empty state renders'); + assert + .dom(GENERAL.emptyStateTitle) + .hasText('Input the start and end dates to view client attribution by path.', 'Empty state renders'); assert.dom(CLIENT_COUNT.dateRange.edit).hasText('Set date range'); }); diff --git a/ui/tests/integration/components/clients/page/overview-test.js b/ui/tests/integration/components/clients/page/overview-test.js index 5544782994..a24e70af55 100644 --- a/ui/tests/integration/components/clients/page/overview-test.js +++ b/ui/tests/integration/components/clients/page/overview-test.js @@ -8,6 +8,7 @@ import { setupRenderingTest } from 'vault/tests/helpers'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setRunOptions } from 'ember-a11y-testing/test-support'; import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers'; import { filterActivityResponse } from 'vault/mirage/handlers/clients'; import { CHARTS, CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors'; @@ -47,6 +48,13 @@ module('Integration | Component | clients/page/overview', function (hooks) { this.mountPath = ''; this.namespace = ''; this.versionHistory = ''; + + // Fails on #ember-testing-container + setRunOptions({ + rules: { + 'scrollable-region-focusable': { enabled: false }, + }, + }); }); test('it hides attribution data when mount filter applied', async function (assert) { diff --git a/ui/tests/integration/components/clients/page/sync-test.js b/ui/tests/integration/components/clients/page/sync-test.js index 604cde102d..611ff4520f 100644 --- a/ui/tests/integration/components/clients/page/sync-test.js +++ b/ui/tests/integration/components/clients/page/sync-test.js @@ -14,8 +14,6 @@ import { getUnixTime } from 'date-fns'; import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { CLIENT_COUNT, CHARTS } from 'vault/tests/helpers/clients/client-count-selectors'; import { formatNumber } from 'core/helpers/format-number'; -import { calculateAverage } from 'vault/utils/chart-helpers'; -import { dateFormat } from 'core/helpers/date-format'; import { assertBarChart } from 'vault/tests/helpers/clients/client-count-helpers'; const START_TIME = getUnixTime(LICENSE_START); @@ -73,37 +71,24 @@ module('Integration | Component | clients | Clients::Page::Sync', function (hook test('it should render with full month activity data', async function (assert) { const monthCount = this.activity.byMonth.length; - assert.expect(7 + monthCount * 2); + assert.expect(3 + monthCount); const expectedTotal = formatNumber([this.activity.total.secret_syncs]); - const expectedNewAvg = formatNumber([ - calculateAverage( - this.activity.byMonth.map((m) => m?.new_clients), - 'secret_syncs' - ), - ]); await this.renderComponent(); - assert .dom(statText('Total sync clients')) .hasText( `Total sync clients The total number of secrets synced from Vault to other destinations during this date range. ${expectedTotal}`, `renders correct total sync stat ${expectedTotal}` ); - assert.dom(statText('Average new sync clients per month')).hasTextContaining(`${expectedNewAvg}`); - - const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { - withTimeZone: true, - }); - assert.dom(CHARTS.timestamp).hasText(`Updated ${formattedTimestamp}`, 'renders response timestamp'); assertBarChart(assert, 'Secrets sync usage', this.activity.byMonth); - assertBarChart(assert, 'Monthly new', this.activity.byMonth); }); test('it should render stats without chart for a single month', async function (assert) { const activityQuery = { start_time: { timestamp: END_TIME }, end_time: { timestamp: END_TIME } }; this.activity = await this.store.queryRecord('clients/activity', activityQuery); const expectedTotal = formatNumber([this.activity.total.secret_syncs]); + await this.renderComponent(); assert.dom(CHARTS.chart('Secrets sync usage')).doesNotExist('total usage chart does not render'); @@ -112,7 +97,7 @@ module('Integration | Component | clients | Clients::Page::Sync', function (hook assert .dom(usageStats('Secrets sync usage')) .hasText( - `Secrets sync usage Usage metrics tutorial This data can be used to understand how many secrets sync clients have been used for this date range. Each Vault secret that is synced to at least one destination counts as one Vault client. Total sync clients ${expectedTotal}`, + `Secrets sync usage Usage metrics tutorial Secrets sync clients which interacted with Vault for the first time each month. Each bar represents the total new sync clients for that month. Total sync clients ${expectedTotal}`, 'it renders usage stats with single month copy' ); }); @@ -147,7 +132,7 @@ module('Integration | Component | clients | Clients::Page::Sync', function (hook assert.dom(GENERAL.emptyStateMessage).hasText('There is no sync data available for this month.'); }); - test('it should render an empty total usage chart if secrets sync is activated but monthly syncs are null or 0', async function (assert) { + test('it should render an empty total usage chart if secrets sync is activated but monthly syncs are null or 0', async function (assert) { // manually stub because mirage isn't setup to handle mixed data yet const counts = { clients: 10, diff --git a/ui/tests/integration/components/clients/page/token-test.js b/ui/tests/integration/components/clients/page/token-test.js index 9528ae324d..c502febf9c 100644 --- a/ui/tests/integration/components/clients/page/token-test.js +++ b/ui/tests/integration/components/clients/page/token-test.js @@ -11,10 +11,7 @@ import { render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients'; import { getUnixTime } from 'date-fns'; -import { calculateAverage } from 'vault/utils/chart-helpers'; import { formatNumber } from 'core/helpers/format-number'; -import { dateFormat } from 'core/helpers/date-format'; -import { GENERAL } from 'vault/tests/helpers/general-selectors'; import { CHARTS, CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors'; import { assertBarChart } from 'vault/tests/helpers/clients/client-count-helpers'; @@ -70,7 +67,7 @@ module('Integration | Component | clients | Clients::Page::Token', function (hoo test('it should render monthly total chart', async function (assert) { const count = this.activity.byMonth.length; const { entity_clients, non_entity_clients } = this.activity.total; - assert.expect(count + 7); + assert.expect(count + 6); const expectedTotal = formatNumber([entity_clients + non_entity_clients]); const chart = CHARTS.container('Entity/Non-entity clients usage'); @@ -84,70 +81,17 @@ module('Integration | Component | clients | Clients::Page::Token', function (hoo assert.dom(`${chart} ${CHARTS.xAxis}`).hasText('7/23 8/23 9/23 10/23 11/23 12/23 1/24'); assertBarChart(assert, 'Entity/Non-entity clients usage', this.activity.byMonth, true); - const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { - withTimeZone: true, - }); - assert.dom(`${chart} ${CHARTS.timestamp}`).hasText(`Updated ${formattedTimestamp}`, 'renders timestamp'); assert.dom(`${chart} ${CHARTS.legendLabel(1)}`).hasText('Entity clients', 'Legend label renders'); assert.dom(`${chart} ${CHARTS.legendLabel(2)}`).hasText('Non-entity clients', 'Legend label renders'); }); - test('it should render monthly new chart', async function (assert) { - const count = this.newActivity.length; - assert.expect(count + 8); - const expectedNewEntity = formatNumber([calculateAverage(this.newActivity, 'entity_clients')]); - const expectedNewNonEntity = formatNumber([calculateAverage(this.newActivity, 'non_entity_clients')]); - const chart = CHARTS.container('Monthly new'); - - await this.renderComponent(); - - assert - .dom(CLIENT_COUNT.statTextValue('Average new entity clients per month')) - .hasText(expectedNewEntity, 'renders correct new entity clients'); - assert - .dom(CLIENT_COUNT.statTextValue('Average new non-entity clients per month')) - .hasText(expectedNewNonEntity, 'renders correct new nonentity clients'); - const formattedTimestamp = dateFormat([this.activity.responseTimestamp, 'MMM d yyyy, h:mm:ss aaa'], { - withTimeZone: true, - }); - assert.dom(`${chart} ${CHARTS.timestamp}`).hasText(`Updated ${formattedTimestamp}`, 'renders timestamp'); - assert.dom(`${chart} ${CHARTS.legendLabel(1)}`).hasText('Entity clients', 'Legend label renders'); - assert.dom(`${chart} ${CHARTS.legendLabel(2)}`).hasText('Non-entity clients', 'Legend label renders'); - - // assert bar chart is correct - assert.dom(`${chart} ${CHARTS.xAxis}`).hasText('7/23 8/23 9/23 10/23 11/23 12/23 1/24'); - assertBarChart(assert, 'Monthly new', this.newActivity, true); - }); - - test('it should render empty state for no new monthly data', async function (assert) { - this.activity.byMonth = this.activity.byMonth.map((d) => ({ - ...d, - new_clients: { month: d.month }, - })); - const chart = CHARTS.container('Monthly new'); - - await this.renderComponent(); - - assert.dom(`${chart} ${CHARTS.verticalBar}`).doesNotExist('Chart does not render'); - assert.dom(`${chart} ${CHARTS.legend}`).doesNotExist('Legend does not render'); - assert.dom(GENERAL.emptyStateTitle).hasText('No new clients'); - assert - .dom(CLIENT_COUNT.statText('Average new entity clients per month')) - .doesNotExist('New client counts does not exist'); - assert - .dom(CLIENT_COUNT.statText('Average new non-entity clients per month')) - .doesNotExist('Average new client counts does not exist'); - }); - test('it should render usage stats', async function (assert) { assert.expect(6); this.activity.endTime = this.activity.startTime; - const { - total: { entity_clients, non_entity_clients }, - } = this.activity; const checkUsage = () => { + const { entity_clients, non_entity_clients } = this.activity.total; assert .dom(CLIENT_COUNT.statTextValue('Total clients')) .hasText(formatNumber([entity_clients + non_entity_clients]), 'Total clients value renders'); diff --git a/ui/tests/integration/components/clients/running-total-test.js b/ui/tests/integration/components/clients/running-total-test.js index e3963529b6..343d29d5dd 100644 --- a/ui/tests/integration/components/clients/running-total-test.js +++ b/ui/tests/integration/components/clients/running-total-test.js @@ -34,6 +34,7 @@ module('Integration | Component | clients/running-total', function (hooks) { const activity = await store.queryRecord('clients/activity', activityQuery); this.byMonthActivity = activity.byMonth; this.newActivity = this.byMonthActivity.map((d) => d.new_clients); + this.totalUsageCounts = activity.total; this.set('timestamp', formatRFC3339(timestamp.now())); this.set('chartLegend', [ @@ -47,10 +48,9 @@ module('Integration | Component | clients/running-total', function (hooks) { await render(hbs` `); @@ -70,7 +70,7 @@ module('Integration | Component | clients/running-total', function (hooks) { assert.dom(CHARTS.chart('Vault client counts')).exists('bar chart renders'); const expectedValues = { - 'Running client total': formatNumber([this.totalUsageCounts.clients]), + 'Running new client total': formatNumber([this.totalUsageCounts.clients]), Entity: formatNumber([this.totalUsageCounts.entity_clients]), 'Non-entity': formatNumber([this.totalUsageCounts.non_entity_clients]), ACME: formatNumber([this.totalUsageCounts.acme_clients]), @@ -85,7 +85,7 @@ module('Integration | Component | clients/running-total', function (hooks) { ); } - // assert grouped bar chart is correct + // assert bar chart is correct findAll(CHARTS.xAxisLabel).forEach((e, i) => { assert .dom(e) @@ -96,7 +96,7 @@ module('Integration | Component | clients/running-total', function (hooks) { }); assert .dom(CHARTS.verticalBar) - .exists({ count: this.byMonthActivity.length * 2 }, 'renders correct number of bars '); + .exists({ count: this.byMonthActivity.length }, 'renders correct number of bars '); }); test('it renders with no new monthly data', async function (assert) { @@ -127,30 +127,11 @@ module('Integration | Component | clients/running-total', function (hooks) { }); test('it renders with single historical month data', async function (assert) { - const singleMonth = this.byMonthActivity[this.byMonthActivity.length - 1]; const singleMonthNew = this.newActivity[this.newActivity.length - 1]; - this.byMonthActivity = [singleMonth]; + this.byMonthActivity = [singleMonthNew]; this.isHistoricalMonth = true; - await this.renderComponent(); - - let expectedStats = { - 'Total monthly clients': formatNumber([singleMonth.clients]), - Entity: formatNumber([singleMonth.entity_clients]), - 'Non-entity': formatNumber([singleMonth.non_entity_clients]), - ACME: formatNumber([singleMonth.acme_clients]), - 'Secret sync': formatNumber([singleMonth.secret_syncs]), - }; - for (const label in expectedStats) { - assert - .dom(`[data-test-total] ${CLIENT_COUNT.statTextValue(label)}`) - .hasText( - `${expectedStats[label]}`, - `stat label: ${label} renders single month total: ${expectedStats[label]}` - ); - } - - expectedStats = { + const expectedStats = { 'New clients': formatNumber([singleMonthNew.clients]), Entity: formatNumber([singleMonthNew.entity_clients]), 'Non-entity': formatNumber([singleMonthNew.non_entity_clients]), @@ -166,7 +147,7 @@ module('Integration | Component | clients/running-total', function (hooks) { ); } assert.dom(CHARTS.chart('Vault client counts')).doesNotExist('bar chart does not render'); - assert.dom(CLIENT_COUNT.statTextValue()).exists({ count: 10 }, 'renders 10 stat text containers'); + assert.dom(CLIENT_COUNT.statTextValue()).exists({ count: 5 }, 'renders 5 stat text containers'); }); test('it hides secret sync totals when feature is not activated', async function (assert) { diff --git a/ui/tests/integration/utils/client-count-utils-test.js b/ui/tests/integration/utils/client-count-utils-test.js index 137fee7155..6a93bf69ab 100644 --- a/ui/tests/integration/utils/client-count-utils-test.js +++ b/ui/tests/integration/utils/client-count-utils-test.js @@ -486,6 +486,7 @@ module('Integration | Util | client count utils', function (hooks) { module('filteredTotalForMount', function (hooks) { hooks.beforeEach(function () { this.byNamespace = SERIALIZED_ACTIVITY_RESPONSE.by_namespace; + this.byMonth = SERIALIZED_ACTIVITY_RESPONSE.by_month; }); const emptyCounts = {