UI: remove current_billing_period from dashboard activity log request (#27559)

* remove current_billing_period from dashboard request

* add changelog

* remove timestamp from assertion

* update mirage
This commit is contained in:
claire bontempo
2024-06-21 11:06:53 -07:00
committed by GitHub
parent 5f078e2d39
commit 6f00ce45d2
10 changed files with 169 additions and 36 deletions

3
changelog/27559.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: Remove deprecated `current_billing_period` from dashboard activity log request
```

View File

@@ -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`;
}
}

View File

@@ -3,13 +3,13 @@
SPDX-License-Identifier: BUSL-1.1
~}}
{{#if (eq @config.enabled "On")}}
{{#if (or @config.reportingEnabled (eq @config.enabled "On"))}}
<EmptyState
@title="No data received {{if @dateRangeMessage @dateRangeMessage}}"
@message="Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes."
class="is-shadowless"
/>
{{else}}
{{else if @config}}
<EmptyState
@title="Data tracking is disabled"
@message="Tracking is disabled, and no data is being collected. To turn it on, edit the configuration."
@@ -24,4 +24,10 @@
/>
{{/if}}
</EmptyState>
{{else}}
<EmptyState
@title="Activity configuration data is unavailable"
@message="Reporting status is unknown and could be enabled or disabled. Check the Vault logs for more information."
class="is-shadowless"
/>
{{/if}}

View File

@@ -14,10 +14,10 @@
<hr class="has-background-gray-100" />
{{#if this.hasActivity}}
{{#if this.fetchClientActivity.isRunning}}
<VaultLogoSpinner />
{{else}}
{{#if this.fetchClientActivity.isRunning}}
<VaultLogoSpinner />
{{else}}
{{#if this.activityData}}
<div class="is-grid grid-2-columns grid-gap-2 has-top-margin-m grid-align-items-start is-flex-v-centered">
<StatText @label="Total" @value={{this.activityData.total.clients}} @size="l" @subText={{this.statSubText.total}} />
<StatText @label="New" @value={{this.currentMonthActivityTotalCount}} @size="l" @subText={{this.statSubText.new}} />
@@ -34,15 +34,13 @@
{{on "click" (perform this.fetchClientActivity)}}
data-test-refresh
/>
<small class="has-left-margin-xs has-text-grey">
<small class="has-left-margin-xs has-text-grey" data-test-updated-timestamp>
Updated
{{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
</small>
</div>
{{else}}
<Clients::NoData @config={{this.activityConfig}} />
{{/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 }}
<Clients::NoData @config={{hash enabled="On"}} />
{{/if}}
</Hds::Card::Container>

View File

@@ -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;
}
}

View File

@@ -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 hasnt opted-out of automated license reporting
@attr('boolean') reportingEnabled;
@attr('date') billingStartTimestamp;

View File

@@ -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();
}

View File

@@ -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]));

View File

@@ -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`<Clients::NoData @config={{this.config}} />`);
};
});
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.'
);
});
});

View File

@@ -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`<Dashboard::ClientCountCard />`);
@@ -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`<Dashboard::ClientCountCard />`);
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`<Dashboard::ClientCountCard />`);
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.'
);
});
});