mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
Backport UI: Render total monthly clients for HVD managed clusters and "new" for only self-managed into ce/main (#10703)
* UI: Render total monthly clients for HVD managed clusters and "new" for only self-managed (#10472) * rename byMonthNewClients to byMonthClients * render total vs new clients depending on cluster type * fix mutation of original array order * add changelog * Apply suggestion from @hellobontempo * Apply suggestion from @hellobontempo * Apply suggestion from @hellobontempo * add test coverage * hide client list tab for HVD clusters * add test coverage * make copy changes * apply copy updates from feedback sync * skip hvd test on CE runs * restart tests --------- Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Co-authored-by: claire bontempo <cbontempo@hashicorp.com>
This commit is contained in:
parent
90571de3bc
commit
e8ef7285a4
24 changed files with 263 additions and 87 deletions
3
changelog/_10472.txt
Normal file
3
changelog/_10472.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
ui/activity: Display total instead of new monthly clients for HCP managed clusters
|
||||
```
|
||||
|
|
@ -48,7 +48,7 @@ type ChartDatum = DatumBase & {
|
|||
* @example
|
||||
* <Clients::Charts::VerticalBarStacked
|
||||
* @chartTitle="Total monthly usage"
|
||||
* @data={{this.byMonthNewClients}}
|
||||
* @data={{this.byMonthClients}}
|
||||
* @chartLegend={{this.legend}}
|
||||
* @chartHeight={{250}}
|
||||
* />
|
||||
|
|
|
|||
|
|
@ -39,7 +39,11 @@ Data visualizations render in in a flex row with a 1/3-width left element and a
|
|||
<div class="legend-container" data-test-counts-card-legend>
|
||||
{{#each @legend as |l idx|}}
|
||||
<div class="legend-item">
|
||||
<span class="dots legend-dot-{{if (eq l.key 'new_clients') 'new_clients' idx}}"></span>
|
||||
{{#if (includes l.key (array "clients" "new_clients"))}}
|
||||
<span class="dots legend-dot-total"></span>
|
||||
{{else}}
|
||||
<span class="dots legend-dot-{{idx}}"></span>
|
||||
{{/if}}
|
||||
<Hds::Text::Body @tag="p" @size="100">{{l.label}}</Hds::Text::Body>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
<div class="is-flex-column align-items-end">
|
||||
{{#if this.version.isEnterprise}}
|
||||
<Hds::Text::Display @tag="p" @size="100" class="has-bottom-margin-xs">
|
||||
Change billing period
|
||||
{{if this.flags.isHvdManaged "Change data period" "Change billing period"}}
|
||||
</Hds::Text::Display>
|
||||
<Hds::Dropdown class="has-left-margin-xs" @matchToggleWidth={{true}} as |D|>
|
||||
<D.ToggleButton @text="Billing start date" @color="secondary" data-test-date-range-edit />
|
||||
<Hds::Dropdown class="has-left-margin-xs" as |D|>
|
||||
<D.ToggleButton @text={{this.formatDropdownDate @startTimestamp}} @color="secondary" data-test-date-range-edit />
|
||||
<D.Description @text="Current period" />
|
||||
<D.Checkmark
|
||||
{{! Pass an empty string to reset query param because the current billing period is the default }}
|
||||
{{on "click" (fn this.updateEnterpriseDateRange "")}}
|
||||
{{on "click" (fn this.updateEnterpriseDateRange "" D.close)}}
|
||||
@selected={{this.isSelected @billingStartTime}}
|
||||
data-test-date-range-billing-start="0"
|
||||
>
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<D.Description @text="Historical periods" />
|
||||
{{#each this.historicalBillingPeriods as |period idx|}}
|
||||
<D.Checkmark
|
||||
{{on "click" (fn this.updateEnterpriseDateRange period)}}
|
||||
{{on "click" (fn this.updateEnterpriseDateRange period D.close)}}
|
||||
data-test-date-range-billing-start={{add idx 1}}
|
||||
@selected={{this.isSelected period}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { buildISOTimestamp, parseAPITimestamp } from 'core/utils/date-formatters
|
|||
import timestamp from 'core/utils/timestamp';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type { HTMLElementEvent } from 'forms';
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ interface Args {
|
|||
*/
|
||||
|
||||
export default class ClientsDateRangeComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
@tracked modalStart = ''; // format yyyy-MM
|
||||
|
|
@ -64,7 +66,8 @@ export default class ClientsDateRangeComponent extends Component<Args> {
|
|||
|
||||
get historicalBillingPeriods() {
|
||||
// we want whole billing periods
|
||||
const count = Math.floor(this.args.retentionMonths / 12);
|
||||
const totalMonths = this.args.retentionMonths || 48;
|
||||
const count = Math.floor(totalMonths / 12);
|
||||
const periods: string[] = [];
|
||||
|
||||
for (let i = 1; i <= count; i++) {
|
||||
|
|
@ -118,9 +121,10 @@ export default class ClientsDateRangeComponent extends Component<Args> {
|
|||
}
|
||||
|
||||
@action
|
||||
updateEnterpriseDateRange(start: string) {
|
||||
updateEnterpriseDateRange(start: string, close: CallableFunction) {
|
||||
// We do not send an end_time so the backend handles computing the expected billing period
|
||||
this.args.onChange({ start_time: start, end_time: '' });
|
||||
close();
|
||||
}
|
||||
|
||||
// HELPERS
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
{{#if this.version.isEnterprise}}
|
||||
<PH.Description class="has-text-weight-semibold flex">
|
||||
<p>
|
||||
For billing period:
|
||||
{{if this.flags.isHvdManaged "For data period:" "For billing period:"}}
|
||||
<span data-test-date-range="start">{{this.formattedStartDate}}</span>
|
||||
-
|
||||
<span data-test-date-range="end">{{this.formattedEndDate}}</span>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import { task } from 'ember-concurrency';
|
|||
*/
|
||||
export default class ClientsPageHeaderComponent extends Component {
|
||||
@service download;
|
||||
@service flags;
|
||||
@service namespace;
|
||||
@service router;
|
||||
@service store;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
<div class="has-top-bottom-margin">
|
||||
<Hds::Text::Body @tag="p" @size="100" class="has-bottom-margin-s">
|
||||
This is the dashboard for your overall client count usages. Review Vault's
|
||||
This is the dashboard for your overall client count usage. Review Vault's
|
||||
<Hds::Link::Inline @href={{doc-link "/vault/docs/concepts/client-count"}} @isHrefExternal={{true}}>
|
||||
client counting documentation</Hds::Link::Inline>
|
||||
for more information.
|
||||
|
|
@ -69,8 +69,12 @@
|
|||
</Hds::Alert>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.version.isEnterprise}}
|
||||
{{#if (and this.version.isEnterprise (not this.flags.isHvdManaged))}}
|
||||
{{! The "Client list" tab only renders for enterprise versions so there is no need for the nav bar }}
|
||||
{{! The "Client list" tab is hidden on HVD managed clusters (for now) because the "Month" filter for that page
|
||||
uses the `client_first_used_time` timestamp. This timestamp tracks when a client is FIRST seen in the queried date range (i.e. billing period).
|
||||
This is useful for self-managed customers who are billed on monthly NEW clients, but not for HVD users who are billed on TOTAL clients per
|
||||
month regardless of whether the client was seen in a previous month. }}
|
||||
<Clients::Counts::NavBar />
|
||||
{{/if}}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { parseAPITimestamp } from 'core/utils/date-formatters';
|
|||
import { filterVersionHistory } from 'core/utils/client-count-utils';
|
||||
|
||||
import type AdapterError from '@ember-data/adapter/error';
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type ClientsActivityModel from 'vault/models/clients/activity';
|
||||
import type ClientsConfigModel from 'vault/models/clients/config';
|
||||
|
|
@ -26,6 +27,7 @@ interface Args {
|
|||
}
|
||||
|
||||
export default class ClientsCountsPageComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
get formattedStartDate() {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,12 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Clients::RunningTotal @byMonthNewClients={{this.byMonthNewClients}} @runningTotals={{@activity.total}} />
|
||||
<Clients::RunningTotal @byMonthClients={{this.byMonthClients}} @runningTotals={{@activity.total}} />
|
||||
|
||||
{{! by_namespace is an empty array when there is no client count activity data }}
|
||||
{{#if @activity.byNamespace}}
|
||||
<Clients::CountsCard
|
||||
@title="Client attribution"
|
||||
@description="Select a month to view the client count per mount for that month."
|
||||
>
|
||||
<Clients::CountsCard @title="Client attribution">
|
||||
<:subheader>
|
||||
<Hds::Text::Body class="has-top-margin-l has-bottom-margin-m" @tag="p" @size="100" @color="faint">Use the filters to
|
||||
view the clients attributed by path.
|
||||
</Hds::Text::Body>
|
||||
<Clients::FilterToolbar
|
||||
@dataset={{this.activityData}}
|
||||
@onFilter={{this.handleFilter}}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,11 @@ import Component from '@glimmer/component';
|
|||
import { cached, tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { filterTableData, flattenMounts } from 'core/utils/client-count-utils';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
import type ClientsActivityModel from 'vault/vault/models/clients/activity';
|
||||
import type { ClientFilterTypes } from 'vault/vault/client-counts/activity-api';
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
|
||||
export interface Args {
|
||||
activity: ClientsActivityModel;
|
||||
|
|
@ -18,10 +20,18 @@ export interface Args {
|
|||
}
|
||||
|
||||
export default class ClientsOverviewPageComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
|
||||
@tracked selectedMonth = '';
|
||||
|
||||
@cached
|
||||
get byMonthNewClients() {
|
||||
get byMonthClients() {
|
||||
// HVD clusters are billed differently and the monthly total is the important metric.
|
||||
if (this.flags.isHvdManaged) {
|
||||
return this.args.activity.byMonth;
|
||||
}
|
||||
// For self-managed clusters only the new_clients per month are relevant because clients accumulate over a billing period.
|
||||
// (Since "total" per month is not cumulative it's not a useful metric)
|
||||
return this.args.activity.byMonth?.map((m) => m?.new_clients) || [];
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +40,7 @@ export default class ClientsOverviewPageComponent extends Component<Args> {
|
|||
// If no month is selected the table displays all of the activity for the queried date range.
|
||||
const selectedMonth = this.args.filterQueryParams.month;
|
||||
const namespaceData = selectedMonth
|
||||
? this.byMonthNewClients.find((m) => m.timestamp === selectedMonth)?.namespaces
|
||||
? this.byMonthClients.find((m) => m.timestamp === selectedMonth)?.namespaces
|
||||
: this.args.activity.byNamespace;
|
||||
|
||||
// Get the array of "mounts" data nested in each namespace object and flatten
|
||||
|
|
@ -39,7 +49,7 @@ export default class ClientsOverviewPageComponent extends Component<Args> {
|
|||
|
||||
@cached
|
||||
get months() {
|
||||
return this.byMonthNewClients.reverse().map((m) => m.timestamp);
|
||||
return this.byMonthClients.map((m) => m.timestamp).reverse();
|
||||
}
|
||||
|
||||
get tableData() {
|
||||
|
|
|
|||
|
|
@ -3,16 +3,16 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
{{#if @byMonthNewClients.length}}
|
||||
<Clients::CountsCard
|
||||
@title="Client usage trends for selected billing period"
|
||||
@description={{this.chartContainerText}}
|
||||
@legend={{this.chartLegend}}
|
||||
>
|
||||
{{#if @byMonthClients.length}}
|
||||
<Clients::CountsCard @title="Client usage trends" @description={{this.chartContainerText}} @legend={{this.chartLegend}}>
|
||||
|
||||
<:dataLeft>
|
||||
<Hds::Text::Body @tag="p">Client count and type distribution</Hds::Text::Body>
|
||||
<VaultReporting::DonutChart @data={{this.donutChartData}} @title="Total Clients" class="donut-chart" />
|
||||
<VaultReporting::DonutChart
|
||||
@data={{this.donutChartData}}
|
||||
@title={{if this.flags.isHvdManaged "Total unique clients" "Total clients"}}
|
||||
class="donut-chart"
|
||||
/>
|
||||
</:dataLeft>
|
||||
|
||||
<:dataRight>
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
<Clients::Charts::VerticalBarBasic
|
||||
@chartTitle="Client usage by month"
|
||||
@data={{this.runningTotalData}}
|
||||
@dataKey="new_clients"
|
||||
@dataKey={{this.dataKey}}
|
||||
@chartHeight={{200}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
</Clients::CountsCard>
|
||||
{{else}}
|
||||
{{! Renders when viewing activity log data that predates the monthly breakdown added in 1.11 }}
|
||||
<Clients::UsageStats @title="Client usage" @description="Total client usage for the selected billing period.">
|
||||
<Clients::UsageStats @title="Client usage" @description="Total client usage for the selected date range.">
|
||||
<StatText
|
||||
class="column"
|
||||
@label="Total clients"
|
||||
|
|
|
|||
|
|
@ -6,30 +6,42 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { service } from '@ember/service';
|
||||
import { toLabel } from 'core/helpers/to-label';
|
||||
|
||||
import type { ByMonthNewClients, TotalClients } from 'vault/vault/client-counts/activity-api';
|
||||
import type { ByMonthClients, ByMonthNewClients, TotalClients } from 'vault/vault/client-counts/activity-api';
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type VersionService from 'vault/services/version';
|
||||
|
||||
interface Args {
|
||||
byMonthNewClients: ByMonthNewClients[];
|
||||
byMonthClients: ByMonthClients[] | ByMonthNewClients[];
|
||||
runningTotals: TotalClients;
|
||||
}
|
||||
|
||||
export default class RunningTotal extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
@tracked showStacked = false;
|
||||
|
||||
get chartContainerText() {
|
||||
return `The total clients in the specified date range, displayed per month. This includes entity, non-entity${
|
||||
this.flags.secretsSyncIsActivated ? ', ACME and secrets sync clients' : ' and ACME clients'
|
||||
}. The total client count number is an important consideration for Vault billing.`;
|
||||
const range = this.version.isEnterprise ? 'billing period' : 'date range';
|
||||
return this.flags.isHvdManaged
|
||||
? 'Number of total unique clients in the data period by client type, and total number of unique clients per month. The monthly total is the relevant billing metric.'
|
||||
: `Number of clients in the ${range} by client type, and a breakdown of new clients per month during the ${range}. `;
|
||||
}
|
||||
|
||||
get dataKey() {
|
||||
return this.flags.isHvdManaged ? 'clients' : 'new_clients';
|
||||
}
|
||||
|
||||
get runningTotalData() {
|
||||
return this.args.byMonthNewClients.map((monthly) => ({
|
||||
// The parent component determines whether `monthly.clients` in @byMonthClients represents "new" or "total" clients per month.
|
||||
// (We render "new" for self-managed clusters and "total" for HVD-managed.)
|
||||
// As a result, we do not use `this.dataKey` to select a property from `monthly` but to add a superficial key
|
||||
// to the data that ensures the chart tooltip and legend text render appropriately.
|
||||
return this.args.byMonthClients.map((monthly) => ({
|
||||
...monthly,
|
||||
new_clients: monthly.clients,
|
||||
[this.dataKey]: monthly.clients,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +66,6 @@ export default class RunningTotal extends Component<Args> {
|
|||
...(this.flags.secretsSyncIsActivated ? [{ key: 'secret_syncs', label: 'Secret sync clients' }] : []),
|
||||
];
|
||||
}
|
||||
return [{ key: 'new_clients', label: 'New clients' }];
|
||||
return [{ key: this.dataKey, label: toLabel([this.dataKey]) }];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@
|
|||
<B.Td data-test-table-data={{key}} class="white-space-nowrap">
|
||||
<Hds::Copy::Snippet @textToCopy={{value}} @color="secondary" />
|
||||
</B.Td>
|
||||
{{else if (and (eq key "clients") this.version.isEnterprise)}}
|
||||
{{else if (and (eq key "clients") this.version.isEnterprise (not this.flags.isHvdManaged))}}
|
||||
{{! The "Client list" tab is hidden on HVD managed clusters (for now) because the "Month" filter for that page
|
||||
uses the `client_first_used_time` timestamp. This timestamp tracks when a client is FIRST seen in the queried date range (i.e. billing period).
|
||||
This is useful for self-managed customers who are billed on monthly NEW clients, but not for HVD users who are billed on TOTAL clients per
|
||||
month regardless of whether the client was seen in a previous month. }}
|
||||
<B.Td data-test-table-data={{key}} class="white-space-nowrap">
|
||||
<Hds::Link::Inline
|
||||
@route="vault.cluster.clients.counts.client-list"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { paginate } from 'core/utils/paginate-list';
|
|||
import { next } from '@ember/runloop';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type { ClientFilterTypes } from 'vault/vault/client-counts/activity-api';
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ interface Args {
|
|||
}
|
||||
|
||||
export default class ClientsTable extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
@tracked currentPage = 1;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,8 @@ export default class ClientsCountsRoute extends Route {
|
|||
|
||||
async fetchAndFormatExportData(startTimestamp: string | undefined, endTimestamp: string | undefined) {
|
||||
// The "Client List" tab is only available on enterprise versions
|
||||
if (this.version.isEnterprise) {
|
||||
// For now, it is also hidden on HVD managed clusters
|
||||
if (this.version.isEnterprise && !this.flags.isHvdManaged) {
|
||||
const adapter = this.store.adapterFor('clients/activity');
|
||||
let exportData, exportError;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,5 +4,25 @@
|
|||
*/
|
||||
|
||||
import Route from '@ember/routing/route';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
export default class ClientsCountsClientListRoute extends Route {}
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type VersionService from 'vault/services/version';
|
||||
|
||||
export default class ClientsCountsClientListRoute extends Route {
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly version: VersionService;
|
||||
|
||||
// The "Client list" tab is only available on enterprise versions
|
||||
// The "Client list" tab is hidden on HVD managed clusters (for now) because the "Month" filter for that page
|
||||
// uses the `client_first_used_time` timestamp. This timestamp tracks when a client is FIRST seen in the queried date range (i.e. billing period).
|
||||
// This is useful for self-managed customers who are billed on monthly NEW clients, but not for HVD users who are billed on TOTAL clients per
|
||||
// month regardless of whether the client was seen in a previous month.
|
||||
redirect() {
|
||||
if (this.version.isCommunity || this.flags.isHvdManaged) {
|
||||
this.router.transitionTo('vault.cluster.clients.counts');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ $fourth: #6cc5b0;
|
|||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
.legend-dot-new_clients {
|
||||
.legend-dot-total {
|
||||
background-color: $single;
|
||||
}
|
||||
// numbers are indices because chart legend is iterated over to ensure
|
||||
|
|
|
|||
|
|
@ -53,6 +53,24 @@ module('Acceptance | clients | counts | client list', function (hooks) {
|
|||
test('it hides client list tab on community', async function (assert) {
|
||||
this.version.type = 'community';
|
||||
assert.dom(GENERAL.tab('client list')).doesNotExist();
|
||||
|
||||
// Navigate directly to URL to test redirect
|
||||
await visit('/vault/clients/counts/client-list');
|
||||
assert.strictEqual(currentURL(), '/vault/clients/counts/overview', 'it redirects to overview');
|
||||
});
|
||||
|
||||
// skip this test on CE test runs because GET sys/license/features an enterprise only endpoint
|
||||
test('enterprise: it hides client list tab on HVD managed clusters', async function (assert) {
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
assert.dom(GENERAL.tab('client list')).doesNotExist();
|
||||
|
||||
// Navigate directly to URL to test redirect
|
||||
await visit('/vault/clients/counts/client-list');
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/clients/counts/overview?namespace=admin',
|
||||
'it redirects to overview'
|
||||
);
|
||||
});
|
||||
|
||||
test('it navigates to client list tab', async function (assert) {
|
||||
|
|
|
|||
|
|
@ -67,10 +67,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
|
||||
.hasText('January 2024', 'end month is correctly parsed from STATIC_NOW');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.dom(CLIENT_COUNT.card('Client usage trends'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends')} ${CHARTS.xAxisLabel}`)
|
||||
.hasText('7/23', 'x-axis labels start with billing start date');
|
||||
assert.dom(CHARTS.xAxisLabel).exists({ count: 7 }, 'chart months matches query');
|
||||
});
|
||||
|
|
@ -95,7 +95,7 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.dom(CLIENT_COUNT.usageStats('Client usage'))
|
||||
.exists('running total single month usage stats show');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.dom(CLIENT_COUNT.card('Client usage trends'))
|
||||
.doesNotExist('running total month over month charts do not show');
|
||||
|
||||
// change to start on month/year of upgrade to 1.10
|
||||
|
|
@ -108,10 +108,10 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
|
||||
.hasText('September 2023', 'client count start month is correctly parsed from start query');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.dom(CLIENT_COUNT.card('Client usage trends'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends for selected billing period')} ${CHARTS.xAxisLabel}`)
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends')} ${CHARTS.xAxisLabel}`)
|
||||
.hasText('9/23', 'x-axis labels start with queried start month (upgrade date)');
|
||||
assert.dom(CHARTS.xAxisLabel).exists({ count: 4 }, 'chart months matches query');
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
await fillIn(CLIENT_COUNT.dateRange.editDate('end'), upgradeMonth);
|
||||
await click(GENERAL.submitButton);
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.dom(CLIENT_COUNT.card('Client usage trends'))
|
||||
.exists('running total month over month charts show');
|
||||
|
||||
// query historical date range (from September 2023 to December 2023)
|
||||
|
|
@ -137,7 +137,7 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
.dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
|
||||
.hasText('December 2023', 'it displays correct end time');
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.dom(CLIENT_COUNT.card('Client usage trends'))
|
||||
.exists('Shows running totals with monthly breakdown charts');
|
||||
|
||||
assert.dom(CHARTS.xAxisLabel).exists({ count: 4 }, 'chart months matches query');
|
||||
|
|
@ -154,6 +154,14 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
});
|
||||
});
|
||||
|
||||
test('it does not render client list links for HVD managed clusters', async function (assert) {
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
assert
|
||||
.dom(`${GENERAL.tableData(0, 'clients')} a`)
|
||||
.doesNotExist('client counts do not render as hyperlinks');
|
||||
});
|
||||
|
||||
// * FILTERING ASSERTIONS
|
||||
// These tests use the static data from the ACTIVITY_RESPONSE_STUB to assert filtering
|
||||
// Filtering tests are split between integration and acceptance tests
|
||||
|
|
@ -336,21 +344,5 @@ module('Acceptance | clients | overview', function (hooks) {
|
|||
'it renders legend in order that matches the stacked bar data and does not include secret sync'
|
||||
);
|
||||
});
|
||||
|
||||
test('it should show secrets sync stats for HVD managed clusters', async function (assert) {
|
||||
// mock HVD managed cluster
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
await login();
|
||||
await visit('/vault/clients/counts/overview');
|
||||
assert.dom(CLIENT_COUNT.statLegendValue('Secret sync clients')).exists();
|
||||
await click(GENERAL.inputByAttr('toggle view'));
|
||||
assert
|
||||
.dom(CHARTS.legend)
|
||||
.hasText(
|
||||
'Entity clients Non-entity clients ACME clients Secret sync clients',
|
||||
'it renders legend in order that matches the stacked bar data'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ module('Integration | Component | clients/date-range', function (hooks) {
|
|||
this.billingStartTime = '2018-01-01T14:15:30';
|
||||
});
|
||||
|
||||
test('it billing start date dropdown for enterprise', async function (assert) {
|
||||
test('it renders billing start date dropdown for enterprise', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await click(DATE_RANGE.edit);
|
||||
const expectedPeriods = [
|
||||
|
|
@ -125,5 +125,32 @@ module('Integration | Component | clients/date-range', function (hooks) {
|
|||
assert.dom(item).hasText(month, `dropdown index: ${idx} renders ${month}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it updates toggle text when a new date is selected', async function (assert) {
|
||||
this.onChange = ({ start_time }) => this.set('startTimestamp', start_time);
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(DATE_RANGE.edit).hasText('January 2018').hasAttribute('aria-expanded', 'false');
|
||||
await click(DATE_RANGE.edit);
|
||||
assert.dom(DATE_RANGE.edit).hasAttribute('aria-expanded', 'true');
|
||||
await click(DATE_RANGE.dropdownOption(1));
|
||||
assert
|
||||
.dom(DATE_RANGE.edit)
|
||||
.hasText('January 2017')
|
||||
.hasAttribute('aria-expanded', 'false', 'it closes dropdown after selection');
|
||||
});
|
||||
|
||||
test('it renders billing period text', async function (assert) {
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(this.element)
|
||||
.hasText('Change billing period January 2018', 'it renders billing related text');
|
||||
});
|
||||
|
||||
test('it renders data period text for HVD managed clusters', async function (assert) {
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
await this.renderComponent();
|
||||
assert.dom(this.element).hasText('Change data period January 2018');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ module('Integration | Component | clients/page-header', function (hooks) {
|
|||
this.renderComponent = async () => {
|
||||
return render(hbs`
|
||||
<Clients::PageHeader
|
||||
@billingStartTime={{this.startTimestamp}}
|
||||
@startTimestamp={{this.startTimestamp}}
|
||||
@endTimestamp={{this.endTimestamp}}
|
||||
@upgradesDuringActivity={{this.upgradesDuringActivity}}
|
||||
|
|
@ -259,4 +260,24 @@ module('Integration | Component | clients/page-header', function (hooks) {
|
|||
assert.strictEqual(filename, 'clients_export_bar');
|
||||
});
|
||||
});
|
||||
|
||||
module('enterprise', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it renders billing period text', async function (assert) {
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(this.element)
|
||||
.hasTextContaining('Client Usage For billing period:', 'it renders billing related text');
|
||||
});
|
||||
|
||||
test('it renders data period text for HVD managed clusters', async function (assert) {
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
await this.renderComponent();
|
||||
assert.dom(this.element).hasTextContaining('Client Usage For data period:');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { click, find, findAll, render } from '@ember/test-helpers';
|
|||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
|
||||
import { CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-selectors';
|
||||
import { CHARTS, CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-selectors';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import sinon from 'sinon';
|
||||
import { ClientFilters, flattenMounts } from 'core/utils/client-count-utils';
|
||||
|
|
@ -143,6 +143,21 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
.hasText('No data found Clear or change filters to view client count data. Client count documentation');
|
||||
});
|
||||
|
||||
test('it renders NEW monthly clients for self-managed clusters instead of total clients', async function (assert) {
|
||||
this.filterQueryParams = {
|
||||
month: this.mostRecentMonth.timestamp,
|
||||
};
|
||||
const topMount = this.mostRecentMonth.new_clients.namespaces
|
||||
.find((ns) => ns.label === 'ns1/')
|
||||
.mounts.find((m) => m.label === 'auth/userpass/0/');
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(CHARTS.legend).hasText('New clients');
|
||||
assert
|
||||
.dom(GENERAL.tableData(0, 'clients'))
|
||||
.hasText(`${topMount.clients}`, 'table renders total monthly clients');
|
||||
});
|
||||
|
||||
test('it filters data if @filterQueryParams specify a month', async function (assert) {
|
||||
const filterKey = 'month';
|
||||
const filterValue = this.mostRecentMonth.timestamp;
|
||||
|
|
@ -209,4 +224,20 @@ module('Integration | Component | clients/page/overview', function (hooks) {
|
|||
.dom(CLIENT_COUNT.card('table empty state'))
|
||||
.hasText('No data found Clear or change filters to view client count data. Client count documentation');
|
||||
});
|
||||
|
||||
test('it renders TOTAL monthly clients for HVD instead of new clients', async function (assert) {
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
this.filterQueryParams = {
|
||||
month: this.mostRecentMonth.timestamp,
|
||||
};
|
||||
const topMount = this.mostRecentMonth.namespaces
|
||||
.find((ns) => ns.label === 'root')
|
||||
.mounts.find((m) => m.label === 'acme/pki/0/');
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(CHARTS.legend).hasText('Clients');
|
||||
assert
|
||||
.dom(GENERAL.tableData(0, 'clients'))
|
||||
.hasText(`${topMount.clients}`, 'table renders total monthly clients');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
|
||||
hooks.beforeEach(async function () {
|
||||
this.flags = this.owner.lookup('service:flags');
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.flags.activatedFlags = ['secrets-sync'];
|
||||
sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
|
||||
clientsHandler(this.server);
|
||||
|
|
@ -35,24 +36,53 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
end_time: { timestamp: getUnixTime(timestamp.now()) },
|
||||
};
|
||||
this.activity = await store.queryRecord('clients/activity', activityQuery);
|
||||
this.byMonthNewClients = this.activity.byMonth.map((d) => d.new_clients);
|
||||
this.byMonthClients = this.activity.byMonth.map((d) => d.new_clients);
|
||||
|
||||
this.renderComponent = async () => {
|
||||
await render(hbs`
|
||||
<Clients::RunningTotal
|
||||
@byMonthNewClients={{this.byMonthNewClients}}
|
||||
@byMonthClients={{this.byMonthClients}}
|
||||
@runningTotals={{this.activity.total}}
|
||||
/>
|
||||
`);
|
||||
};
|
||||
});
|
||||
|
||||
test('it text for community versions', async function (assert) {
|
||||
this.version.type = 'community';
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends')} p`)
|
||||
.hasText(
|
||||
'Number of clients in the date range by client type, and a breakdown of new clients per month during the date range.'
|
||||
);
|
||||
});
|
||||
|
||||
// abbreviating "ent" so the test is not filtered out in CE repo runs
|
||||
test('it renders text for ent versions', async function (assert) {
|
||||
this.version.type = 'enterprise';
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends')} p`)
|
||||
.hasText(
|
||||
'Number of clients in the billing period by client type, and a breakdown of new clients per month during the billing period.'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders text for HVD managed versions', async function (assert) {
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
await this.renderComponent();
|
||||
assert
|
||||
.dom(`${CLIENT_COUNT.card('Client usage trends')} p`)
|
||||
.hasText(
|
||||
'Number of total unique clients in the data period by client type, and total number of unique clients per month. The monthly total is the relevant billing metric.'
|
||||
);
|
||||
});
|
||||
|
||||
test('it renders with full monthly activity data', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('running total component renders');
|
||||
assert.dom(CLIENT_COUNT.card('Client usage trends')).exists('running total component renders');
|
||||
assert.dom(CHARTS.chart('Client usage by month')).exists('bar chart renders');
|
||||
assert.dom(CHARTS.legend).hasText('New clients');
|
||||
const expectedColor = 'rgb(28, 52, 95)';
|
||||
|
|
@ -76,22 +106,20 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
|
||||
// assert bar chart is correct
|
||||
findAll(CHARTS.xAxisLabel).forEach((e, i) => {
|
||||
const timestamp = this.byMonthNewClients[i].timestamp;
|
||||
const timestamp = this.byMonthClients[i].timestamp;
|
||||
const displayMonth = parseAPITimestamp(timestamp, 'M/yy');
|
||||
assert.dom(e).hasText(displayMonth, `renders x-axis labels for bar chart: ${displayMonth}`);
|
||||
});
|
||||
assert
|
||||
.dom(CHARTS.verticalBar)
|
||||
.exists({ count: this.byMonthNewClients.length }, 'renders correct number of bars ');
|
||||
.exists({ count: this.byMonthClients.length }, 'renders correct number of bars ');
|
||||
});
|
||||
|
||||
test('it toggles to split chart by client type', async function (assert) {
|
||||
await this.renderComponent();
|
||||
await click(GENERAL.inputByAttr('toggle view'));
|
||||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('running total component renders');
|
||||
assert.dom(CLIENT_COUNT.card('Client usage trends')).exists('running total component renders');
|
||||
assert.dom(CHARTS.chart('Client usage by month')).exists('bar chart renders');
|
||||
assert
|
||||
.dom(CHARTS.legend)
|
||||
|
|
@ -117,12 +145,12 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
|
||||
// assert bar chart is correct
|
||||
findAll(CHARTS.xAxisLabel).forEach((e, i) => {
|
||||
const timestamp = this.byMonthNewClients[i].timestamp;
|
||||
const timestamp = this.byMonthClients[i].timestamp;
|
||||
const displayMonth = parseAPITimestamp(timestamp, 'M/yy');
|
||||
assert.dom(e).hasText(`${displayMonth}`, `renders x-axis labels for bar chart: ${displayMonth}`);
|
||||
});
|
||||
|
||||
const months = this.byMonthNewClients.length;
|
||||
const months = this.byMonthClients.length;
|
||||
const barsPerMonth = expectedLegend.length;
|
||||
assert
|
||||
.dom(CHARTS.verticalBar)
|
||||
|
|
@ -130,7 +158,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
});
|
||||
|
||||
test('it renders when no monthly breakdown is available', async function (assert) {
|
||||
this.byMonthNewClients = [];
|
||||
this.byMonthClients = [];
|
||||
await this.renderComponent();
|
||||
const expectedStats = {
|
||||
Entity: formatNumber([this.activity.total.entity_clients]),
|
||||
|
|
@ -153,13 +181,11 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
test('it hides secret sync totals when feature is not activated', async function (assert) {
|
||||
this.flags.activatedFlags = [];
|
||||
// reset secret sync clients to 0
|
||||
this.byMonthNewClients = this.byMonthNewClients.map((obj) => ({ ...obj, secret_syncs: 0 }));
|
||||
this.byMonthClients = this.byMonthClients.map((obj) => ({ ...obj, secret_syncs: 0 }));
|
||||
|
||||
await this.renderComponent();
|
||||
|
||||
assert
|
||||
.dom(CLIENT_COUNT.card('Client usage trends for selected billing period'))
|
||||
.exists('running total component renders');
|
||||
assert.dom(CLIENT_COUNT.card('Client usage trends')).exists('running total component renders');
|
||||
assert.dom(CHARTS.chart('Client usage by month')).exists('bar chart renders');
|
||||
assert.dom(CLIENT_COUNT.statLegendValue('Entity clients')).exists();
|
||||
assert.dom(CLIENT_COUNT.statLegendValue('Non-entity clients')).exists();
|
||||
|
|
@ -187,7 +213,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
|
|||
assert.strictEqual(dotColor, color, `${label} - actual color: ${dotColor}, expected: ${color}`);
|
||||
});
|
||||
|
||||
const months = this.byMonthNewClients.length;
|
||||
const months = this.byMonthClients.length;
|
||||
const barsPerMonth = expectedLegend.length;
|
||||
assert
|
||||
.dom(CHARTS.verticalBar)
|
||||
|
|
|
|||
Loading…
Reference in a new issue