mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
UI: remove initial date from client counts (#27816)
This commit is contained in:
3
changelog/27816.txt
Normal file
3
changelog/27816.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: remove initial start/end parameters on the activity call for client counts dashboard.
|
||||
```
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Args> {
|
||||
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;
|
||||
|
||||
@@ -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}}
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
data-test-date-range-validation
|
||||
>{{this.validationError}}</Hds::Form::Error>
|
||||
{{/if}}
|
||||
{{#if this.useDefaultDates}}
|
||||
{{#if (and this.version.isEnterprise this.useDefaultDates)}}
|
||||
<Hds::Alert @type="compact" @color="highlight" class="has-top-margin-xs" data-test-range-default-alert as |A|>
|
||||
<A.Description>Dashboard will use the default date range from the API.</A.Description>
|
||||
</Hds::Alert>
|
||||
|
||||
@@ -67,8 +67,8 @@ export default class ClientsDateRangeComponent extends Component<Args> {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
</p>
|
||||
|
||||
<Clients::DateRange
|
||||
@startTime={{this.startTimestampISO}}
|
||||
@endTime={{this.endTimestampISO}}
|
||||
@startTime={{@startTimestamp}}
|
||||
@endTime={{@endTimestamp}}
|
||||
@onChange={{this.onDateChange}}
|
||||
class="has-bottom-margin-l"
|
||||
/>
|
||||
|
||||
@@ -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<Args> {
|
||||
@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<Args> {
|
||||
// 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 (
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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<ClientsRoute>;
|
||||
// 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,17 +66,6 @@ export const filterVersionHistory = (
|
||||
return [];
|
||||
};
|
||||
|
||||
export const setStartTimeQuery = (
|
||||
isEnterprise: boolean,
|
||||
config: ClientsConfigModel | Record<string, never>
|
||||
) => {
|
||||
// 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;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"]`,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, {});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user