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 ApplicationAdapter from '../application';
import { formatDateObject } from 'core/utils/client-count-utils'; import { formatDateObject } from 'core/utils/client-count-utils';
import { debug } from '@ember/debug';
export default class ActivityAdapter extends ApplicationAdapter { export default class ActivityAdapter extends ApplicationAdapter {
// javascript localizes new Date() objects but all activity log data is stored in UTC // 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 SPDX-License-Identifier: BUSL-1.1
~}} ~}}
{{#if (eq @config.enabled "On")}} {{#if (or @config.reportingEnabled (eq @config.enabled "On"))}}
<EmptyState <EmptyState
@title="No data received {{if @dateRangeMessage @dateRangeMessage}}" @title="No data received {{if @dateRangeMessage @dateRangeMessage}}"
@message="Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes." @message="Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes."
class="is-shadowless" class="is-shadowless"
/> />
{{else}} {{else if @config}}
<EmptyState <EmptyState
@title="Data tracking is disabled" @title="Data tracking is disabled"
@message="Tracking is disabled, and no data is being collected. To turn it on, edit the configuration." @message="Tracking is disabled, and no data is being collected. To turn it on, edit the configuration."
@@ -24,4 +24,10 @@
/> />
{{/if}} {{/if}}
</EmptyState> </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}} {{/if}}

View File

