UI: remove initial date from client counts (#27816)

This commit is contained in:
Chelsea Shaw
2024-07-31 12:35:11 -05:00
committed by GitHub
parent 4ccf568480
commit 266ea693cc
17 changed files with 324 additions and 175 deletions

3
changelog/27816.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: remove initial start/end parameters on the activity call for client counts dashboard.
```

View File

@@ -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) {

View File

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

View File

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

View File

@@ -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) {

View File

@@ -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"
/>

View File

@@ -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 (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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}"]`,

View File

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

View File

@@ -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) {

View File

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

View File

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