Skip to content

Commit 266ea69

Browse files
authored
UI: remove initial date from client counts (#27816)
1 parent 4ccf568 commit 266ea69

File tree

17 files changed

+326
-177
lines changed

17 files changed

+326
-177
lines changed

changelog/27816.txt

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

ui/app/adapters/clients/activity.js

+26-15
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,42 @@ import { formatDateObject } from 'core/utils/client-count-utils';
88
import { debug } from '@ember/debug';
99

1010
export default class ActivityAdapter extends ApplicationAdapter {
11+
formatTimeParam(dateObj, isEnd = false) {
12+
let formatted;
13+
if (dateObj) {
14+
try {
15+
const iso = dateObj.timestamp || formatDateObject(dateObj, isEnd);
16+
formatted = iso;
17+
} catch (e) {
18+
// carry on
19+
}
20+
}
21+
return formatted;
22+
}
1123
// javascript localizes new Date() objects but all activity log data is stored in UTC
1224
// create date object from user's input using Date.UTC() then send to backend as unix
1325
// time params from the backend are formatted as a zulu timestamp
1426
formatQueryParams(queryParams) {
15-
if (queryParams?.current_billing_period) {
16-
// { current_billing_period: true } automatically queries the activity log
17-
// from the builtin license start timestamp to the current month
18-
return queryParams;
27+
const query = {};
28+
const start = this.formatTimeParam(queryParams?.start_time);
29+
const end = this.formatTimeParam(queryParams?.end_time, true);
30+
if (start) {
31+
query.start_time = start;
1932
}
20-
let { start_time, end_time } = queryParams;
21-
start_time = start_time.timestamp || formatDateObject(start_time);
22-
end_time = end_time.timestamp || formatDateObject(end_time, true);
23-
return { start_time, end_time };
33+
if (end) {
34+
query.end_time = end;
35+
}
36+
return query;
2437
}
2538

2639
queryRecord(store, type, query) {
2740
const url = `${this.buildURL()}/internal/counters/activity`;
2841
const queryParams = this.formatQueryParams(query);
29-
if (queryParams) {
30-
return this.ajax(url, 'GET', { data: queryParams }).then((resp) => {
31-
const response = resp || {};
32-
response.id = response.request_id || 'no-data';
33-
return response;
34-
});
35-
}
42+
return this.ajax(url, 'GET', { data: queryParams }).then((resp) => {
43+
const response = resp || {};
44+
response.id = response.request_id || 'no-data';
45+
return response;
46+
});
3647
}
3748

3849
urlForFindRecord(id) {

ui/app/components/clients/activity.ts

+3-11
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// contains getters that filter and extract data from activity model for use in charts
88

99
import Component from '@glimmer/component';
10-
import { isSameMonth, fromUnixTime } from 'date-fns';
10+
import { isSameMonth } from 'date-fns';
1111
import { parseAPITimestamp } from 'core/utils/date-formatters';
1212
import { calculateAverage } from 'vault/utils/chart-helpers';
1313
import { filterVersionHistory, hasMountsKey, hasNamespacesKey } from 'core/utils/client-count-utils';
@@ -24,8 +24,8 @@ import type {
2424
interface Args {
2525
activity: ClientsActivityModel;
2626
versionHistory: ClientsVersionHistoryModel[];
27-
startTimestamp: number;
28-
endTimestamp: number;
27+
startTimestamp: string;
28+
endTimestamp: string;
2929
namespace: string;
3030
mountPath: string;
3131
}
@@ -40,14 +40,6 @@ export default class ClientsActivityComponent extends Component<Args> {
4040
return calculateAverage(data, key);
4141
};
4242

43-
get startTimeISO() {
44-
return fromUnixTime(this.args.startTimestamp).toISOString();
45-
}
46-
47-
get endTimeISO() {
48-
return fromUnixTime(this.args.endTimestamp).toISOString();
49-
}
50-
5143
get byMonthActivityData() {
5244
const { activity, namespace } = this.args;
5345
return namespace ? this.filteredActivityByMonth : activity.byMonth;

ui/app/components/clients/date-range.hbs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
@text="Set date range"
2828
@icon="edit"
2929
{{on "click" (fn (mut this.showEditModal) true)}}
30-
data-test-set-date-range
30+
data-test-date-range-edit
3131
/>
3232
{{/if}}
3333
</div>
@@ -87,7 +87,7 @@
8787
data-test-date-range-validation
8888
>{{this.validationError}}</Hds::Form::Error>
8989
{{/if}}
90-
{{#if this.useDefaultDates}}
90+
{{#if (and this.version.isEnterprise this.useDefaultDates)}}
9191
<Hds::Alert @type="compact" @color="highlight" class="has-top-margin-xs" data-test-range-default-alert as |A|>
9292
<A.Description>Dashboard will use the default date range from the API.</A.Description>
9393
</Hds::Alert>

ui/app/components/clients/date-range.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export default class ClientsDateRangeComponent extends Component<Args> {
6767
}
6868

6969
get validationError() {
70-
if (this.useDefaultDates) {
71-
// this means we want to reset, which is fine
70+
if (this.useDefaultDates && this.version.isEnterprise) {
71+
// this means we want to reset, which is fine for ent only
7272
return null;
7373
}
7474
if (!this.startDate || !this.endDate) {

ui/app/components/clients/page/counts.hbs

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
</p>
2020

2121
<Clients::DateRange
22-
@startTime={{this.startTimestampISO}}
23-
@endTime={{this.endTimestampISO}}
22+
@startTime={{@startTimestamp}}
23+
@endTime={{@endTimestamp}}
2424
@onChange={{this.onDateChange}}
2525
class="has-bottom-margin-l"
2626
/>

ui/app/components/clients/page/counts.ts

+11-19
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import Component from '@glimmer/component';
77
import { service } from '@ember/service';
88
import { action } from '@ember/object';
9-
import { fromUnixTime, isSameMonth, isAfter } from 'date-fns';
9+
import { isSameMonth, isAfter } from 'date-fns';
1010
import { parseAPITimestamp } from 'core/utils/date-formatters';
1111
import { filterVersionHistory } from 'core/utils/client-count-utils';
1212

@@ -21,11 +21,11 @@ interface Args {
2121
activity: ClientsActivityModel;
2222
activityError?: AdapterError;
2323
config: ClientsConfigModel;
24-
endTimestamp: number;
24+
endTimestamp: string; // ISO format
2525
mountPath: string;
2626
namespace: string;
2727
onFilterChange: CallableFunction;
28-
startTimestamp: number;
28+
startTimestamp: string; // ISO format
2929
versionHistory: ClientsVersionHistoryModel[];
3030
}
3131

@@ -34,29 +34,21 @@ export default class ClientsCountsPageComponent extends Component<Args> {
3434
@service declare readonly version: VersionService;
3535
@service declare readonly store: StoreService;
3636

37-
get startTimestampISO() {
38-
return this.args.startTimestamp ? fromUnixTime(this.args.startTimestamp).toISOString() : null;
39-
}
40-
41-
get endTimestampISO() {
42-
return this.args.endTimestamp ? fromUnixTime(this.args.endTimestamp).toISOString() : null;
43-
}
44-
4537
get formattedStartDate() {
46-
return this.startTimestampISO ? parseAPITimestamp(this.startTimestampISO, 'MMMM yyyy') : null;
38+
return this.args.startTimestamp ? parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy') : null;
4739
}
4840

4941
// returns text for empty state message if noActivityData
5042
get dateRangeMessage() {
51-
if (this.startTimestampISO && this.endTimestampISO) {
43+
if (this.args.startTimestamp && this.args.endTimestamp) {
5244
const endMonth = isSameMonth(
53-
parseAPITimestamp(this.startTimestampISO) as Date,
54-
parseAPITimestamp(this.endTimestampISO) as Date
45+
parseAPITimestamp(this.args.startTimestamp) as Date,
46+
parseAPITimestamp(this.args.endTimestamp) as Date
5547
)
5648
? ''
57-
: `to ${parseAPITimestamp(this.endTimestampISO, 'MMMM yyyy')}`;
49+
: `to ${parseAPITimestamp(this.args.endTimestamp, 'MMMM yyyy')}`;
5850
// completes the message 'No data received from { dateRangeMessage }'
59-
return `from ${parseAPITimestamp(this.startTimestampISO, 'MMMM yyyy')} ${endMonth}`;
51+
return `from ${parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy')} ${endMonth}`;
6052
}
6153
return null;
6254
}
@@ -127,9 +119,9 @@ export default class ClientsCountsPageComponent extends Component<Args> {
127119
// show banner if startTime returned from activity log (response) is after the queried startTime
128120
const { activity, config } = this.args;
129121
const activityStartDateObject = parseAPITimestamp(activity.startTime) as Date;
130-
const queryStartDateObject = parseAPITimestamp(this.startTimestampISO) as Date;
122+
const queryStartDateObject = parseAPITimestamp(this.args.startTimestamp) as Date;
131123
const isEnterprise =
132-
this.startTimestampISO === config.billingStartTimestamp?.toISOString() && this.version.isEnterprise;
124+
this.args.startTimestamp === config.billingStartTimestamp?.toISOString() && this.version.isEnterprise;
133125
const message = isEnterprise ? 'Your license start date is' : 'You requested data from';
134126

135127
if (

ui/app/components/clients/page/overview.hbs

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
@totalClientAttribution={{this.totalClientAttribution}}
2222
@newClientAttribution={{this.newClientAttribution}}
2323
@selectedNamespace={{@namespace}}
24-
@startTimestamp={{this.startTimeISO}}
25-
@endTimestamp={{this.endTimeISO}}
24+
@startTimestamp={{@startTimestamp}}
25+
@endTimestamp={{@endTimestamp}}
2626
@responseTimestamp={{@activity.responseTimestamp}}
2727
@isHistoricalMonth={{and (not this.isCurrentMonth) (not this.isDateRange)}}
2828
@upgradesDuringActivity={{this.upgradesDuringActivity}}

ui/app/routes/vault/cluster/clients/counts.ts

+49-21
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@
55

66
import Route from '@ember/routing/route';
77
import { service } from '@ember/service';
8-
import timestamp from 'core/utils/timestamp';
9-
import { getUnixTime } from 'date-fns';
8+
import { fromUnixTime } from 'date-fns';
109

1110
import type FlagsService from 'vault/services/flags';
1211
import type StoreService from 'vault/services/store';
1312
import type VersionService from 'vault/services/version';
1413
import type { ModelFrom } from 'vault/vault/route';
1514
import type ClientsRoute from '../clients';
1615
import type ClientsCountsController from 'vault/controllers/vault/cluster/clients/counts';
17-
import { setStartTimeQuery } from 'core/utils/client-count-utils';
1816

1917
export interface ClientsCountsRouteParams {
2018
start_time?: string | number | undefined;
@@ -39,38 +37,68 @@ export default class ClientsCountsRoute extends Route {
3937
return this.flags.fetchActivatedFlags();
4038
}
4139

42-
async getActivity(start_time: number | null, end_time: number) {
40+
/**
41+
* This method returns the query param timestamp if it exists. If not, it returns the activity timestamp value instead.
42+
*/
43+
paramOrResponseTimestamp(
44+
qpMillisString: string | number | undefined,
45+
activityTimeStamp: string | undefined
46+
) {
47+
let timestamp: string | undefined;
48+
const millis = Number(qpMillisString);
49+
if (!isNaN(millis)) {
50+
timestamp = fromUnixTime(millis).toISOString();
51+
}
52+
// fallback to activity timestamp only if there was no query param
53+
if (!timestamp && activityTimeStamp) {
54+
timestamp = activityTimeStamp;
55+
}
56+
return timestamp;
57+
}
58+
59+
async getActivity(params: ClientsCountsRouteParams) {
4360
let activity, activityError;
44-
// if there is no start_time we want the user to manually choose a date
45-
// in that case bypass the query so that the user isn't stuck viewing the activity error
46-
if (start_time) {
61+
// if CE without start time we want to skip the activity call
62+
// so that the user is forced to choose a date range
63+
if (this.version.isEnterprise || params.start_time) {
64+
const query = {
65+
// start and end params are optional -- if not provided, will fallback to API default
66+
start_time: this.formatTimeQuery(params?.start_time),
67+
end_time: this.formatTimeQuery(params?.end_time),
68+
};
4769
try {
48-
activity = await this.store.queryRecord('clients/activity', {
49-
start_time: { timestamp: start_time },
50-
end_time: { timestamp: end_time },
51-
});
70+
activity = await this.store.queryRecord('clients/activity', query);
5271
} catch (error) {
5372
activityError = error;
5473
}
5574
}
56-
return { activity, activityError };
75+
return {
76+
activity,
77+
activityError,
78+
};
79+
}
80+
81+
// Takes the string URL param and formats it as the adapter expects it,
82+
// if it exists and is valid
83+
formatTimeQuery(param: string | number | undefined) {
84+
let timeParam: { timestamp: number } | undefined;
85+
const millis = Number(param);
86+
if (!isNaN(millis)) {
87+
timeParam = { timestamp: millis };
88+
}
89+
return timeParam;
5790
}
5891

5992
async model(params: ClientsCountsRouteParams) {
6093
const { config, versionHistory } = this.modelFor('vault.cluster.clients') as ModelFrom<ClientsRoute>;
61-
// only enterprise versions will have a relevant billing start date, if null users must select initial start time
62-
const startTime = setStartTimeQuery(this.version.isEnterprise, config);
63-
64-
const startTimestamp = Number(params.start_time) || startTime;
65-
const endTimestamp = Number(params.end_time) || getUnixTime(timestamp.now());
66-
const { activity, activityError } = await this.getActivity(startTimestamp, endTimestamp);
67-
94+
const { activity, activityError } = await this.getActivity(params);
6895
return {
6996
activity,
7097
activityError,
7198
config,
72-
endTimestamp,
73-
startTimestamp,
99+
// activity.startTime corresponds to first month with data, but we want first month returned or requested
100+
startTimestamp: this.paramOrResponseTimestamp(params?.start_time, activity?.byMonth[0]?.timestamp),
101+
endTimestamp: this.paramOrResponseTimestamp(params?.end_time, activity?.endTime),
74102
versionHistory,
75103
};
76104
}

ui/lib/core/addon/utils/client-count-utils.ts

-11
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,6 @@ export const filterVersionHistory = (
6666
return [];
6767
};
6868

69-
export const setStartTimeQuery = (
70-
isEnterprise: boolean,
71-
config: ClientsConfigModel | Record<string, never>
72-
) => {
73-
// CE versions have no license and so the start time defaults to "0001-01-01T00:00:00Z"
74-
if (isEnterprise && _hasConfig(config)) {
75-
return getUnixTime(config.billingStartTimestamp);
76-
}
77-
return null;
78-
};
79-
8069
// METHODS FOR SERIALIZING ACTIVITY RESPONSE
8170
export const formatDateObject = (dateObj: { monthIdx: number; year: number }, isEnd: boolean) => {
8271
const { year, monthIdx } = dateObj;

0 commit comments

Comments
 (0)