@@ -14,10 +14,10 @@
<hr class="has-background-gray-100" /> <hr class="has-background-gray-100" />
{{#if this.hasActivity}}
{{#if this.fetchClientActivity.isRunning}} {{#if this.fetchClientActivity.isRunning}}
<VaultLogoSpinner /> <VaultLogoSpinner />
{{else}} {{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"> <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="Total" @value={{this.activityData.total.clients}} @size="l" @subText={{this.statSubText.total}} />
<StatText @label="New" @value={{this.currentMonthActivityTotalCount}} @size="l" @subText={{this.statSubText.new}} /> <StatText @label="New" @value={{this.currentMonthActivityTotalCount}} @size="l" @subText={{this.statSubText.new}} />
@@ -34,15 +34,13 @@
{{on "click" (perform this.fetchClientActivity)}} {{on "click" (perform this.fetchClientActivity)}}
data-test-refresh 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 Updated
{{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}} {{date-format this.updatedAt "MMM d yyyy, h:mm:ss aaa" withTimeZone=true}}
</small> </small>
</div> </div>
{{/if}}
{{else}} {{else}}
{{! This will likely never show since the clients activity api has changed to always return data. In the past it <Clients::NoData @config={{this.activityConfig}} />
would return no activity data. Adding this empty state here to match the current client count behavior }} {{/if}}
<Clients::NoData @config={{hash enabled="On"}} />
{{/if}} {{/if}}
</Hds::Card::Container> </Hds::Card::Container>

View File

@@ -23,7 +23,7 @@ export default class DashboardClientCountCard extends Component {
@service store; @service store;
@tracked activityData = null; @tracked activityData = null;
@tracked hasActivity = false; @tracked activityConfig = null;
@tracked updatedAt = null; @tracked updatedAt = null;
constructor() { constructor() {
@@ -53,11 +53,10 @@ export default class DashboardClientCountCard extends Component {
this.updatedAt = timestamp.now().toISOString(); this.updatedAt = timestamp.now().toISOString();
try { try {
this.activityData = yield this.store.queryRecord('clients/activity', { this.activityData = yield this.store.findRecord('clients/activity', 'clients/activity');
current_billing_period: true,
});
this.hasActivity = this.activityData.id === 'no-data' ? false : true;
} catch (error) { } 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; this.error = error;
} }
} }

View File

@@ -35,8 +35,10 @@ export default class ClientsConfigModel extends Model {
@attr('number') minimumRetentionMonths; @attr('number') minimumRetentionMonths;
// refers specifically to the activitylog and will always be on for enterprise
@attr('string') enabled; @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('boolean') reportingEnabled;
@attr('date') billingStartTimestamp; @attr('date') billingStartTimestamp;

View File

@@ -215,9 +215,9 @@ export default function (server) {
server.get('/sys/internal/counters/activity', (schema, req) => { server.get('/sys/internal/counters/activity', (schema, req) => {
let { start_time, end_time } = req.queryParams; let { start_time, end_time } = req.queryParams;
if (req.queryParams.current_billing_period) { if (!start_time && !end_time) {
// { current_billing_period: true } automatically queries the activity log // if there are no date query params, the activity log default behavior
// from the builtin license start timestamp to the current month // queries from the builtin license start timestamp to the current month
start_time = LICENSE_START.toISOString(); start_time = LICENSE_START.toISOString();
end_time = STATIC_NOW.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.true(version.isEnterprise, 'version is enterprise');
assert.strictEqual(currentURL(), '/vault/dashboard'); assert.strictEqual(currentURL(), '/vault/dashboard');
assert.dom(DASHBOARD.cardName('client-count')).exists(); 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-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-label').hasText('Total');
assert.dom('[data-test-stat-text="Total"] .stat-value').hasText(formatNumber([response.total.clients])); 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 { render, click } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars'; import { hbs } from 'ember-cli-htmlbars';
import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupMirage } from 'ember-cli-mirage/test-support';
import { Response } from 'miragejs';
import sinon from 'sinon'; import sinon from 'sinon';
import { STATIC_NOW } from 'vault/mirage/handlers/clients'; import { STATIC_NOW } from 'vault/mirage/handlers/clients';
import timestamp from 'core/utils/timestamp'; import timestamp from 'core/utils/timestamp';
@@ -21,21 +22,14 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
setupMirage(hooks); setupMirage(hooks);
test('it should display client count information', async function (assert) { test('it should display client count information', async function (assert) {
sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW)); assert.expect(6);
assert.expect(5); sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW)); // 1/25/24
const { months, total } = ACTIVITY_RESPONSE_STUB; const { months, total } = ACTIVITY_RESPONSE_STUB;
const [latestMonth] = months.slice(-1); 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' // this assertion should be hit twice, once initially and then again clicking 'refresh'
assert.propEqual( assert.true(true, 'makes request to sys/internal/counters/activity');
req.queryParams, return { data: ACTIVITY_RESPONSE_STUB };
{ 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,
};
}); });
await render(hbs`<Dashboard::ClientCountCard />`); await render(hbs`<Dashboard::ClientCountCard />`);
@@ -54,6 +48,7 @@ module('Integration | Component | dashboard/client-count-card', function (hooks)
latestMonth.new_clients.counts.clients, latestMonth.new_clients.counts.clients,
])}` ])}`
); );
assert.dom('[data-test-updated-timestamp]').hasTextContaining('Updated Jan 25 2024');
// fires second request to /activity // fires second request to /activity
await click('[data-test-refresh]'); 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 // stubbing this unrealistic response just to test component subtext logic
this.server.get('sys/internal/counters/activity', () => { this.server.get('sys/internal/counters/activity', () => {
return { return {
request_id: 'some-activity-id',
data: { by_namespace: [], months: [], total: {} }, 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. -'); 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 // the activity response has changed and now should ALWAYS return something
// but adding this test until we update the adapter to reflect that // 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', () => { this.server.get('sys/internal/counters/activity', () => {
assert.true(true, 'makes request to sys/internal/counters/activity'); assert.true(true, 'makes request to sys/internal/counters/activity');
return { data: {} }; 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 />`); await render(hbs`<Dashboard::ClientCountCard />`);
assert.dom(GENERAL.emptyStateTitle).hasText('No data received'); assert.dom(GENERAL.emptyStateTitle).hasText('No data received');
assert assert
.dom(GENERAL.emptyStateMessage) .dom(GENERAL.emptyStateMessage)
.hasText('Tracking is turned on and Vault is gathering data. It should appear here within 30 minutes.'); .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.'
);
});
}); });