Skip to content

Commit 6d0e4f6

Browse files
UI: Update dashboard client count query to use billing_start_timestamp (#26729)
* remvoe request tolicense in dashboard client count card * cleanup jsdoc * add changelog * use helper to set start time * update component tests * update overview test * update util tests * throw error instead, add comment to util file * fix accidentally removed type from import * remove typo arg from test component * rename token stat getter to avoid future typos
1 parent e7778e2 commit 6d0e4f6

File tree

15 files changed

+171
-116
lines changed

15 files changed

+171
-116
lines changed

changelog/26729.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
ui (enterprise): Update dashboard to make activity log query using the same start time as the metrics overview
3+
```

ui/app/components/clients/page/token.hbs

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<StatText
1616
@label="Total clients"
1717
@subText="The total number of entity and non-entity clients for this date range."
18-
@value={{this.totalUsageCounts.clients}}
18+
@value={{this.tokenStats.total}}
1919
@tooltipText="This number is the total for the queried date range. The chart displays a monthly breakdown of total clients per month."
2020
@size="l"
2121
/>
@@ -100,21 +100,21 @@
100100
<StatText
101101
class="column"
102102
@label="Total clients"
103-
@value={{this.tokenUsageCounts.clients}}
103+
@value={{this.tokenStats.total}}
104104
@size="l"
105105
@subText="The number of clients which interacted with Vault during this month. This is Vault’s primary billing metric."
106106
/>
107107
<StatText
108108
class="column"
109109
@label="Entity"
110-
@value={{this.tokenUsageCounts.entity_clients}}
110+
@value={{this.tokenStats.entity_clients}}
111111
@size="l"
112112
@subText="Representations of a particular user, client, or application that created a token via login."
113113
/>
114114
<StatText
115115
class="column"
116116
@label="Non-entity"
117-
@value={{this.tokenUsageCounts.non_entity_clients}}
117+
@value={{this.tokenStats.non_entity_clients}}
118118
@size="l"
119119
@subText="Clients created with a shared set of permissions, but not associated with an entity."
120120
/>

ui/app/components/clients/page/token.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ export default class ClientsTokenPageComponent extends ActivityComponent {
3737
return this.calculateClientAverages(this.byMonthNewClients);
3838
}
3939

40-
get tokenUsageCounts() {
40+
get tokenStats() {
4141
if (this.totalUsageCounts) {
4242
const { entity_clients, non_entity_clients } = this.totalUsageCounts;
4343
return {
44-
clients: entity_clients + non_entity_clients,
44+
total: entity_clients + non_entity_clients,
4545
entity_clients,
4646
non_entity_clients,
4747
};

ui/app/components/dashboard/client-count-card.hbs

+3-18
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,8 @@
2323
<VaultLogoSpinner />
2424
{{else}}
2525
<div class="is-grid grid-2-columns grid-gap-2 has-top-margin-m grid-align-items-start is-flex-v-centered">
26-
<StatText
27-
@label="Total"
28-
@value={{this.activityData.total.clients}}
29-
@size="l"
30-
@subText="The number of clients in this billing period ({{date-format
31-
this.licenseStartTime
32-
'MMM yyyy'
33-
}} - {{date-format this.updatedAt 'MMM yyyy'}})."
34-
data-test-stat-text="total-clients"
35-
/>
36-
<StatText
37-
@label="New"
38-
@value={{this.currentMonthActivityTotalCount}}
39-
@size="l"
40-
@subText="The number of clients new to Vault in the current month."
41-
data-test-stat-text="new-clients"
42-
/>
26+
<StatText @label="Total" @value={{this.activityData.total.clients}} @size="l" @subText={{this.statSubText.total}} />
27+
<StatText @label="New" @value={{this.currentMonthActivityTotalCount}} @size="l" @subText={{this.statSubText.new}} />
4328
</div>
4429

4530
<div class="has-top-margin-l is-flex-center">
@@ -55,7 +40,7 @@
5540
/>
5641
<small class="has-left-margin-xs has-text-grey">
5742
Updated
58-
{{date-format this.updatedAt "MMM dd, yyyy hh:mm:ss"}}
43+
{{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
5944
</small>
6045
</div>
6146
{{/if}}

ui/app/components/dashboard/client-count-card.js

+25-9
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,66 @@
44
*/
55

66
import Component from '@glimmer/component';
7-
import getStorage from 'vault/lib/token-storage';
87
import timestamp from 'core/utils/timestamp';
98
import { task } from 'ember-concurrency';
109
import { waitFor } from '@ember/test-waiters';
1110
import { tracked } from '@glimmer/tracking';
1211
import { service } from '@ember/service';
12+
import { setStartTimeQuery } from 'core/utils/client-count-utils';
13+
import { dateFormat } from 'core/helpers/date-format';
1314

1415
/**
1516
* @module DashboardClientCountCard
1617
* DashboardClientCountCard component are used to display total and new client count information
1718
*
1819
* @example
19-
* ```js
20-
* <Dashboard::ClientCountCard @license={{@model.license}} />
21-
* ```
22-
* @param {object} license - license object passed from the parent
20+
*
21+
* <Dashboard::ClientCountCard @isEnterprise={{@version.isEnterprise}} />
22+
*
23+
* @param {boolean} isEnterprise - used for setting the start time for the activity log query
2324
*/
2425

2526
export default class DashboardClientCountCard extends Component {
2627
@service store;
2728

29+
clientConfig = null;
30+
licenseStartTime = null;
2831
@tracked activityData = null;
29-
@tracked clientConfig = null;
3032
@tracked updatedAt = timestamp.now().toISOString();
3133

3234
constructor() {
3335
super(...arguments);
3436
this.fetchClientActivity.perform();
35-
this.clientConfig = this.store.queryRecord('clients/config', {}).catch(() => {});
3637
}
3738

3839
get currentMonthActivityTotalCount() {
3940
return this.activityData?.byMonth?.lastObject?.new_clients.clients;
4041
}
4142

42-
get licenseStartTime() {
43-
return this.args.license.startTime || getStorage().getItem('vault:ui-inputted-start-date') || null;
43+
get statSubText() {
44+
const format = (date) => dateFormat([date, 'MMM yyyy'], {});
45+
return this.licenseStartTime
46+
? {
47+
total: `The number of clients in this billing period (${format(this.licenseStartTime)} - ${format(
48+
this.updatedAt
49+
)}).`,
50+
new: 'The number of clients new to Vault in the current month.',
51+
}
52+
: { total: 'No total client data available.', new: 'No new client data available.' };
4453
}
4554

4655
@task
4756
@waitFor
4857
*fetchClientActivity(e) {
4958
if (e) e.preventDefault();
5059
this.updatedAt = timestamp.now().toISOString();
60+
61+
if (!this.clientConfig) {
62+
// set config and license start time when component initializes
63+
this.clientConfig = yield this.store.queryRecord('clients/config', {}).catch(() => {});
64+
this.licenseStartTime = setStartTimeQuery(this.args.isEnterprise, this.clientConfig);
65+
}
66+
5167
// only make the network request if we have a start_time
5268
if (!this.licenseStartTime) return {};
5369
try {

ui/app/components/dashboard/overview.hbs

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77

88
<div class="has-bottom-margin-xl">
99
<div class="is-flex-row gap-24">
10-
{{#if (and @version.isEnterprise (or @license @isRootNamespace))}}
10+
{{#if (and @version.isEnterprise @isRootNamespace)}}
1111
<div class="is-flex-column is-flex-1 gap-24">
12-
{{#if @license}}
13-
<Dashboard::ClientCountCard @license={{@license}} />
14-
{{/if}}
12+
<Dashboard::ClientCountCard @isEnterprise={{@version.isEnterprise}} />
1513
{{#if
1614
(and @isRootNamespace (has-permission "status" routeParams="replication") (not (is-empty-value @replication)))
1715
}}

ui/app/routes/vault/cluster/clients/counts.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import type VersionService from 'vault/services/version';
1414

1515
import type { ModelFrom } from 'vault/vault/route';
1616
import type ClientsRoute from '../clients';
17-
import type ClientsConfigModel from 'vault/models/clients/config';
1817
import type ClientsActivityModel from 'vault/models/clients/activity';
1918
import type ClientsCountsController from 'vault/controllers/vault/cluster/clients/counts';
19+
import { setStartTimeQuery } from 'core/utils/client-count-utils';
20+
2021
export interface ClientsCountsRouteParams {
2122
start_time?: string | number | undefined;
2223
end_time?: string | number | undefined;
@@ -86,10 +87,7 @@ export default class ClientsCountsRoute extends Route {
8687
async model(params: ClientsCountsRouteParams) {
8788
const { config, versionHistory } = this.modelFor('vault.cluster.clients') as ModelFrom<ClientsRoute>;
8889
// only enterprise versions will have a relevant billing start date, if null users must select initial start time
89-
let startTime = null;
90-
if (this.version.isEnterprise && this._hasConfig(config)) {
91-
startTime = getUnixTime(config.billingStartTimestamp);
92-
}
90+
const startTime = setStartTimeQuery(this.version.isEnterprise, config);
9391

9492
const startTimestamp = Number(params.start_time) || startTime;
9593
const endTimestamp = Number(params.end_time) || getUnixTime(timestamp.now());
@@ -118,8 +116,4 @@ export default class ClientsCountsRoute extends Route {
118116
});
119117
}
120118
}
121-
122-
_hasConfig(model: ClientsConfigModel | object): model is ClientsConfigModel {
123-
return 'billingStartTimestamp' in model;
124-
}
125119
}

ui/app/routes/vault/cluster/dashboard.js

-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
4040
return hash({
4141
replication,
4242
secretsEngines: this.store.query('secret-engine', {}),
43-
license: this.store.queryRecord('license', {}).catch(() => null),
4443
isRootNamespace: this.namespace.inRootNamespace && !hasChroot,
4544
version: this.version,
4645
vaultConfiguration: hasChroot ? null : this.getVaultConfiguration(),

ui/app/templates/vault/cluster/dashboard.hbs

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<Dashboard::Overview
77
@replication={{this.model.replication}}
88
@secretsEngines={{this.model.secretsEngines}}
9-
@license={{this.model.license}}
109
@isRootNamespace={{this.model.isRootNamespace}}
1110
@version={{this.model.version}}
1211
@vaultConfiguration={{this.model.vaultConfiguration}}

ui/lib/core/addon/utils/client-count-utils.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { parseAPITimestamp } from 'core/utils/date-formatters';
77
import { compareAsc, getUnixTime, isWithinInterval } from 'date-fns';
88

9+
import type ClientsConfigModel from 'vault/models/clients/config';
910
import type ClientsVersionHistoryModel from 'vault/vault/models/clients/version-history';
1011

1112
/*
@@ -59,6 +60,18 @@ export const filterVersionHistory = (
5960
return [];
6061
};
6162

63+
export const setStartTimeQuery = (
64+
isEnterprise: boolean,
65+
config: ClientsConfigModel | Record<string, never>
66+
) => {
67+
// CE versions have no license and so the start time defaults to "0001-01-01T00:00:00Z"
68+
if (isEnterprise && _hasConfig(config)) {
69+
return getUnixTime(config.billingStartTimestamp);
70+
}
71+
return null;
72+
};
73+
74+
// METHODS FOR SERIALIZING ACTIVITY RESPONSE
6275
export const formatDateObject = (dateObj: { monthIdx: number; year: number }, isEnd: boolean) => {
6376
const { year, monthIdx } = dateObj;
6477
// day=0 for Date.UTC() returns the last day of the month before
@@ -188,6 +201,11 @@ export const namespaceArrayToObject = (
188201
};
189202

190203
// type guards for conditionals
204+
function _hasConfig(model: ClientsConfigModel | object): model is ClientsConfigModel {
205+
if (!model) return false;
206+
return 'billingStartTimestamp' in model;
207+
}
208+
191209
export function hasMountsKey(
192210
obj: ByMonthNewClients | NamespaceNewClients | MountNewClients
193211
): obj is NamespaceNewClients {
@@ -201,7 +219,6 @@ export function hasNamespacesKey(
201219
}
202220

203221
// TYPES RETURNED BY UTILS (serialized)
204-
205222
export interface TotalClients {
206223
clients: number;
207224
entity_clients: number;

ui/tests/acceptance/dashboard-test.js

+5-7
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,14 @@ module('Acceptance | landing page dashboard', function (hooks) {
404404
assert.dom(DASHBOARD.cardName('client-count')).exists();
405405
const response = await this.store.peekRecord('clients/activity', 'some-activity-id');
406406
assert.dom('[data-test-client-count-title]').hasText('Client count');
407-
assert.dom('[data-test-stat-text="total-clients"] .stat-label').hasText('Total');
407+
assert.dom('[data-test-stat-text="Total"] .stat-label').hasText('Total');
408+
assert.dom('[data-test-stat-text="Total"] .stat-value').hasText(formatNumber([response.total.clients]));
409+
assert.dom('[data-test-stat-text="New"] .stat-label').hasText('New');
408410
assert
409-
.dom('[data-test-stat-text="total-clients"] .stat-value')
410-
.hasText(formatNumber([response.total.clients]));
411-
assert.dom('[data-test-stat-text="new-clients"] .stat-label').hasText('New');
412-
assert
413-
.dom('[data-test-stat-text="new-clients"] .stat-text')
411+
.dom('[data-test-stat-text="New"] .stat-text')
414412
.hasText('The number of clients new to Vault in the current month.');
415413
assert
416-
.dom('[data-test-stat-text="new-clients"] .stat-value')
414+
.dom('[data-test-stat-text="New"] .stat-value')
417415
.hasText(formatNumber([response.byMonth.lastObject.new_clients.clients]));
418416
});
419417
});

ui/tests/integration/components/clients/page/token-test.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -69,21 +69,25 @@ module('Integration | Component | clients | Clients::Page::Token', function (hoo
6969

7070
test('it should render monthly total chart', async function (assert) {
7171
const count = this.activity.byMonth.length;
72-
assert.expect(count + 7);
72+
const { entity_clients, non_entity_clients } = this.activity.total;
73+
assert.expect(count + 8);
7374
const getAverage = (data) => {
7475
const average = ['entity_clients', 'non_entity_clients'].reduce((count, key) => {
7576
return (count += calculateAverage(data, key) || 0);
7677
}, 0);
7778
return formatNumber([average]);
7879
};
79-
const expectedTotal = getAverage(this.activity.byMonth);
80+
const expectedAvg = getAverage(this.activity.byMonth);
81+
const expectedTotal = formatNumber([entity_clients + non_entity_clients]);
8082
const chart = CHARTS.container('Entity/Non-entity clients usage');
81-
8283
await this.renderComponent();
8384

8485
assert
85-
.dom(CLIENT_COUNT.statTextValue('Average total clients per month'))
86+
.dom(CLIENT_COUNT.statTextValue('Total clients'))
8687
.hasText(expectedTotal, 'renders correct total clients');
88+
assert
89+
.dom(CLIENT_COUNT.statTextValue('Average total clients per month'))
90+
.hasText(expectedAvg, 'renders correct average clients');
8791

8892
// assert bar chart is correct
8993
assert.dom(`${chart} ${CHARTS.xAxis}`).hasText('7/23 8/23 9/23 10/23 11/23 12/23 1/24');

0 commit comments

Comments
 (0)