diff --git a/changelog/27559.txt b/changelog/27559.txt
new file mode 100644
index 000000000000..a9afccdc98d5
--- /dev/null
+++ b/changelog/27559.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: Remove deprecated `current_billing_period` from dashboard activity log request
+```
diff --git a/ui/app/adapters/clients/activity.js b/ui/app/adapters/clients/activity.js
index 67c4a7478695..3e9455228e94 100644
--- a/ui/app/adapters/clients/activity.js
+++ b/ui/app/adapters/clients/activity.js
@@ -5,6 +5,7 @@
import ApplicationAdapter from '../application';
import { formatDateObject } from 'core/utils/client-count-utils';
+import { debug } from '@ember/debug';
export default class ActivityAdapter extends ApplicationAdapter {
// javascript localizes new Date() objects but all activity log data is stored in UTC
@@ -33,4 +34,12 @@ export default class ActivityAdapter extends ApplicationAdapter {
});
}
}
+
+ urlForFindRecord(id) {
+ // debug reminder so model is stored in Ember data with the same id for consistency
+ if (id !== 'clients/activity') {
+ debug(`findRecord('clients/activity') should pass 'clients/activity' as the id, you passed: '${id}'`);
+ }
+ return `${this.buildURL()}/internal/counters/activity`;
+ }
}
diff --git a/ui/app/components/clients/no-data.hbs b/ui/app/components/clients/no-data.hbs
index b6fa080dcd8d..113c1e33c733 100644
--- a/ui/app/components/clients/no-data.hbs
+++ b/ui/app/components/clients/no-data.hbs
@@ -3,13 +3,13 @@
SPDX-License-Identifier: BUSL-1.1
~}}
-{{#if (eq @config.enabled "On")}}
+{{#if (or @config.reportingEnabled (eq @config.enabled "On"))}}
-{{else}}
+{{else if @config}}
{{/if}}
+{{else}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/app/components/dashboard/client-count-card.hbs b/ui/app/components/dashboard/client-count-card.hbs
index 2b9b4fa32026..b904566483ef 100644
--- a/ui/app/components/dashboard/client-count-card.hbs
+++ b/ui/app/components/dashboard/client-count-card.hbs
@@ -14,10 +14,10 @@
- {{#if this.hasActivity}}
- {{#if this.fetchClientActivity.isRunning}}
-
- {{else}}
+ {{#if this.fetchClientActivity.isRunning}}
+
+ {{else}}
+ {{#if this.activityData}}
@@ -34,15 +34,13 @@
{{on "click" (perform this.fetchClientActivity)}}
data-test-refresh
/>
-
+
Updated
{{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
+ {{else}}
+
{{/if}}
- {{else}}
- {{! This will likely never show since the clients activity api has changed to always return data. In the past it
- would return no activity data. Adding this empty state here to match the current client count behavior }}
-
{{/if}}
\ No newline at end of file
diff --git a/ui/app/components/dashboard/client-count-card.js b/ui/app/components/dashboard/client-count-card.js
index 51c2d174ddc4..6b613bfe53ad 100644
--- a/ui/app/components/dashboard/client-count-card.js
+++ b/ui/app/components/dashboard/client-count-card.js
@@ -23,7 +23,7 @@ export default class DashboardClientCountCard extends Component {
@service store;
@tracked activityData = null;
- @tracked hasActivity = false;
+ @tracked activityConfig = null;
@tracked updatedAt = null;
constructor() {
@@ -53,11 +53,10 @@ export default class DashboardClientCountCard extends Component {
this.updatedAt = timestamp.now().toISOString();
try {
- this.activityData = yield this.store.queryRecord('clients/activity', {
- current_billing_period: true,
- });
- this.hasActivity = this.activityData.id === 'no-data' ? false : true;
+ this.activityData = yield this.store.findRecord('clients/activity', 'clients/activity');
} catch (error) {
+ // used for rendering the "No data" empty state, swallow any errors requesting config data
+ this.activityConfig = yield this.store.queryRecord('clients/config', {}).catch(() => null);
this.error = error;
}
}
diff --git a/ui/app/models/clients/config.js b/ui/app/models/clients/config.js
index 69288e23fb76..26c1663a9740 100644
--- a/ui/app/models/clients/config.js
+++ b/ui/app/models/clients/config.js
@@ -35,8 +35,10 @@ export default class ClientsConfigModel extends Model {
@attr('number') minimumRetentionMonths;
+ // refers specifically to the activitylog and will always be on for enterprise
@attr('string') enabled;
+ // reporting_enabled is for automated reporting and only true of the customer hasn’t opted-out of automated license reporting
@attr('boolean') reportingEnabled;
@attr('date') billingStartTimestamp;
diff --git a/ui/mirage/handlers/clients.js b/ui/mirage/handlers/clients.js
index a8e08e81cba2..1ac7659833f1 100644
--- a/ui/mirage/handlers/clients.js
+++ b/ui/mirage/handlers/clients.js
@@ -215,9 +215,9 @@ export default function (server) {
server.get('/sys/internal/counters/activity', (schema, req) => {
let { start_time, end_time } = req.queryParams;
- if (req.queryParams.current_billing_period) {
- // { current_billing_period: true } automatically queries the activity log
- // from the builtin license start timestamp to the current month
+ if (!start_time && !end_time) {
+ // if there are no date query params, the activity log default behavior
+ // queries from the builtin license start timestamp to the current month
start_time = LICENSE_START.toISOString();
end_time = STATIC_NOW.toISOString();
}
diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js
index aec0a89ec7f4..d1119d4df25f 100644
--- a/ui/tests/acceptance/dashboard-test.js
+++ b/ui/tests/acceptance/dashboard-test.js
@@ -402,7 +402,7 @@ module('Acceptance | landing page dashboard', function (hooks) {
assert.true(version.isEnterprise, 'version is enterprise');
assert.strictEqual(currentURL(), '/vault/dashboard');
assert.dom(DASHBOARD.cardName('client-count')).exists();
- const response = await this.store.peekRecord('clients/activity', 'some-activity-id');
+ const response = await this.store.findRecord('clients/activity', 'clients/activity');
assert.dom('[data-test-client-count-title]').hasText('Client count');
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]));
diff --git a/ui/tests/integration/components/clients/no-data-test.js b/ui/tests/integration/components/clients/no-data-test.js
new file mode 100644
index 000000000000..7605d24d6a85
--- /dev/null
+++ b/ui/tests/integration/components/clients/no-data-test.js
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+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 { GENERAL } from 'vault/tests/helpers/general-selectors';
+import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
+
+module('Integration | Component | clients/no-data', function (hooks) {
+ setupRenderingTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ this.server.post('/sys/capabilities-self', allowAllCapabilitiesStub());
+ this.store = this.owner.lookup('service:store');
+ this.setConfig = async (data) => {
+ // the clients/config model does some funky serializing for the "enabled" param
+ // so stubbing the request here instead of just the model for additional coverage
+ this.server.get('sys/internal/counters/config', () => {
+ return {
+ request_id: '25a94b99-b49a-c4ac-cb7b-5ba0eb390a25',
+ data,
+ };
+ });
+ return this.store.queryRecord('clients/config', {});
+ };
+ this.renderComponent = async () => {
+ return render(hbs``);
+ };
+ });
+
+ test('it renders empty state when enabled is "on"', async function (assert) {
+ assert.expect(2);
+ const data = {
+ enabled: 'default-enabled',
+ reporting_enabled: false,
+ };
+ ``;
+ this.config = await this.setConfig(data);
+ await this.renderComponent();
+ assert.dom(GENERAL.emptyStateTitle).hasText('No data received');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.');
+ });
+
+ test('it renders empty state when reporting_enabled is true', async function (assert) {
+ assert.expect(2);
+ const data = {
+ enabled: 'default-disabled',
+ reporting_enabled: true,
+ };
+ this.config = await this.setConfig(data);
+ await this.renderComponent();
+ assert.dom(GENERAL.emptyStateTitle).hasText('No data received');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.');
+ });
+
+ test('it renders empty state when reporting is fully disabled', async function (assert) {
+ assert.expect(2);
+ const data = {
+ enabled: 'default-disabled',
+ reporting_enabled: false,
+ };
+ this.config = await this.setConfig(data);
+ await this.renderComponent();
+ assert.dom(GENERAL.emptyStateTitle).hasText('Data tracking is disabled');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText(
+ 'Tracking is disabled, and no data is being collected. To turn it on, edit the configuration.'
+ );
+ });
+
+ test('it renders empty state when config data is not available', async function (assert) {
+ assert.expect(2);
+ this.config = null;
+ await this.renderComponent();
+ assert.dom(GENERAL.emptyStateTitle).hasText('Activity configuration data is unavailable');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText(
+ 'Reporting status is unknown and could be enabled or disabled. Check the Vault logs for more information.'
+ );
+ });
+});
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 388de5097e20..1542018a2904 100644
--- a/ui/tests/integration/components/dashboard/client-count-card-test.js
+++ b/ui/tests/integration/components/dashboard/client-count-card-test.js
@@ -8,6 +8,7 @@ import { setupRenderingTest } from 'vault/tests/helpers';
import { render, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support';
+import { Response } from 'miragejs';
import sinon from 'sinon';
import { STATIC_NOW } from 'vault/mirage/handlers/clients';
import timestamp from 'core/utils/timestamp';
@@ -21,21 +22,14 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
setupMirage(hooks);
test('it should display client count information', async function (assert) {
- sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
- assert.expect(5);
+ assert.expect(6);
+ sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW)); // 1/25/24
const { months, total } = ACTIVITY_RESPONSE_STUB;
const [latestMonth] = months.slice(-1);
- this.server.get('sys/internal/counters/activity', (schema, req) => {
+ this.server.get('sys/internal/counters/activity', () => {
// this assertion should be hit twice, once initially and then again clicking 'refresh'
- assert.propEqual(
- req.queryParams,
- { current_billing_period: 'true' },
- 'it makes request to sys/internal/counters/activity with builtin license start time'
- );
- return {
- request_id: 'some-activity-id',
- data: ACTIVITY_RESPONSE_STUB,
- };
+ assert.true(true, 'makes request to sys/internal/counters/activity');
+ return { data: ACTIVITY_RESPONSE_STUB };
});
await render(hbs``);
@@ -54,6 +48,7 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
latestMonth.new_clients.counts.clients,
])}`
);
+ assert.dom('[data-test-updated-timestamp]').hasTextContaining('Updated Jan 25 2024');
// fires second request to /activity
await click('[data-test-refresh]');
@@ -65,7 +60,6 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
// stubbing this unrealistic response just to test component subtext logic
this.server.get('sys/internal/counters/activity', () => {
return {
- request_id: 'some-activity-id',
data: { by_namespace: [], months: [], total: {} },
};
});
@@ -75,19 +69,48 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
assert.dom(CLIENT_COUNT.statText('New')).hasText('New No new client data available. -');
});
- test('it shows empty state if no activity data', async function (assert) {
+ test('it shows empty state if no activity data and reporting is enabled', async function (assert) {
// the activity response has changed and now should ALWAYS return something
// but adding this test until we update the adapter to reflect that
- assert.expect(3);
+ assert.expect(4);
this.server.get('sys/internal/counters/activity', () => {
assert.true(true, 'makes request to sys/internal/counters/activity');
return { data: {} };
});
-
+ this.server.get('sys/internal/counters/config', () => {
+ assert.true(true, 'makes request to sys/internal/counters/config');
+ return {
+ request_id: '25a94b99-b49a-c4ac-cb7b-5ba0eb390a25',
+ data: { reporting_enabled: true },
+ };
+ });
await render(hbs``);
assert.dom(GENERAL.emptyStateTitle).hasText('No data received');
assert
.dom(GENERAL.emptyStateMessage)
.hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.');
});
+
+ test('it shows empty state if no activity data and config data is unavailable', async function (assert) {
+ assert.expect(4);
+ this.server.get('sys/internal/counters/activity', () => {
+ assert.true(true, 'makes request to sys/internal/counters/activity');
+ return { data: {} };
+ });
+ this.server.get('sys/internal/counters/config', () => {
+ assert.true(true, 'makes request to sys/internal/counters/config');
+ return new Response(
+ 403,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ errors: ['permission denied'] })
+ );
+ });
+ await render(hbs``);
+ assert.dom(GENERAL.emptyStateTitle).hasText('Activity configuration data is unavailable');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText(
+ 'Reporting status is unknown and could be enabled or disabled. Check the Vault logs for more information.'
+ );
+ });
});