- {{#if (and @version.isEnterprise (or @license @isRootNamespace))}}
+ {{#if (and @version.isEnterprise @isRootNamespace)}}
- {{#if @license}}
-
- {{/if}}
+
{{#if
(and @isRootNamespace (has-permission "status" routeParams="replication") (not (is-empty-value @replication)))
}}
diff --git a/ui/app/routes/vault/cluster/clients/counts.ts b/ui/app/routes/vault/cluster/clients/counts.ts
index 54012bb6cdc4..e5be73f97e67 100644
--- a/ui/app/routes/vault/cluster/clients/counts.ts
+++ b/ui/app/routes/vault/cluster/clients/counts.ts
@@ -14,9 +14,10 @@ import type VersionService from 'vault/services/version';
import type { ModelFrom } from 'vault/vault/route';
import type ClientsRoute from '../clients';
-import type ClientsConfigModel from 'vault/models/clients/config';
import type ClientsActivityModel from 'vault/models/clients/activity';
import type ClientsCountsController from 'vault/controllers/vault/cluster/clients/counts';
+import { setStartTimeQuery } from 'core/utils/client-count-utils';
+
export interface ClientsCountsRouteParams {
start_time?: string | number | undefined;
end_time?: string | number | undefined;
@@ -86,10 +87,7 @@ export default class ClientsCountsRoute extends Route {
async model(params: ClientsCountsRouteParams) {
const { config, versionHistory } = this.modelFor('vault.cluster.clients') as ModelFrom;
// only enterprise versions will have a relevant billing start date, if null users must select initial start time
- let startTime = null;
- if (this.version.isEnterprise && this._hasConfig(config)) {
- startTime = getUnixTime(config.billingStartTimestamp);
- }
+ const startTime = setStartTimeQuery(this.version.isEnterprise, config);
const startTimestamp = Number(params.start_time) || startTime;
const endTimestamp = Number(params.end_time) || getUnixTime(timestamp.now());
@@ -118,8 +116,4 @@ export default class ClientsCountsRoute extends Route {
});
}
}
-
- _hasConfig(model: ClientsConfigModel | object): model is ClientsConfigModel {
- return 'billingStartTimestamp' in model;
- }
}
diff --git a/ui/app/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js
index a8af612f1720..c7871ab2cdda 100644
--- a/ui/app/routes/vault/cluster/dashboard.js
+++ b/ui/app/routes/vault/cluster/dashboard.js
@@ -40,7 +40,6 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
return hash({
replication,
secretsEngines: this.store.query('secret-engine', {}),
- license: this.store.queryRecord('license', {}).catch(() => null),
isRootNamespace: this.namespace.inRootNamespace && !hasChroot,
version: this.version,
vaultConfiguration: hasChroot ? null : this.getVaultConfiguration(),
diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs
index b7f212674a0d..52ce5adcf197 100644
--- a/ui/app/templates/vault/cluster/dashboard.hbs
+++ b/ui/app/templates/vault/cluster/dashboard.hbs
@@ -6,7 +6,6 @@
+) => {
+ // CE versions have no license and so the start time defaults to "0001-01-01T00:00:00Z"
+ if (isEnterprise && _hasConfig(config)) {
+ return getUnixTime(config.billingStartTimestamp);
+ }
+ return null;
+};
+
+// METHODS FOR SERIALIZING ACTIVITY RESPONSE
export const formatDateObject = (dateObj: { monthIdx: number; year: number }, isEnd: boolean) => {
const { year, monthIdx } = dateObj;
// day=0 for Date.UTC() returns the last day of the month before
@@ -188,6 +201,11 @@ export const namespaceArrayToObject = (
};
// type guards for conditionals
+function _hasConfig(model: ClientsConfigModel | object): model is ClientsConfigModel {
+ if (!model) return false;
+ return 'billingStartTimestamp' in model;
+}
+
export function hasMountsKey(
obj: ByMonthNewClients | NamespaceNewClients | MountNewClients
): obj is NamespaceNewClients {
@@ -201,7 +219,6 @@ export function hasNamespacesKey(
}
// TYPES RETURNED BY UTILS (serialized)
-
export interface TotalClients {
clients: number;
entity_clients: number;
diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js
index acdb8e984c20..e3753c4deea1 100644
--- a/ui/tests/acceptance/dashboard-test.js
+++ b/ui/tests/acceptance/dashboard-test.js
@@ -404,16 +404,14 @@ module('Acceptance | landing page dashboard', function (hooks) {
assert.dom(DASHBOARD.cardName('client-count')).exists();
const response = await this.store.peekRecord('clients/activity', 'some-activity-id');
assert.dom('[data-test-client-count-title]').hasText('Client count');
- assert.dom('[data-test-stat-text="total-clients"] .stat-label').hasText('Total');
+ assert.dom('[data-test-stat-text="Total"] .stat-label').hasText('Total');
+ assert.dom('[data-test-stat-text="Total"] .stat-value').hasText(formatNumber([response.total.clients]));
+ assert.dom('[data-test-stat-text="New"] .stat-label').hasText('New');
assert
- .dom('[data-test-stat-text="total-clients"] .stat-value')
- .hasText(formatNumber([response.total.clients]));
- assert.dom('[data-test-stat-text="new-clients"] .stat-label').hasText('New');
- assert
- .dom('[data-test-stat-text="new-clients"] .stat-text')
+ .dom('[data-test-stat-text="New"] .stat-text')
.hasText('The number of clients new to Vault in the current month.');
assert
- .dom('[data-test-stat-text="new-clients"] .stat-value')
+ .dom('[data-test-stat-text="New"] .stat-value')
.hasText(formatNumber([response.byMonth.lastObject.new_clients.clients]));
});
});
diff --git a/ui/tests/integration/components/clients/page/token-test.js b/ui/tests/integration/components/clients/page/token-test.js
index a0553ac11f62..60e562282adf 100644
--- a/ui/tests/integration/components/clients/page/token-test.js
+++ b/ui/tests/integration/components/clients/page/token-test.js
@@ -69,21 +69,25 @@ 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;
- assert.expect(count + 7);
+ const { entity_clients, non_entity_clients } = this.activity.total;
+ assert.expect(count + 8);
const getAverage = (data) => {
const average = ['entity_clients', 'non_entity_clients'].reduce((count, key) => {
return (count += calculateAverage(data, key) || 0);
}, 0);
return formatNumber([average]);
};
- const expectedTotal = getAverage(this.activity.byMonth);
+ const expectedAvg = getAverage(this.activity.byMonth);
+ const expectedTotal = formatNumber([entity_clients + non_entity_clients]);
const chart = CHARTS.container('Entity/Non-entity clients usage');
-
await this.renderComponent();
assert
- .dom(CLIENT_COUNT.statTextValue('Average total clients per month'))
+ .dom(CLIENT_COUNT.statTextValue('Total clients'))
.hasText(expectedTotal, 'renders correct total clients');
+ assert
+ .dom(CLIENT_COUNT.statTextValue('Average total clients per month'))
+ .hasText(expectedAvg, 'renders correct average clients');
// 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');
diff --git a/ui/tests/integration/components/dashboard/client-count-card-test.js b/ui/tests/integration/components/dashboard/client-count-card-test.js
index e3e6445f5716..0cc75566de04 100644
--- a/ui/tests/integration/components/dashboard/client-count-card-test.js
+++ b/ui/tests/integration/components/dashboard/client-count-card-test.js
@@ -13,6 +13,7 @@ import { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
import timestamp from 'core/utils/timestamp';
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
import { formatNumber } from 'core/helpers/format-number';
+import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
module('Integration | Component | dashboard/client-count-card', function (hooks) {
setupRenderingTest(hooks);
@@ -22,18 +23,12 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
sinon.stub(timestamp, 'now').callsFake(() => STATIC_NOW);
});
- hooks.beforeEach(function () {
- this.license = {
- startTime: LICENSE_START.toISOString(),
- };
- });
-
hooks.after(function () {
timestamp.now.restore();
});
test('it should display client count information', async function (assert) {
- assert.expect(9);
+ assert.expect(6);
const { months, total } = ACTIVITY_RESPONSE_STUB;
const [latestMonth] = months.slice(-1);
this.server.get('sys/internal/counters/activity', () => {
@@ -44,24 +39,62 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
data: ACTIVITY_RESPONSE_STUB,
};
});
+ this.server.get('sys/internal/counters/config', function () {
+ assert.true(true, 'sys/internal/counters/config');
+ return {
+ request_id: 'some-config-id',
+ data: {
+ billing_start_timestamp: LICENSE_START.toISOString(),
+ },
+ };
+ });
- await render(hbs``);
+ await render(hbs``);
assert.dom('[data-test-client-count-title]').hasText('Client count');
- assert.dom('[data-test-stat-text="total-clients"] .stat-label').hasText('Total');
assert
- .dom('[data-test-stat-text="total-clients"] .stat-text')
- .hasText('The number of clients in this billing period (Jul 2023 - Jan 2024).');
- assert
- .dom('[data-test-stat-text="total-clients"] .stat-value')
- .hasText(`${formatNumber([total.clients])}`);
- assert.dom('[data-test-stat-text="new-clients"] .stat-label').hasText('New');
- assert
- .dom('[data-test-stat-text="new-clients"] .stat-text')
- .hasText('The number of clients new to Vault in the current month.');
+ .dom(CLIENT_COUNT.statText('Total'))
+ .hasText(
+ `Total The number of clients in this billing period (Jul 2023 - Jan 2024). ${formatNumber([
+ total.clients,
+ ])}`
+ );
+
assert
- .dom('[data-test-stat-text="new-clients"] .stat-value')
- .hasText(`${formatNumber([latestMonth.new_clients.counts.clients])}`);
+ .dom(CLIENT_COUNT.statText('New'))
+ .hasText(
+ `New The number of clients new to Vault in the current month. ${formatNumber([
+ latestMonth.new_clients.counts.clients,
+ ])}`
+ );
+
// fires second request to /activity
await click('[data-test-refresh]');
});
+
+ test('it does not query activity for community edition', async function (assert) {
+ assert.expect(3);
+ // in the template this component is wrapped in an isEnterprise conditional so this
+ // state is currently not possible, adding a test to safeguard against introducing
+ // regressions during future refactors
+ this.server.get(
+ 'sys/internal/counters/activity',
+ () => new Error('uh oh! a request was made to sys/internal/counters/activity')
+ );
+ this.server.get('sys/internal/counters/config', function () {
+ assert.true(true, 'sys/internal/counters/config');
+ return {
+ request_id: 'some-config-id',
+ data: {
+ billing_start_timestamp: '0001-01-01T00:00:00Z',
+ },
+ };
+ });
+
+ await render(hbs``);
+ assert.dom(CLIENT_COUNT.statText('Total')).hasText('Total No total client data available. -');
+ assert.dom(CLIENT_COUNT.statText('New')).hasText('New No new client data available. -');
+
+ // attempt second request to /activity but component task should return instead of hitting endpoint
+ await click('[data-test-refresh]');
+ });
});
diff --git a/ui/tests/integration/components/dashboard/overview-test.js b/ui/tests/integration/components/dashboard/overview-test.js
index 6670e4d2ea4a..a367e1c2813f 100644
--- a/ui/tests/integration/components/dashboard/overview-test.js
+++ b/ui/tests/integration/components/dashboard/overview-test.js
@@ -9,6 +9,7 @@ import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { DASHBOARD } from 'vault/tests/helpers/components/dashboard/dashboard-selectors';
+import { LICENSE_START } from 'vault/mirage/handlers/clients';
module('Integration | Component | dashboard/overview', function (hooks) {
setupRenderingTest(hooks);
@@ -60,6 +61,14 @@ module('Integration | Component | dashboard/overview', function (hooks) {
],
};
this.refreshModel = () => {};
+ this.server.get('sys/internal/counters/config', function () {
+ return {
+ request_id: 'some-config-id',
+ data: {
+ billing_start_timestamp: LICENSE_START.toISOString(),
+ },
+ };
+ });
});
test('it should show dashboard empty states', async function (assert) {
@@ -129,7 +138,6 @@ module('Integration | Component | dashboard/overview', function (hooks) {
@replication={{this.replication}}
@version={{this.version}}
@isRootNamespace={{true}}
- @license={{this.license}}
@refreshModel={{this.refreshModel}} />`
);
assert.dom(DASHBOARD.cardHeader('Vault version')).exists();
@@ -140,43 +148,11 @@ module('Integration | Component | dashboard/overview', function (hooks) {
assert.dom(DASHBOARD.cardName('client-count')).exists();
});
- test('it should hide client count on enterprise w/o license ', async function (assert) {
- this.version = this.owner.lookup('service:version');
- this.version.version = '1.13.1+ent';
- this.version.type = 'enterprise';
- this.isRootNamespace = true;
-
- await render(
- hbs`
- `
- );
-
- assert.dom(DASHBOARD.cardHeader('Vault version')).exists();
- assert.dom('[data-test-badge-namespace]').exists();
- assert.dom(DASHBOARD.cardName('secrets-engines')).exists();
- assert.dom(DASHBOARD.cardName('learn-more')).exists();
- assert.dom(DASHBOARD.cardName('quick-actions')).exists();
- assert.dom(DASHBOARD.cardName('configuration-details')).exists();
- assert.dom(DASHBOARD.cardName('client-count')).doesNotExist();
- });
-
test('it should hide replication on enterprise not on root namespace', async function (assert) {
this.version = this.owner.lookup('service:version');
this.version.version = '1.13.1+ent';
this.version.type = 'enterprise';
this.isRootNamespace = false;
- this.license = {
- autoloaded: {
- license_id: '7adbf1f4-56ef-35cd-3a6c-50ef2627865d',
- },
- };
await render(
hbs`
@@ -186,7 +162,6 @@ module('Integration | Component | dashboard/overview', function (hooks) {
@secretsEngines={{this.secretsEngines}}
@vaultConfiguration={{this.vaultConfiguration}}
@replication={{this.replication}}
- @license={{this.license}}
@refreshModel={{this.refreshModel}} />`
);
@@ -197,7 +172,7 @@ module('Integration | Component | dashboard/overview', function (hooks) {
assert.dom(DASHBOARD.cardName('quick-actions')).exists();
assert.dom(DASHBOARD.cardName('configuration-details')).exists();
assert.dom(DASHBOARD.cardName('replication')).doesNotExist();
- assert.dom(DASHBOARD.cardName('client-count')).exists();
+ assert.dom(DASHBOARD.cardName('client-count')).doesNotExist();
});
module('learn more card', function () {
@@ -238,7 +213,6 @@ module('Integration | Component | dashboard/overview', function (hooks) {