mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
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>
This commit is contained in:
parent
1b037c99cc
commit
cdbb0c49cc
3
changelog/30506.txt
Normal file
3
changelog/30506.txt
Normal file
@ -0,0 +1,3 @@
|
||||
```release-note:change
|
||||
ui: Client counting "running total" charts now reflect new clients only
|
||||
```
|
||||
@ -57,18 +57,15 @@ export default class ClientsActivityComponent extends Component<Args> {
|
||||
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() {
|
||||
|
||||
@ -36,10 +36,4 @@
|
||||
<EmptyState @icon="skip" @title="No data found" @bottomBorder={{true}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="timestamp" data-test-attribution-timestamp>
|
||||
{{#if @responseTimestamp}}
|
||||
Updated
|
||||
{{date-format @responseTimestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
@ -14,13 +14,11 @@ import Component from '@glimmer/component';
|
||||
* <Clients::Attribution
|
||||
* @noun="mount"
|
||||
* @attribution={{array (hash label="my-kv" clients=100)}}
|
||||
* @responseTimestamp="2018-04-03T14:15:30"
|
||||
* @isSecretsSyncActivated={{true}}
|
||||
* />
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
@ -48,11 +48,4 @@
|
||||
{{yield to="emptyState"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if @timestamp}}
|
||||
<div class="timestamp" data-test-chart-container-timestamp>
|
||||
Updated
|
||||
{{date-format @timestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@ -47,7 +47,7 @@ type ChartDatum = DatumBase & {
|
||||
* @example
|
||||
* <Clients::Charts::VerticalBarStacked
|
||||
* @chartTitle="Total monthly usage"
|
||||
* @data={{this.byMonthActivityData}}
|
||||
* @data={{this.byMonthNewClients}}
|
||||
* @chartLegend={{this.legend}}
|
||||
* @chartHeight={{250}}
|
||||
* />
|
||||
|
||||
@ -7,6 +7,13 @@
|
||||
<PH.Title>
|
||||
Client Usage
|
||||
</PH.Title>
|
||||
|
||||
{{#if @activityTimestamp}}
|
||||
<PH.Subtitle>
|
||||
Last Updated:
|
||||
{{date-format @activityTimestamp "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
|
||||
</PH.Subtitle>
|
||||
{{/if}}
|
||||
{{#if this.version.isEnterprise}}
|
||||
<PH.Description class="has-text-weight-semibold flex">
|
||||
<p>
|
||||
|
||||
@ -21,14 +21,15 @@ import { task } from 'ember-concurrency';
|
||||
* ```js
|
||||
* <Clients::PageHeader @startTimestamp="2022-06-01T23:00:11.050Z" @endTimestamp="2022-12-01T23:00:11.050Z" @namespace="foo" @upgradesDuringActivity={{array (hash version="1.10.1" previousVersion="1.9.1" timestampInstalled= "2021-11-18T10:23:16Z") }} />
|
||||
* ```
|
||||
* @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;
|
||||
|
||||
@ -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 }}
|
||||
<EmptyState
|
||||
@ -15,13 +15,7 @@
|
||||
class="is-shadowless"
|
||||
/>
|
||||
{{else if this.isDateRange}}
|
||||
<Clients::ChartContainer
|
||||
@title={{this.title}}
|
||||
@description={{this.description}}
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@hasChartData={{true}}
|
||||
class="no-legend"
|
||||
>
|
||||
<Clients::ChartContainer @title={{this.title}} @description={{this.description}} @hasChartData={{true}} class="no-legend">
|
||||
<:subTitle>
|
||||
<StatText
|
||||
@label="Total ACME clients"
|
||||
@ -35,45 +29,12 @@
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle={{this.title}}
|
||||
@data={{this.byMonthActivityData}}
|
||||
@data={{this.byMonthNewClients}}
|
||||
@dataKey="acme_clients"
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
</:chart>
|
||||
</Clients::ChartContainer>
|
||||
|
||||
{{#if this.totalUsageCounts.acme_clients}}
|
||||
{{! no need to render two empty charts! hide this one if there are no acme clients }}
|
||||
<Clients::ChartContainer
|
||||
@title="Monthly new"
|
||||
@description="ACME clients which interacted with Vault for the first time each month. Each bar represents the total new ACME clients for that month."
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@hasChartData={{true}}
|
||||
class="no-legend"
|
||||
>
|
||||
<:stats>
|
||||
{{#let (this.average this.byMonthNewClients "acme_clients") as |avg|}}
|
||||
{{#if avg}}
|
||||
<StatText
|
||||
@label="Average new ACME clients per month"
|
||||
@value={{avg}}
|
||||
@size="m"
|
||||
class="chart-subTitle has-top-padding-l"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</:stats>
|
||||
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle="Monthly new"
|
||||
@data={{this.byMonthNewClients}}
|
||||
@dataKey="acme_clients"
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
</:chart>
|
||||
</Clients::ChartContainer>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<Clients::UsageStats @title={{this.title}} @description={{this.description}}>
|
||||
<StatText @label="Total ACME clients" @value={{this.totalUsageCounts.acme_clients}} @size="l" class="column" />
|
||||
|
||||
@ -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.';
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
<Clients::PageHeader
|
||||
@billingStartTime={{this.formattedBillingStartDate}}
|
||||
@retentionMonths={{@config.retentionMonths}}
|
||||
@activityTimestamp={{@activity.responseTimestamp}}
|
||||
@startTimestamp={{@startTimestamp}}
|
||||
@endTimestamp={{@endTimestamp}}
|
||||
@namespace={{@namespace}}
|
||||
@ -120,19 +121,18 @@
|
||||
|
||||
{{! CLIENT COUNT PAGE COMPONENTS RENDER HERE }}
|
||||
{{yield}}
|
||||
|
||||
{{else if (and this.version.isCommunity (not @startTimestamp))}}
|
||||
{{! Empty state for community without start query param }}
|
||||
{{else if (and this.version.isCommunity (or (not @startTimestamp) (not @endTimestamp)))}}
|
||||
{{! Empty state for community without start or end query param }}
|
||||
<EmptyState
|
||||
@title="No start date found"
|
||||
@message="In order to get the most from this data, please enter a start month above. Vault will calculate new clients starting from that month."
|
||||
@title="Input the start and end dates to view client attribution by path."
|
||||
@message="Only historical data may be queried. No data is available for the current month."
|
||||
/>
|
||||
{{else}}
|
||||
<EmptyState
|
||||
@title="No data received {{if this.dateRangeMessage this.dateRangeMessage}}"
|
||||
@message={{if
|
||||
this.version.isCommunity
|
||||
"Select a start date above to query client count data."
|
||||
"Select a start and end date above to query client count data."
|
||||
"Update the filter values or click the button to reset them."
|
||||
}}
|
||||
>
|
||||
|
||||
@ -68,7 +68,7 @@ export default class ClientsCountsPageComponent extends Component<Args> {
|
||||
// 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<Args> {
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@ -5,12 +5,11 @@
|
||||
|
||||
<Clients::RunningTotal
|
||||
@isSecretsSyncActivated={{this.flags.secretsSyncIsActivated}}
|
||||
@byMonthActivityData={{this.byMonthActivityData}}
|
||||
@byMonthNewClients={{this.byMonthNewClients}}
|
||||
@isHistoricalMonth={{and (not this.isCurrentMonth) (not this.isDateRange)}}
|
||||
@isCurrentMonth={{this.isCurrentMonth}}
|
||||
@runningTotals={{this.totalUsageCounts}}
|
||||
@upgradeData={{this.upgradesDuringActivity}}
|
||||
@responseTimestamp={{@activity.responseTimestamp}}
|
||||
@mountPath={{@mountPath}}
|
||||
/>
|
||||
|
||||
@ -18,14 +17,12 @@
|
||||
<Clients::Attribution
|
||||
@noun="namespace"
|
||||
@attribution={{@activity.byNamespace}}
|
||||
@responseTimestamp={{@activity.responseTimestamp}}
|
||||
@isSecretsSyncActivated={{this.flags.secretsSyncIsActivated}}
|
||||
/>
|
||||
{{#if this.namespaceMountAttribution}}
|
||||
<Clients::Attribution
|
||||
@noun="mount"
|
||||
@attribution={{this.namespaceMountAttribution}}
|
||||
@responseTimestamp={{@activity.responseTimestamp}}
|
||||
@isSecretsSyncActivated={{this.flags.secretsSyncIsActivated}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
@ -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 }}
|
||||
<EmptyState
|
||||
@ -16,7 +16,6 @@
|
||||
<Clients::ChartContainer
|
||||
@title={{this.title}}
|
||||
@description={{this.description}}
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@hasChartData={{true}}
|
||||
class="no-legend"
|
||||
>
|
||||
@ -33,45 +32,12 @@
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle={{this.title}}
|
||||
@data={{this.byMonthActivityData}}
|
||||
@data={{this.byMonthNewClients}}
|
||||
@dataKey="secret_syncs"
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
</:chart>
|
||||
</Clients::ChartContainer>
|
||||
|
||||
{{! no need to render two empty charts! hide this one if there is no sync data }}
|
||||
{{#if this.totalUsageCounts.secret_syncs}}
|
||||
<Clients::ChartContainer
|
||||
@title="Monthly new"
|
||||
@description="Secrets sync clients which interacted with Vault for the first time each month. Each bar represents the total new sync clients for that month."
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@hasChartData={{true}}
|
||||
class="no-legend"
|
||||
>
|
||||
<:stats>
|
||||
{{#let (this.average this.byMonthNewClients "secret_syncs") as |avg|}}
|
||||
{{#if avg}}
|
||||
<StatText
|
||||
@label="Average new sync clients per month"
|
||||
@value={{avg}}
|
||||
@size="m"
|
||||
class="chart-subTitle has-top-padding-l"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</:stats>
|
||||
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle="Monthly new"
|
||||
@data={{this.byMonthNewClients}}
|
||||
@dataKey="secret_syncs"
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
</:chart>
|
||||
</Clients::ChartContainer>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<Clients::UsageStats @title={{this.title}} @description={{this.description}}>
|
||||
<StatText @label="Total sync clients" @value={{this.totalUsageCounts.secret_syncs}} @size="l" class="column" />
|
||||
|
||||
@ -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.';
|
||||
}
|
||||
|
||||
@ -3,11 +3,10 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#if (and this.byMonthActivityData this.isDateRange)}}
|
||||
{{#if (and this.byMonthNewClients this.isDateRange)}}
|
||||
<Clients::ChartContainer
|
||||
@title="Entity/Non-entity clients usage"
|
||||
@description="This data can be used to understand how many entity and non-entity clients are using Vault each month for this date range. Each client is counted once per month."
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@description="Entity or non-entity clients which interacted with Vault for the first time during this date range. Each bar represents the total new clients for that month."
|
||||
@legend={{this.legend}}
|
||||
@hasChartData={{true}}
|
||||
>
|
||||
@ -24,58 +23,11 @@
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarStacked
|
||||
@chartTitle="Entity/Non-entity clients usage"
|
||||
@data={{this.byMonthActivityData}}
|
||||
@chartLegend={{this.legend}}
|
||||
@chartHeight={{250}}
|
||||
/>
|
||||
</:chart>
|
||||
</Clients::ChartContainer>
|
||||
|
||||
<Clients::ChartContainer
|
||||
@title="Monthly new"
|
||||
@description="Entity or non-entity clients which interacted with Vault for the first time during this date range. Each bar represents the total new clients for that month."
|
||||
@timestamp={{@activity.responseTimestamp}}
|
||||
@legend={{this.legend}}
|
||||
@hasChartData={{this.hasNewClients}}
|
||||
class={{unless this.hasNewClients "no-legend"}}
|
||||
>
|
||||
<:stats>
|
||||
<StatText
|
||||
@label="Average new entity clients per month"
|
||||
@value={{this.average this.byMonthNewClients "entity_clients"}}
|
||||
@size="m"
|
||||
class="chart-subTitle has-top-padding-l"
|
||||
/>
|
||||
|
||||
<StatText
|
||||
@label="Average new non-entity clients per month"
|
||||
@value={{this.average this.byMonthNewClients "non_entity_clients"}}
|
||||
@size="m"
|
||||
class="data-details-top has-top-padding-l"
|
||||
/>
|
||||
</:stats>
|
||||
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarStacked
|
||||
@chartTitle="Monthly new"
|
||||
@data={{this.byMonthNewClients}}
|
||||
@chartLegend={{this.legend}}
|
||||
@chartHeight={{250}}
|
||||
/>
|
||||
</:chart>
|
||||
|
||||
<:emptyState>
|
||||
<EmptyState
|
||||
@title="No new clients"
|
||||
@subTitle="There is no new client data available for this {{if
|
||||
this.countsController.mountPath
|
||||
'auth method'
|
||||
'namespace'
|
||||
}} in this date range."
|
||||
@bottomBorder={{true}}
|
||||
class="is-shadowless"
|
||||
/>
|
||||
</:emptyState>
|
||||
</Clients::ChartContainer>
|
||||
{{else}}
|
||||
{{! Renders when viewing a single month or activity log data that predates the monthly breakdown added in 1.11 }}
|
||||
|
||||
@ -3,20 +3,15 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#if (gt @byMonthActivityData.length 1)}}
|
||||
<Clients::ChartContainer
|
||||
@title="Vault client counts"
|
||||
@description={{this.chartContainerText}}
|
||||
@timestamp={{@responseTimestamp}}
|
||||
@hasChartData={{true}}
|
||||
>
|
||||
{{#if (gt @byMonthNewClients.length 1)}}
|
||||
<Clients::ChartContainer @title="Vault client counts" @description={{this.chartContainerText}} @hasChartData={{true}}>
|
||||
<:subTitle>
|
||||
<StatText
|
||||
@label="Running client total"
|
||||
@subText="The number of clients which interacted with Vault during this date range."
|
||||
@label="Running new client total"
|
||||
@subText="The number of new clients which interacted with Vault during the selected period."
|
||||
@value={{@runningTotals.clients}}
|
||||
@size="l"
|
||||
@tooltipText="This number is the total for the queried date range. The chart displays a monthly breakdown of total clients per month."
|
||||
@tooltipText="This number is the total for the queried date range. The chart displays a monthly breakdown of total new clients per month."
|
||||
/>
|
||||
</:subTitle>
|
||||
|
||||
@ -36,12 +31,11 @@
|
||||
</:stats>
|
||||
|
||||
<:chart>
|
||||
<Clients::Charts::VerticalBarGrouped
|
||||
@data={{this.runningTotalData}}
|
||||
@legend={{this.chartLegend}}
|
||||
@upgradeData={{@upgradeData}}
|
||||
@chartHeight="250"
|
||||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle="Vault client counts"
|
||||
@data={{this.runningTotalData}}
|
||||
@dataKey="new_clients"
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
</:chart>
|
||||
|
||||
@ -50,32 +44,16 @@
|
||||
<span class="legend-colors dot-{{l.key}}"></span><span class="legend-label">{{capitalize l.label}}</span>
|
||||
{{/each}}
|
||||
</:legend>
|
||||
|
||||
</Clients::ChartContainer>
|
||||
{{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)}}
|
||||
<Clients::UsageStats @title="Vault client counts" @description={{this.chartContainerText}}>
|
||||
<div class="column" data-test-new>
|
||||
<StatText
|
||||
@label="New clients"
|
||||
@subText="This is the number of clients which were created in Vault for the first time in the selected month."
|
||||
@value={{singleMonthData.new_clients.clients}}
|
||||
@size="l"
|
||||
class="has-bottom-margin-l"
|
||||
/>
|
||||
<div class="flex row-wrap gap-36">
|
||||
<StatText @label="Entity" @value={{singleMonthData.new_clients.entity_clients}} @size="m" />
|
||||
<StatText @label="Non-entity" @value={{singleMonthData.new_clients.non_entity_clients}} @size="m" />
|
||||
<StatText @label="ACME" @value={{singleMonthData.new_clients.acme_clients}} @size="m" />
|
||||
{{#if @isSecretsSyncActivated}}
|
||||
<StatText @label="Secret sync" @value={{singleMonthData.new_clients.secret_syncs}} @size="m" />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="column" data-test-total>
|
||||
<StatText
|
||||
@label="Total monthly clients"
|
||||
@subText="This is the number of total clients which used Vault for the given month, both new and previous."
|
||||
@value={{singleMonthData.clients}}
|
||||
@size="l"
|
||||
class="has-bottom-margin-l"
|
||||
@ -85,7 +63,7 @@
|
||||
<StatText @label="Non-entity" @value={{singleMonthData.non_entity_clients}} @size="m" />
|
||||
<StatText @label="ACME" @value={{singleMonthData.acme_clients}} @size="m" />
|
||||
{{#if @isSecretsSyncActivated}}
|
||||
<StatText @label="Secret sync" @value={{singleMonthData.secret_syncs}} @size="m" class="column-start-4" />
|
||||
<StatText @label="Secret sync" @value={{singleMonthData.secret_syncs}} @size="m" />
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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<Args> {
|
||||
}
|
||||
|
||||
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' }];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -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) {
|
||||
<Clients::Attribution
|
||||
@noun={{this.noun}}
|
||||
@attribution={{this.namespaceAttribution}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
/>
|
||||
`);
|
||||
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`
|
||||
<Clients::Attribution
|
||||
@attribution={{this.namespaceAttribution}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
/>
|
||||
`);
|
||||
|
||||
@ -147,7 +142,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
await render(hbs`
|
||||
<Clients::Attribution
|
||||
@attribution={{this.namespaceAttribution}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
@isSecretsSyncActivated={{true}}
|
||||
/>
|
||||
`);
|
||||
@ -160,7 +154,6 @@ module('Integration | Component | clients/attribution', function (hooks) {
|
||||
await render(hbs`
|
||||
<Clients::Attribution
|
||||
@attribution={{this.namespaceAttribution}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
/>
|
||||
`);
|
||||
|
||||
|
||||
@ -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'
|
||||
);
|
||||
});
|
||||
|
||||
@ -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');
|
||||
});
|
||||
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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`
|
||||
<Clients::RunningTotal
|
||||
@isSecretsSyncActivated={{this.isSecretsSyncActivated}}
|
||||
@byMonthActivityData={{this.byMonthActivity}}
|
||||
@byMonthNewClients={{this.byMonthActivity}}
|
||||
@runningTotals={{this.totalUsageCounts}}
|
||||
@upgradeData={{this.upgradesDuringActivity}}
|
||||
@responseTimestamp={{this.timestamp}}
|
||||
@isHistoricalMonth={{this.isHistoricalMonth}}
|
||||
/>
|
||||
`);
|
||||
@ -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) {
|
||||
|
||||
@ -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 = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user