From 266ea693ccdec27718c17b7ee354cc6e19d9f305 Mon Sep 17 00:00:00 2001
From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
Date: Wed, 31 Jul 2024 12:35:11 -0500
Subject: [PATCH] UI: remove initial date from client counts (#27816)
---
changelog/27816.txt | 3 +
ui/app/adapters/clients/activity.js | 41 ++++---
ui/app/components/clients/activity.ts | 14 +--
ui/app/components/clients/date-range.hbs | 4 +-
ui/app/components/clients/date-range.ts | 4 +-
ui/app/components/clients/page/counts.hbs | 4 +-
ui/app/components/clients/page/counts.ts | 30 ++---
ui/app/components/clients/page/overview.hbs | 4 +-
ui/app/routes/vault/cluster/clients/counts.ts | 70 +++++++----
ui/lib/core/addon/utils/client-count-utils.ts | 11 --
ui/tests/acceptance/clients/counts-test.js | 86 ++++++++++++-
.../clients/counts/overview-test.js | 2 +-
.../helpers/clients/client-count-selectors.ts | 1 -
.../components/clients/date-range-test.js | 45 ++++++-
.../components/clients/page/counts-test.js | 115 ++++++++++++------
.../utils/client-count-utils-test.js | 35 ------
.../unit/adapters/clients-activity-test.js | 30 +++--
17 files changed, 324 insertions(+), 175 deletions(-)
create mode 100644 changelog/27816.txt
diff --git a/changelog/27816.txt b/changelog/27816.txt
new file mode 100644
index 0000000000..92dd2d7bb9
--- /dev/null
+++ b/changelog/27816.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: remove initial start/end parameters on the activity call for client counts dashboard.
+```
\ No newline at end of file
diff --git a/ui/app/adapters/clients/activity.js b/ui/app/adapters/clients/activity.js
index 3e9455228e..419d951fab 100644
--- a/ui/app/adapters/clients/activity.js
+++ b/ui/app/adapters/clients/activity.js
@@ -8,31 +8,42 @@ import { formatDateObject } from 'core/utils/client-count-utils';
import { debug } from '@ember/debug';
export default class ActivityAdapter extends ApplicationAdapter {
+ formatTimeParam(dateObj, isEnd = false) {
+ let formatted;
+ if (dateObj) {
+ try {
+ const iso = dateObj.timestamp || formatDateObject(dateObj, isEnd);
+ formatted = iso;
+ } catch (e) {
+ // carry on
+ }
+ }
+ return formatted;
+ }
// javascript localizes new Date() objects but all activity log data is stored in UTC
// create date object from user's input using Date.UTC() then send to backend as unix
// time params from the backend are formatted as a zulu timestamp
formatQueryParams(queryParams) {
- if (queryParams?.current_billing_period) {
- // { current_billing_period: true } automatically queries the activity log
- // from the builtin license start timestamp to the current month
- return queryParams;
+ const query = {};
+ const start = this.formatTimeParam(queryParams?.start_time);
+ const end = this.formatTimeParam(queryParams?.end_time, true);
+ if (start) {
+ query.start_time = start;
}
- let { start_time, end_time } = queryParams;
- start_time = start_time.timestamp || formatDateObject(start_time);
- end_time = end_time.timestamp || formatDateObject(end_time, true);
- return { start_time, end_time };
+ if (end) {
+ query.end_time = end;
+ }
+ return query;
}
queryRecord(store, type, query) {
const url = `${this.buildURL()}/internal/counters/activity`;
const queryParams = this.formatQueryParams(query);
- if (queryParams) {
- return this.ajax(url, 'GET', { data: queryParams }).then((resp) => {
- const response = resp || {};
- response.id = response.request_id || 'no-data';
- return response;
- });
- }
+ return this.ajax(url, 'GET', { data: queryParams }).then((resp) => {
+ const response = resp || {};
+ response.id = response.request_id || 'no-data';
+ return response;
+ });
}
urlForFindRecord(id) {
diff --git a/ui/app/components/clients/activity.ts b/ui/app/components/clients/activity.ts
index f22134b94a..10a419b2e9 100644
--- a/ui/app/components/clients/activity.ts
+++ b/ui/app/components/clients/activity.ts
@@ -7,7 +7,7 @@
// contains getters that filter and extract data from activity model for use in charts
import Component from '@glimmer/component';
-import { isSameMonth, fromUnixTime } from 'date-fns';
+import { isSameMonth } from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { calculateAverage } from 'vault/utils/chart-helpers';
import { filterVersionHistory, hasMountsKey, hasNamespacesKey } from 'core/utils/client-count-utils';
@@ -24,8 +24,8 @@ import type {
interface Args {
activity: ClientsActivityModel;
versionHistory: ClientsVersionHistoryModel[];
- startTimestamp: number;
- endTimestamp: number;
+ startTimestamp: string;
+ endTimestamp: string;
namespace: string;
mountPath: string;
}
@@ -40,14 +40,6 @@ export default class ClientsActivityComponent extends Component {
return calculateAverage(data, key);
};
- get startTimeISO() {
- return fromUnixTime(this.args.startTimestamp).toISOString();
- }
-
- get endTimeISO() {
- return fromUnixTime(this.args.endTimestamp).toISOString();
- }
-
get byMonthActivityData() {
const { activity, namespace } = this.args;
return namespace ? this.filteredActivityByMonth : activity.byMonth;
diff --git a/ui/app/components/clients/date-range.hbs b/ui/app/components/clients/date-range.hbs
index 0d5ed63634..c652c9f912 100644
--- a/ui/app/components/clients/date-range.hbs
+++ b/ui/app/components/clients/date-range.hbs
@@ -27,7 +27,7 @@
@text="Set date range"
@icon="edit"
{{on "click" (fn (mut this.showEditModal) true)}}
- data-test-set-date-range
+ data-test-date-range-edit
/>
{{/if}}
@@ -87,7 +87,7 @@
data-test-date-range-validation
>{{this.validationError}}
{{/if}}
- {{#if this.useDefaultDates}}
+ {{#if (and this.version.isEnterprise this.useDefaultDates)}}
Dashboard will use the default date range from the API.
diff --git a/ui/app/components/clients/date-range.ts b/ui/app/components/clients/date-range.ts
index 4704a7c745..9e87e413cf 100644
--- a/ui/app/components/clients/date-range.ts
+++ b/ui/app/components/clients/date-range.ts
@@ -67,8 +67,8 @@ export default class ClientsDateRangeComponent extends Component {
}
get validationError() {
- if (this.useDefaultDates) {
- // this means we want to reset, which is fine
+ if (this.useDefaultDates && this.version.isEnterprise) {
+ // this means we want to reset, which is fine for ent only
return null;
}
if (!this.startDate || !this.endDate) {
diff --git a/ui/app/components/clients/page/counts.hbs b/ui/app/components/clients/page/counts.hbs
index bb369ced1f..38b8fd840c 100644
--- a/ui/app/components/clients/page/counts.hbs
+++ b/ui/app/components/clients/page/counts.hbs
@@ -19,8 +19,8 @@
diff --git a/ui/app/components/clients/page/counts.ts b/ui/app/components/clients/page/counts.ts
index b2cf003ddd..bd53145a54 100644
--- a/ui/app/components/clients/page/counts.ts
+++ b/ui/app/components/clients/page/counts.ts
@@ -6,7 +6,7 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { action } from '@ember/object';
-import { fromUnixTime, isSameMonth, isAfter } from 'date-fns';
+import { isSameMonth, isAfter } from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { filterVersionHistory } from 'core/utils/client-count-utils';
@@ -21,11 +21,11 @@ interface Args {
activity: ClientsActivityModel;
activityError?: AdapterError;
config: ClientsConfigModel;
- endTimestamp: number;
+ endTimestamp: string; // ISO format
mountPath: string;
namespace: string;
onFilterChange: CallableFunction;
- startTimestamp: number;
+ startTimestamp: string; // ISO format
versionHistory: ClientsVersionHistoryModel[];
}
@@ -34,29 +34,21 @@ export default class ClientsCountsPageComponent extends Component {
@service declare readonly version: VersionService;
@service declare readonly store: StoreService;
- get startTimestampISO() {
- return this.args.startTimestamp ? fromUnixTime(this.args.startTimestamp).toISOString() : null;
- }
-
- get endTimestampISO() {
- return this.args.endTimestamp ? fromUnixTime(this.args.endTimestamp).toISOString() : null;
- }
-
get formattedStartDate() {
- return this.startTimestampISO ? parseAPITimestamp(this.startTimestampISO, 'MMMM yyyy') : null;
+ return this.args.startTimestamp ? parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy') : null;
}
// returns text for empty state message if noActivityData
get dateRangeMessage() {
- if (this.startTimestampISO && this.endTimestampISO) {
+ if (this.args.startTimestamp && this.args.endTimestamp) {
const endMonth = isSameMonth(
- parseAPITimestamp(this.startTimestampISO) as Date,
- parseAPITimestamp(this.endTimestampISO) as Date
+ parseAPITimestamp(this.args.startTimestamp) as Date,
+ parseAPITimestamp(this.args.endTimestamp) as Date
)
? ''
- : `to ${parseAPITimestamp(this.endTimestampISO, 'MMMM yyyy')}`;
+ : `to ${parseAPITimestamp(this.args.endTimestamp, 'MMMM yyyy')}`;
// completes the message 'No data received from { dateRangeMessage }'
- return `from ${parseAPITimestamp(this.startTimestampISO, 'MMMM yyyy')} ${endMonth}`;
+ return `from ${parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy')} ${endMonth}`;
}
return null;
}
@@ -127,9 +119,9 @@ export default class ClientsCountsPageComponent extends Component {
// show banner if startTime returned from activity log (response) is after the queried startTime
const { activity, config } = this.args;
const activityStartDateObject = parseAPITimestamp(activity.startTime) as Date;
- const queryStartDateObject = parseAPITimestamp(this.startTimestampISO) as Date;
+ const queryStartDateObject = parseAPITimestamp(this.args.startTimestamp) as Date;
const isEnterprise =
- this.startTimestampISO === config.billingStartTimestamp?.toISOString() && this.version.isEnterprise;
+ this.args.startTimestamp === config.billingStartTimestamp?.toISOString() && this.version.isEnterprise;
const message = isEnterprise ? 'Your license start date is' : 'You requested data from';
if (
diff --git a/ui/app/components/clients/page/overview.hbs b/ui/app/components/clients/page/overview.hbs
index f069423df2..e8abfc6abf 100644
--- a/ui/app/components/clients/page/overview.hbs
+++ b/ui/app/components/clients/page/overview.hbs
@@ -21,8 +21,8 @@
@totalClientAttribution={{this.totalClientAttribution}}
@newClientAttribution={{this.newClientAttribution}}
@selectedNamespace={{@namespace}}
- @startTimestamp={{this.startTimeISO}}
- @endTimestamp={{this.endTimeISO}}
+ @startTimestamp={{@startTimestamp}}
+ @endTimestamp={{@endTimestamp}}
@responseTimestamp={{@activity.responseTimestamp}}
@isHistoricalMonth={{and (not this.isCurrentMonth) (not this.isDateRange)}}
@upgradesDuringActivity={{this.upgradesDuringActivity}}
diff --git a/ui/app/routes/vault/cluster/clients/counts.ts b/ui/app/routes/vault/cluster/clients/counts.ts
index b2322970db..4170d02c4f 100644
--- a/ui/app/routes/vault/cluster/clients/counts.ts
+++ b/ui/app/routes/vault/cluster/clients/counts.ts
@@ -5,8 +5,7 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
-import timestamp from 'core/utils/timestamp';
-import { getUnixTime } from 'date-fns';
+import { fromUnixTime } from 'date-fns';
import type FlagsService from 'vault/services/flags';
import type StoreService from 'vault/services/store';
@@ -14,7 +13,6 @@ import type VersionService from 'vault/services/version';
import type { ModelFrom } from 'vault/vault/route';
import type ClientsRoute from '../clients';
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;
@@ -39,38 +37,68 @@ export default class ClientsCountsRoute extends Route {
return this.flags.fetchActivatedFlags();
}
- async getActivity(start_time: number | null, end_time: number) {
+ /**
+ * This method returns the query param timestamp if it exists. If not, it returns the activity timestamp value instead.
+ */
+ paramOrResponseTimestamp(
+ qpMillisString: string | number | undefined,
+ activityTimeStamp: string | undefined
+ ) {
+ let timestamp: string | undefined;
+ const millis = Number(qpMillisString);
+ if (!isNaN(millis)) {
+ timestamp = fromUnixTime(millis).toISOString();
+ }
+ // fallback to activity timestamp only if there was no query param
+ if (!timestamp && activityTimeStamp) {
+ timestamp = activityTimeStamp;
+ }
+ return timestamp;
+ }
+
+ async getActivity(params: ClientsCountsRouteParams) {
let activity, activityError;
- // if there is no start_time we want the user to manually choose a date
- // in that case bypass the query so that the user isn't stuck viewing the activity error
- if (start_time) {
+ // if CE without start time we want to skip the activity call
+ // so that the user is forced to choose a date range
+ if (this.version.isEnterprise || params.start_time) {
+ const query = {
+ // start and end params are optional -- if not provided, will fallback to API default
+ start_time: this.formatTimeQuery(params?.start_time),
+ end_time: this.formatTimeQuery(params?.end_time),
+ };
try {
- activity = await this.store.queryRecord('clients/activity', {
- start_time: { timestamp: start_time },
- end_time: { timestamp: end_time },
- });
+ activity = await this.store.queryRecord('clients/activity', query);
} catch (error) {
activityError = error;
}
}
- return { activity, activityError };
+ return {
+ activity,
+ activityError,
+ };
+ }
+
+ // Takes the string URL param and formats it as the adapter expects it,
+ // if it exists and is valid
+ formatTimeQuery(param: string | number | undefined) {
+ let timeParam: { timestamp: number } | undefined;
+ const millis = Number(param);
+ if (!isNaN(millis)) {
+ timeParam = { timestamp: millis };
+ }
+ return timeParam;
}
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
- const startTime = setStartTimeQuery(this.version.isEnterprise, config);
-
- const startTimestamp = Number(params.start_time) || startTime;
- const endTimestamp = Number(params.end_time) || getUnixTime(timestamp.now());
- const { activity, activityError } = await this.getActivity(startTimestamp, endTimestamp);
-
+ const { activity, activityError } = await this.getActivity(params);
return {
activity,
activityError,
config,
- endTimestamp,
- startTimestamp,
+ // activity.startTime corresponds to first month with data, but we want first month returned or requested
+ startTimestamp: this.paramOrResponseTimestamp(params?.start_time, activity?.byMonth[0]?.timestamp),
+ endTimestamp: this.paramOrResponseTimestamp(params?.end_time, activity?.endTime),
versionHistory,
};
}
diff --git a/ui/lib/core/addon/utils/client-count-utils.ts b/ui/lib/core/addon/utils/client-count-utils.ts
index 3d20f540e6..50a4b5a9e7 100644
--- a/ui/lib/core/addon/utils/client-count-utils.ts
+++ b/ui/lib/core/addon/utils/client-count-utils.ts
@@ -66,17 +66,6 @@ export const filterVersionHistory = (
return [];
};
-export const setStartTimeQuery = (
- isEnterprise: boolean,
- config: ClientsConfigModel | Record
-) => {
- // 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;
diff --git a/ui/tests/acceptance/clients/counts-test.js b/ui/tests/acceptance/clients/counts-test.js
index 4c283f4b83..6db0217e10 100644
--- a/ui/tests/acceptance/clients/counts-test.js
+++ b/ui/tests/acceptance/clients/counts-test.js
@@ -43,19 +43,19 @@ module('Acceptance | clients | counts', function (hooks) {
test('it should persist filter query params between child routes', async function (assert) {
await visit('/vault/clients/counts/overview');
await click(CLIENT_COUNT.dateRange.edit);
- await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2020-03');
- await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2022-02');
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2023-03');
+ await fillIn(CLIENT_COUNT.dateRange.editDate('end'), '2023-10');
await click(GENERAL.saveButton);
assert.strictEqual(
currentURL(),
- '/vault/clients/counts/overview?end_time=1706659200&start_time=1643673600',
+ '/vault/clients/counts/overview?end_time=1698710400&start_time=1677628800',
'Start and end times added as query params'
);
await click(GENERAL.tab('token'));
assert.strictEqual(
currentURL(),
- '/vault/clients/counts/token?end_time=1706659200&start_time=1643673600',
+ '/vault/clients/counts/token?end_time=1698710400&start_time=1677628800',
'Start and end times persist through child route change'
);
@@ -81,4 +81,82 @@ module('Acceptance | clients | counts', function (hooks) {
'You must be granted permissions to view this page. Ask your administrator if you think you should have access to the /v1/sys/internal/counters/activity endpoint.'
);
});
+
+ test('it should use the first month timestamp from default response rather than response start_time', async function (assert) {
+ const getCounts = () => {
+ return {
+ acme_clients: 0,
+ clients: 0,
+ entity_clients: 0,
+ non_entity_clients: 0,
+ secret_syncs: 0,
+ distinct_entities: 0,
+ non_entity_tokens: 1,
+ };
+ };
+ // set to enterprise because when community the initial activity call is skipped
+ this.owner.lookup('service:version').type = 'enterprise';
+ this.server.get('/sys/internal/counters/activity', function () {
+ return {
+ request_id: 'some-activity-id',
+ data: {
+ start_time: '2023-04-01T00:00:00Z', // reflects the first month with data
+ end_time: '2023-04-30T00:00:00Z',
+ by_namespace: [],
+ months: [
+ {
+ timestamp: '2023-02-01T00:00:00Z',
+ counts: null,
+ namespaces: null,
+ new_clients: null,
+ },
+ {
+ timestamp: '2023-03-01T00:00:00Z',
+ counts: null,
+ namespaces: null,
+ new_clients: null,
+ },
+ {
+ timestamp: '2023-04-01T00:00:00Z',
+ counts: getCounts(),
+ namespaces: [
+ {
+ namespace_id: 'root',
+ namespace_path: '',
+ counts: getCounts(),
+ mounts: [
+ {
+ mount_path: 'auth/authid/0',
+ counts: getCounts(),
+ },
+ ],
+ },
+ ],
+ new_clients: {
+ counts: getCounts(),
+ namespaces: [
+ {
+ namespace_id: 'root',
+ namespace_path: '',
+ counts: getCounts(),
+ mounts: [
+ {
+ mount_path: 'auth/authid/0',
+ counts: getCounts(),
+ },
+ ],
+ },
+ ],
+ },
+ },
+ ],
+ total: getCounts(),
+ },
+ };
+ });
+ await visit('/vault/clients/counts/overview');
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('start')).hasText('February 2023');
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('end')).hasText('April 2023');
+ assert.dom(CLIENT_COUNT.counts.startDiscrepancy).exists();
+ });
});
diff --git a/ui/tests/acceptance/clients/counts/overview-test.js b/ui/tests/acceptance/clients/counts/overview-test.js
index 6e5ba2dedc..56d8ce5000 100644
--- a/ui/tests/acceptance/clients/counts/overview-test.js
+++ b/ui/tests/acceptance/clients/counts/overview-test.js
@@ -72,7 +72,7 @@ module('Acceptance | clients | overview', function (hooks) {
.exists('new client attribution has empty state');
assert
.dom(GENERAL.emptyStateSubtitle)
- .hasText('There are no new clients for this namespace during this time period. ');
+ .hasText('There are no new clients for this namespace during this time period.');
assert.dom(CHARTS.container('total-clients')).exists('total client attribution chart shows');
// reset to billing period
diff --git a/ui/tests/helpers/clients/client-count-selectors.ts b/ui/tests/helpers/clients/client-count-selectors.ts
index 53691cfabf..ef820a6860 100644
--- a/ui/tests/helpers/clients/client-count-selectors.ts
+++ b/ui/tests/helpers/clients/client-count-selectors.ts
@@ -14,7 +14,6 @@ export const CLIENT_COUNT = {
},
dateRange: {
dateDisplay: (name: string) => (name ? `[data-test-date-range="${name}"]` : '[data-test-date-range]'),
- set: '[data-test-set-date-range]',
edit: '[data-test-date-range-edit]',
editModal: '[data-test-date-range-edit-modal]',
editDate: (name: string) => `[data-test-date-edit="${name}"]`,
diff --git a/ui/tests/integration/components/clients/date-range-test.js b/ui/tests/integration/components/clients/date-range-test.js
index beec2fe491..2e3765d6a2 100644
--- a/ui/tests/integration/components/clients/date-range-test.js
+++ b/ui/tests/integration/components/clients/date-range-test.js
@@ -33,9 +33,11 @@ module('Integration | Component | clients/date-range', function (hooks) {
this.startTime = undefined;
await this.renderComponent();
- assert.dom(DATE_RANGE.set).exists();
+ assert.dom(DATE_RANGE.dateDisplay('start')).doesNotExist();
+ assert.dom(DATE_RANGE.dateDisplay('end')).doesNotExist();
+ assert.dom(DATE_RANGE.edit).hasText('Set date range');
- await click(DATE_RANGE.set);
+ await click(DATE_RANGE.edit);
assert.dom(DATE_RANGE.editModal).exists();
assert.dom(DATE_RANGE.editDate('start')).hasValue('');
await fillIn(DATE_RANGE.editDate('start'), '2018-01');
@@ -50,11 +52,13 @@ module('Integration | Component | clients/date-range', function (hooks) {
assert.dom(DATE_RANGE.editModal).doesNotExist('closes modal');
});
- test('it renders the date range passed and can reset it', async function (assert) {
+ test('it renders the date range passed and can reset it (ent)', async function (assert) {
+ this.owner.lookup('service:version').type = 'enterprise';
await this.renderComponent();
assert.dom(DATE_RANGE.dateDisplay('start')).hasText('January 2018');
assert.dom(DATE_RANGE.dateDisplay('end')).hasText('January 2019');
+ assert.dom(DATE_RANGE.edit).hasText('Edit');
await click(DATE_RANGE.edit);
assert.dom(DATE_RANGE.editModal).exists();
@@ -70,7 +74,30 @@ module('Integration | Component | clients/date-range', function (hooks) {
assert.deepEqual(this.onChange.args[0], [{ start_time: undefined, end_time: undefined }]);
});
+ test('it renders the date range passed and cannot reset it when community', async function (assert) {
+ this.owner.lookup('service:version').type = 'community';
+ await this.renderComponent();
+
+ assert.dom(DATE_RANGE.dateDisplay('start')).hasText('January 2018');
+ assert.dom(DATE_RANGE.dateDisplay('end')).hasText('January 2019');
+ assert.dom(DATE_RANGE.edit).hasText('Edit');
+
+ await click(DATE_RANGE.edit);
+ assert.dom(DATE_RANGE.editModal).exists();
+ assert.dom(DATE_RANGE.editDate('start')).hasValue('2018-01');
+ assert.dom(DATE_RANGE.editDate('end')).hasValue('2019-01');
+ assert.dom(DATE_RANGE.defaultRangeAlert).doesNotExist();
+
+ await click(DATE_RANGE.editDate('reset'));
+ assert.dom(DATE_RANGE.editDate('start')).hasValue('');
+ assert.dom(DATE_RANGE.editDate('end')).hasValue('');
+ assert.dom(DATE_RANGE.validation).hasText('You must supply both start and end dates.');
+ await click(GENERAL.saveButton);
+ assert.false(this.onChange.called);
+ });
+
test('it does not trigger onChange if date range invalid', async function (assert) {
+ this.owner.lookup('service:version').type = 'enterprise';
await this.renderComponent();
await click(DATE_RANGE.edit);
@@ -90,6 +117,18 @@ module('Integration | Component | clients/date-range', function (hooks) {
assert.dom(DATE_RANGE.editModal).doesNotExist();
});
+ test('it does not trigger onChange when reset and CE', async function (assert) {
+ this.owner.lookup('service:version').type = 'community';
+ await this.renderComponent();
+
+ await click(DATE_RANGE.edit);
+
+ await click(DATE_RANGE.reset);
+ assert.dom(DATE_RANGE.validation).hasText('You must supply both start and end dates.');
+ await click(GENERAL.saveButton);
+ assert.false(this.onChange.called);
+ });
+
test('it resets the tracked values on close', async function (assert) {
await this.renderComponent();
diff --git a/ui/tests/integration/components/clients/page/counts-test.js b/ui/tests/integration/components/clients/page/counts-test.js
index 4577d695c6..b2567cb826 100644
--- a/ui/tests/integration/components/clients/page/counts-test.js
+++ b/ui/tests/integration/components/clients/page/counts-test.js
@@ -9,7 +9,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { render, click, findAll, fillIn } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
-import { getUnixTime } from 'date-fns';
+import { fromUnixTime, getUnixTime } from 'date-fns';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
import { selectChoose } from 'ember-power-select/test-support';
@@ -19,6 +19,8 @@ import { allowAllCapabilitiesStub } from 'vault/tests/helpers/stubs';
const START_TIME = getUnixTime(LICENSE_START);
const END_TIME = getUnixTime(STATIC_NOW);
+const START_ISO = LICENSE_START.toISOString();
+const END_ISO = STATIC_NOW.toISOString();
module('Integration | Component | clients | Page::Counts', function (hooks) {
setupRenderingTest(hooks);
@@ -35,8 +37,8 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
};
this.activity = await this.store.queryRecord('clients/activity', activityQuery);
this.config = await this.store.queryRecord('clients/config', {});
- this.startTimestamp = START_TIME;
- this.endTimestamp = END_TIME;
+ this.startTimestamp = START_ISO;
+ this.endTimestamp = END_ISO;
this.versionHistory = [];
this.renderComponent = () =>
render(hbs`
@@ -94,42 +96,77 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
.hasText('Tracking is disabled', 'Config disabled alert renders');
});
- test('it should send correct values on start and end date change', async function (assert) {
- assert.expect(3);
- const jan23start = getUnixTime(new Date('2023-01-01T00:00:00Z'));
- const dec23end = getUnixTime(new Date('2023-12-31T00:00:00Z'));
- const jan24end = getUnixTime(new Date('2024-01-31T00:00:00Z'));
+ const jan23start = getUnixTime(new Date('2023-01-01T00:00:00Z'));
+ // license start is July 2, 2024 on date change it recalculates start to beginning of the month
+ const july23start = getUnixTime(new Date('2023-07-01T00:00:00Z'));
+ const dec23end = getUnixTime(new Date('2023-12-31T00:00:00Z'));
+ const jan24end = getUnixTime(new Date('2024-01-31T00:00:00Z'));
+ [
+ {
+ scenario: 'changing start only',
+ expected: { start_time: jan23start, end_time: jan24end },
+ editStart: '2023-01',
+ expectedStart: 'January 2023',
+ expectedEnd: 'January 2024',
+ },
+ {
+ scenario: 'changing end only',
+ expected: { start_time: july23start, end_time: dec23end },
+ editEnd: '2023-12',
+ expectedStart: 'July 2023',
+ expectedEnd: 'December 2023',
+ },
+ {
+ scenario: 'changing both',
+ expected: { start_time: jan23start, end_time: dec23end },
+ editStart: '2023-01',
+ editEnd: '2023-12',
+ expectedStart: 'January 2023',
+ expectedEnd: 'December 2023',
+ },
+ {
+ scenario: 'reset',
+ expected: { start_time: undefined, end_time: undefined },
+ reset: true,
+ expectedStart: 'July 2023',
+ expectedEnd: 'January 2024',
+ },
+ ].forEach((testCase) => {
+ test(`it should send correct millis value on filter change when ${testCase.scenario}`, async function (assert) {
+ assert.expect(5);
+ // set to enterprise so reset will save correctly
+ this.owner.lookup('service:version').type = 'enterprise';
+ this.onFilterChange = (params) => {
+ assert.deepEqual(params, testCase.expected, 'Correct values sent on filter change');
+ // in the app, the timestamp choices trigger a qp refresh as millis from epoch,
+ // but in the model they are translated from millis to ISO timestamps before being
+ // passed to this component. Mock that behavior here.
+ this.set(
+ 'startTimestamp',
+ params?.start_time ? fromUnixTime(params.start_time).toISOString() : START_ISO
+ );
+ this.set('endTimestamp', params?.end_time ? fromUnixTime(params.end_time).toISOString() : END_ISO);
+ };
+ await this.renderComponent();
+ await click(CLIENT_COUNT.dateRange.edit);
- const expected = { start_time: START_TIME, end_time: END_TIME };
- this.onFilterChange = (params) => {
- assert.deepEqual(params, expected, 'Correct values sent on filter change');
- this.set('startTimestamp', params.start_time || START_TIME);
- this.set('endTimestamp', params.end_time || END_TIME);
- };
- // page starts with default billing dates, which are july 23 - jan 24
- await this.renderComponent();
+ // page starts with default billing dates, which are july 23 - jan 24
+ assert.dom(CLIENT_COUNT.dateRange.editDate('start')).hasValue('2023-07');
+ assert.dom(CLIENT_COUNT.dateRange.editDate('end')).hasValue('2024-01');
- // First, change only the start date
- expected.start_time = jan23start;
- // the end date which is first set to STATIC_NOW gets recalculated
- // to the end of given month/year on date range change
- expected.end_time = jan24end;
- await click(CLIENT_COUNT.dateRange.edit);
- await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2023-01');
- await click(GENERAL.saveButton);
-
- // Then change only the end date
- expected.end_time = dec23end;
- await click(CLIENT_COUNT.dateRange.edit);
- await fillIn(CLIENT_COUNT.dateRange.editDate('end'), '2023-12');
- await click(GENERAL.saveButton);
-
- // Then reset to billing which should reset the params
- expected.start_time = undefined;
- expected.end_time = undefined;
- await click(CLIENT_COUNT.dateRange.edit);
- await click(CLIENT_COUNT.dateRange.reset);
- await click(GENERAL.saveButton);
+ if (testCase.editStart) {
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), testCase.editStart);
+ }
+ if (testCase.editEnd) {
+ await fillIn(CLIENT_COUNT.dateRange.editDate('end'), testCase.editEnd);
+ }
+ if (testCase.reset) {
+ await click(CLIENT_COUNT.dateRange.reset);
+ }
+ await click(GENERAL.saveButton);
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('start')).hasText(testCase.expectedStart);
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('end')).hasText(testCase.expectedEnd);
+ });
});
test('it should render namespace and auth mount filters', async function (assert) {
@@ -168,7 +205,7 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
});
test('it should render start time discrepancy alert', async function (assert) {
- this.startTimestamp = getUnixTime(new Date('2022-06-01T00:00:00Z'));
+ this.startTimestamp = new Date('2022-06-01T00:00:00Z').toISOString();
await this.renderComponent();
@@ -236,7 +273,7 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
await this.renderComponent();
assert.dom(GENERAL.emptyStateTitle).hasText('No start date found', 'Empty state renders');
- assert.dom(CLIENT_COUNT.dateRange.set).exists();
+ assert.dom(CLIENT_COUNT.dateRange.edit).hasText('Set date range');
});
test('it should render catch all empty state', async function (assert) {
diff --git a/ui/tests/integration/utils/client-count-utils-test.js b/ui/tests/integration/utils/client-count-utils-test.js
index 7465297726..b783d7a345 100644
--- a/ui/tests/integration/utils/client-count-utils-test.js
+++ b/ui/tests/integration/utils/client-count-utils-test.js
@@ -13,7 +13,6 @@ import {
destructureClientCounts,
namespaceArrayToObject,
sortMonthsByTimestamp,
- setStartTimeQuery,
} from 'core/utils/client-count-utils';
import clientsHandler from 'vault/mirage/handlers/clients';
import {
@@ -28,9 +27,6 @@ used to normalize the sys/counters/activity response in the clients/activity
serializer. these functions are tested individually here, instead of all at once
in a serializer test for easier debugging
*/
-
-// TODO refactor tests into a module for each util method, then make each assertion its separate tests
-
module('Integration | Util | client count utils', function (hooks) {
setupTest(hooks);
@@ -439,35 +435,4 @@ module('Integration | Util | client count utils', function (hooks) {
'it formats combined data for monthly namespaces_by_key spanning upgrade to 1.10'
);
});
-
- test('setStartTimeQuery: it returns start time query for activity log', async function (assert) {
- assert.expect(6);
- const apiPath = 'sys/internal/counters/config';
- assert.strictEqual(setStartTimeQuery(true, {}), null, `it returns null if no permission to ${apiPath}`);
- assert.strictEqual(
- setStartTimeQuery(false, {}),
- null,
- `it returns null for community edition and no permission to ${apiPath}`
- );
- assert.strictEqual(
- setStartTimeQuery(true, { billingStartTimestamp: new Date('2022-06-08T00:00:00Z') }),
- 1654646400,
- 'it returns unix time if enterprise and billing_start_timestamp exists'
- );
- assert.strictEqual(
- setStartTimeQuery(false, { billingStartTimestamp: new Date('0001-01-01T00:00:00Z') }),
- null,
- 'it returns null time for community edition even if billing_start_timestamp exists'
- );
- assert.strictEqual(
- setStartTimeQuery(false, { foo: 'bar' }),
- null,
- 'it returns null if billing_start_timestamp key does not exist'
- );
- assert.strictEqual(
- setStartTimeQuery(false, undefined),
- null,
- 'fails gracefully if no config model is passed'
- );
- });
});
diff --git a/ui/tests/unit/adapters/clients-activity-test.js b/ui/tests/unit/adapters/clients-activity-test.js
index 7d23d1e975..0e732c7933 100644
--- a/ui/tests/unit/adapters/clients-activity-test.js
+++ b/ui/tests/unit/adapters/clients-activity-test.js
@@ -137,17 +137,33 @@ module('Unit | Adapter | clients activity', function (hooks) {
this.store.queryRecord(this.modelName, queryParams);
});
- test('it sends current billing period boolean if provided', async function (assert) {
+ test('it sends without query if no dates provided', async function (assert) {
assert.expect(1);
this.server.get('sys/internal/counters/activity', (schema, req) => {
- assert.propEqual(
- req.queryParams,
- { current_billing_period: 'true' },
- 'it passes current_billing_period to query record'
- );
+ assert.propEqual(req.queryParams, {});
});
- this.store.queryRecord(this.modelName, { current_billing_period: true });
+ this.store.queryRecord(this.modelName, { foo: 'bar' });
+ });
+
+ test('it sends without query if no valid dates provided', async function (assert) {
+ assert.expect(1);
+
+ this.server.get('sys/internal/counters/activity', (schema, req) => {
+ assert.propEqual(req.queryParams, {});
+ });
+
+ this.store.queryRecord(this.modelName, { start_time: 'bar' });
+ });
+
+ test('it handles empty query gracefully', async function (assert) {
+ assert.expect(1);
+
+ this.server.get('sys/internal/counters/activity', (schema, req) => {
+ assert.propEqual(req.queryParams, {});
+ });
+
+ this.store.queryRecord(this.modelName, {});
});
});