Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client Count Calendar widget updates #13777

Merged
merged 23 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 19 additions & 21 deletions ui/app/adapters/clients/activity.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import Application from '../application';
import { zonedTimeToUtc } from 'date-fns-tz'; // https://github.com/marnusw/date-fns-tz#zonedtimetoutc
import { formatRFC3339 } from 'date-fns';

export default Application.extend({
checkTimeType(query) {
formatTimeParams(query) {
let { start_time, end_time } = query;
// do not query without start_time. Otherwise returns last year data, which is not reflective of billing data.
if (start_time) {
// check if start_time is a RFC3339 timestamp and if not convert to one.
if (start_time.split(',').length > 1) {
let utcDate = this.utcDate(
new Date(Number(start_time.split(',')[1]), Number(start_time.split(',')[0] - 1))
);
start_time = utcDate.toISOString();
// check if it's an array, if it is, it's coming from an action like selecting a new Start Time or new EndTime
if (Array.isArray(start_time)) {
let startYear = Number(start_time[0]);
let startMonth = Number(start_time[1]);
start_time = formatRFC3339(new Date(startYear, startMonth));
}
// look for end_time. If there is one check if it is a RFC3339 timestamp otherwise convert it.
// look for end_time
if (end_time) {
let utcDateEnd = this.utcDate(
new Date(Number(end_time.split(',')[1]), Number(end_time.split(',')[0]))
);
// ARG TODO !!!! SUPER IMPORTANT, with endDate you need to make it the last day of the month, right now it's the first!!!
end_time = utcDateEnd.toISOString();
if (Array.isArray(end_time)) {
let endYear = Number(end_time[0]);
let endMonth = Number(end_time[1]);
end_time = formatRFC3339(new Date(endYear, endMonth));
}

return { start_time, end_time };
} else {
return { start_time };
Expand All @@ -30,17 +30,15 @@ export default Application.extend({
}
},

utcDate(dateObject) {
// To remove the timezone of the local user (API returns and expects Zulu time/UTC) we need to use a method provided by date-fns-tz to return the UTC date
let timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; // browser API method
return zonedTimeToUtc(dateObject, timeZone);
},

// ARG TODO current Month tab is hitting this endpoint. Need to amend so only hit on Monthly history (large payload)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made a ticket for this comment.

// query comes in as either: {start_time: '2021-03-17T00:00:00Z'} or
// {start_time: Array(2), end_time: Array(2)}
// end_time: (2) ['2022', 0]
// start_time: (2) ['2021', 2]
queryRecord(store, type, query) {
let url = `${this.buildURL()}/internal/counters/activity`;
// check if start and/or end times are in RFC3395 format, if not convert with timezone UTC/zulu.
let queryParams = this.checkTimeType(query);
let queryParams = this.formatTimeParams(query);
if (queryParams) {
return this.ajax(url, 'GET', { data: queryParams }).then((resp) => {
let response = resp || {};
Expand Down
17 changes: 9 additions & 8 deletions ui/app/components/calendar-widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import { tracked } from '@glimmer/tracking';
* @example
* ```js
* <CalendarWidget
* @param {string} endTimeDisplay - The display value of the endTime. Ex: January 2022.
* @param {string} endTimeFromResponse - Because endTimeDisplay is a tracked property of the parent it changes. But we need to keep a value unchanged that records the endTime returned by the counters/activity endpoint. This is that value.
* @param {string} startTimeDisplay - The display value of startTime that the parent manages. This component is only responsible for modifying the endTime which is sends to the parent to make the network request.
* @param {array} arrayOfMonths - An array of all the months that the calendar widget iterates through.
* @param {string} endTimeDisplay - The formatted display value of the endTime. Ex: January 2022.
* @param {string} endTimeFromResponse - The value returned on the counters/activity endpoint, which shows the true endTime not the selected one, which can be different.
* @param {function} handleClientActivityQuery - a function passed from parent. This component sends the month and year to the parent via this method which then calculates the new data.
* @param {function} handleCurrentBillingPeriod - a function passed from parent. This component makes the parent aware that the user selected Current billing period and it handles resetting the data.
* @param {string} startTimeDisplay - The formatted display value of the endTime. Ex: January 2022. This component is only responsible for modifying the endTime which is sends to the parent to make the network request.
* />
*
* ```
Expand All @@ -41,7 +42,7 @@ class CalendarWidget extends Component {
}

isObsoleteYear() {
// do not allow them to choose a end year before the this.args.startTimeDisplay
// do not allow them to choose a year before the this.args.startTimeDisplay
let startYear = this.args.startTimeDisplay.split(' ')[1];
return this.displayYear.toString() === startYear; // if on startYear then don't let them click back to the year prior
}
Expand All @@ -68,6 +69,7 @@ class CalendarWidget extends Component {

let elementMonthId = parseInt(e.id.split('-')[0]); // dependent on the shape of the element id
// for current year

if (this.currentMonth <= elementMonthId) {
// only disable months when current year is selected
if (this.isCurrentYear()) {
Expand All @@ -87,11 +89,10 @@ class CalendarWidget extends Component {
}
// Compare values so the user cannot select an endTime after the endTime returned from counters/activity response on page load.
// ARG TODO will need to test if no data is returned on page load.
if (this.displayYear.toString() === this.args.endTimeFromResponse.split(' ')[1]) {
let endMonth = this.args.endTimeFromResponse.split(' ')[0];
let endMonthIndex = this.args.arrayOfMonths.indexOf(endMonth);
if (this.displayYear.toString() === this.args.endTimeFromResponse[0]) {
let endMonth = this.args.endTimeFromResponse[1];
// add readOnly class to any month that is older (higher) than the endMonth index. (e.g. if nov is the endMonth of the endTimeDisplay, then 11 and 12 should not be displayed 10 < 11 and 10 < 12.)
if (endMonthIndex < elementMonthId) {
if (endMonth < elementMonthId) {
e.classList.add('is-readOnly');
}
}
Expand Down
38 changes: 28 additions & 10 deletions ui/app/components/clients/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,21 @@ export default class Dashboard extends Component {
@tracked isEditStartMonthOpen = false;
@tracked responseRangeDiffMessage = null;
@tracked startTimeRequested = null;
@tracked startTimeFromResponse = this.args.model.startTimeFromLicense; // ex: "3,2021"
@tracked startTimeFromResponse = this.args.model.startTimeFromLicense; // ex: ['2021', 3] is April 2021 (0 indexed)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great comments, thank you for the example!

@tracked endTimeFromResponse = this.args.model.endTimeFromLicense;
@tracked startMonth = null;
@tracked startYear = null;
@tracked selectedNamespace = null;
// @tracked selectedNamespace = 'namespacelonglonglong4/'; // for testing namespace selection view

get startTimeDisplay() {
// changes 3,2021 to March, 2021
if (!this.startTimeFromResponse) {
// otherwise will return date of new Date(null)
return null;
}
let month = Number(this.startTimeFromResponse.split(',')[0]) - 1;
let year = this.startTimeFromResponse.split(',')[1];
// this.startTimeFromResponse = ['2021', 2] is March 2021
Monkeychip marked this conversation as resolved.
Show resolved Hide resolved
let month = this.startTimeFromResponse[1];
let year = this.startTimeFromResponse[0];
return `${this.arrayOfMonths[month]} ${year}`;
}

Expand All @@ -63,8 +63,8 @@ export default class Dashboard extends Component {
// otherwise will return date of new Date(null)
return null;
}
let month = Number(this.endTimeFromResponse.split(',')[0]) - 1;
let year = this.endTimeFromResponse.split(',')[1];
let month = this.endTimeFromResponse[1];
let year = this.endTimeFromResponse[0];
return `${this.arrayOfMonths[month]} ${year}`;
}

Expand Down Expand Up @@ -106,6 +106,22 @@ export default class Dashboard extends Component {
}
return this.args.model.activity.responseTimestamp;
}
// HELPERS
areArraysTheSame(a1, a2) {
return (
a1 === a2 ||
(a1 !== null &&
a2 !== null &&
a1.length === a2.length &&
a1
.map(function (val, idx) {
return val === a2[idx];
})
.reduce(function (prev, cur) {
return prev && cur;
}, true))
);
}

// ACTIONS
@action
Expand All @@ -121,14 +137,14 @@ export default class Dashboard extends Component {
// clicked "Edit" Billing start month in Dashboard which opens a modal.
if (dateType === 'startTime') {
let monthIndex = this.arrayOfMonths.indexOf(month);
this.startTimeRequested = `${monthIndex + 1},${year}`; // "1, 2021"
this.startTimeRequested = [year.toString(), monthIndex]; // ['2021', 0] (e.g. January 2021) // TODO CHANGE TO ARRAY
this.endTimeRequested = null;
}
// clicked "Custom End Month" from the calendar-widget
if (dateType === 'endTime') {
// use the currently selected startTime for your startTimeRequested.
this.startTimeRequested = this.startTimeFromResponse;
this.endTimeRequested = `${month},${year}`; // "1, 2021"
this.endTimeRequested = [year.toString(), month];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

month here is a string if I'm reading correctly, do you mean monthIndex?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endTime and startTime come in a little different. One comes in as a month name (startTime) whereas endTime comes in as an index because it's a calendar of months instead of a list. I'll add a comment in the code.

}

try {
Expand All @@ -140,10 +156,12 @@ export default class Dashboard extends Component {
// this.endTime will be null and use this to show EmptyState message on the template.
return;
}

// note: this.startTimeDisplay (at getter) is updated by this.startTimeFromResponse
this.startTimeFromResponse = response.formattedStartTime;
this.endTimeFromResponse = response.formattedEndTime;
if (this.startTimeRequested !== this.startTimeFromResponse) {
// compare if the response and what you requested are the same. If they are not throw a warning.
// this only gets triggered if the data was returned, which does not happen if the user selects a startTime after for which we have data. That's an adapter error and is captured differently.
if (!this.areArraysTheSame(this.startTimeFromResponse, this.startTimeRequested)) {
this.responseRangeDiffMessage = `You requested data from ${month} ${year}. We only have data from ${this.startTimeDisplay}, and that is what is being shown here.`;
} else {
this.responseRangeDiffMessage = null;
Expand Down
4 changes: 2 additions & 2 deletions ui/app/models/clients/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export default class Activity extends Model {
@attr('string') responseTimestamp;
@attr('array') byNamespace;
@attr('string') endTime;
@attr('string') formattedEndTime;
@attr('string') formattedStartTime;
@attr('array') formattedEndTime;
@attr('array') formattedStartTime;
@attr('string') startTime;
@attr('object') total;
}
14 changes: 7 additions & 7 deletions ui/app/routes/vault/cluster/clients/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ export default Route.extend(ClusterRoute, {
}
},

rfc33395ToMonthYear(timestamp) {
// return [2021, 04 (e.g. 2021 March, make 0-indexed)
return [timestamp.split('-')[0], Number(timestamp.split('-')[1].replace(/^0+/, '')) - 1];
},

async model() {
let config = this.store.queryRecord('clients/config', {}).catch((e) => {
console.debug(e);
Expand All @@ -52,14 +57,9 @@ export default Route.extend(ClusterRoute, {
let license = await this.getLicense(); // get default start_time
let activity = await this.getActivity(license.startTime); // returns client counts using license start_time.
let monthly = await this.getMonthly(); // returns the partial month endpoint
let endTimeFromLicense = this.rfc33395ToMonthYear(activity.endTime);
let startTimeFromLicense = this.rfc33395ToMonthYear(license.startTime);

// all times to the component must be mm,yyyy ex: "1,2021"
let endTimeFromLicense = `${activity.endTime.split('-')[1].replace(/^0+/, '')},${
activity.endTime.split('-')[0]
}`;
let startTimeFromLicense = `${license.startTime.split('-')[1].replace(/^0+/, '')},${
license.startTime.split('-')[0]
}`;
return hash({
// ARG TODO will remove "hash" once remove "activity," which currently relies on it.
activity,
Expand Down
4 changes: 2 additions & 2 deletions ui/app/serializers/clients/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ export default class ActivitySerializer extends ApplicationSerializer {
}

rfc33395ToMonthYear(timestamp) {
// return 03,2021 (e.g. March 2021, non-indexed)
return `${timestamp.split('-')[1].replace(/^0+/, '')},${timestamp.split('-')[0]}`;
// return ['2021,' 04 (e.g. 2021 March, make 0-indexed)
return [timestamp.split('-')[0], Number(timestamp.split('-')[1].replace(/^0+/, '')) - 1];
}

normalizeResponse(store, primaryModelClass, payload, id, requestType) {
Expand Down
3 changes: 0 additions & 3 deletions ui/app/styles/core/alert-banner.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
.has-margin-top {
margin-top: $spacing-s;
}
.alert-banner-message-body {
border: 0;
margin-top: $spacing-xxs;
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/clients/dashboard.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
</ToolbarFilters>
</Toolbar>
{{#if this.responseRangeDiffMessage}}
<AlertBanner @class="has-margin-top" @type="warning" @message={{this.responseRangeDiffMessage}} />
<AlertBanner @type="warning" @class="has-top-margin-s" @message={{this.responseRangeDiffMessage}} />
{{/if}}
{{#if @isLoading}}
<LayoutLoading />
Expand Down