From 171b4f46fd20fa8081061efe3ae21cbbc1282cd7 Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Thu, 10 Feb 2022 12:51:50 -0800 Subject: [PATCH] UI/Client counts view if no license (#13964) * adds date picker if no license start date found * handle permissions denied for license endpoint * handle permissions errors if no license start date * change empty state copy for OSS * fix tests and empty state view * update nav links * remove ternary Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> * simplify hbs boolean Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> * organize history file * organize current file * rerun tests * fix conditional to show attribution chart * match main Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> --- ui/app/adapters/clients/activity.js | 36 ++++------- ui/app/components/clients/current.js | 16 ++++- ui/app/components/clients/history.js | 50 +++++++++------ ui/app/components/date-dropdown.js | 37 +++++++++++ .../routes/vault/cluster/clients/history.js | 27 ++++---- .../components/clients/attribution.hbs | 61 ++++++++----------- .../templates/components/clients/current.hbs | 9 ++- .../templates/components/clients/history.hbs | 29 ++++++--- ui/app/templates/components/cluster-info.hbs | 2 +- ui/app/templates/components/date-dropdown.hbs | 54 ++++++++++++++++ ui/app/templates/vault/cluster.hbs | 5 +- .../vault/cluster/clients/history.hbs | 1 - .../components/clients-current-test.js | 3 +- 13 files changed, 220 insertions(+), 110 deletions(-) create mode 100644 ui/app/components/date-dropdown.js create mode 100644 ui/app/templates/components/date-dropdown.hbs diff --git a/ui/app/adapters/clients/activity.js b/ui/app/adapters/clients/activity.js index ad0c65e3f2..eea69237aa 100644 --- a/ui/app/adapters/clients/activity.js +++ b/ui/app/adapters/clients/activity.js @@ -4,32 +4,25 @@ import { formatRFC3339 } from 'date-fns'; export default Application.extend({ 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 it's an array, if it is, it's coming from an action like selecting a new startTime 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)); + // check if it's an array, if it is, it's coming from an action like selecting a new startTime 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)); + } + if (end_time) { + if (Array.isArray(end_time)) { + let endYear = Number(end_time[0]); + let endMonth = Number(end_time[1]); + end_time = formatRFC3339(new Date(endYear, endMonth)); } - if (end_time) { - 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 }; - } + return { start_time, end_time }; } else { - // did not have a start time, return null through to component. - return null; + return { start_time }; } }, - // ARG TODO current Month tab is hitting this endpoint. Need to amend so only hit on Monthly history (large payload) // 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] @@ -44,9 +37,6 @@ export default Application.extend({ response.id = response.request_id || 'no-data'; return response; }); - } else { - // did not have a start time, return null through to component. - return null; } }, }); diff --git a/ui/app/components/clients/current.js b/ui/app/components/clients/current.js index 090649ff3e..9da877166e 100644 --- a/ui/app/components/clients/current.js +++ b/ui/app/components/clients/current.js @@ -7,18 +7,28 @@ export default class Current extends Component { { key: 'entity_clients', label: 'entity clients' }, { key: 'non_entity_clients', label: 'non-entity clients' }, ]; + @tracked firstUpgradeVersion = this.args.model.versionHistory[0].id || null; // return 1.9.0 or earliest upgrade post 1.9.0 + @tracked upgradeDate = this.args.model.versionHistory[0].timestampInstalled || null; // returns RFC3339 timestamp + @tracked selectedNamespace = null; @tracked namespaceArray = this.byNamespaceCurrent.map((namespace) => { return { name: namespace['label'], id: namespace['label'] }; }); - @tracked firstUpgradeVersion = this.args.model.versionHistory[0].id || null; // return 1.9.0 or earliest upgrade post 1.9.0 - @tracked upgradeDate = this.args.model.versionHistory[0].timestampInstalled || null; // returns RFC3339 timestamp - // API client count data by namespace for current/partial month + // Response client count data by namespace for current/partial month get byNamespaceCurrent() { return this.args.model.monthly?.byNamespace || []; } + get isGatheringData() { + // return true if tracking IS enabled but no data collected yet + return this.args.model.config?.enabled === 'On' && this.byNamespaceCurrent.length === 0; + } + + get hasAttributionData() { + return this.totalUsageCounts.clients !== 0 && this.totalClientsData.length !== 0; + } + get countsIncludeOlderData() { let firstUpgrade = this.args.model.versionHistory[0]; if (!firstUpgrade) { diff --git a/ui/app/components/clients/history.js b/ui/app/components/clients/history.js index 940e0e6fb4..f3ebb38e55 100644 --- a/ui/app/components/clients/history.js +++ b/ui/app/components/clients/history.js @@ -3,9 +3,10 @@ import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { isSameMonth, isAfter } from 'date-fns'; - export default class History extends Component { - // TODO CMB alphabetize and delete unused vars (particularly @tracked) + @service store; + @service version; + arrayOfMonths = [ 'January', 'February', @@ -26,7 +27,7 @@ export default class History extends Component { { key: 'non_entity_clients', label: 'non-entity clients' }, ]; - // needed for startTime modal picker + // FOR START DATE EDIT & MODAL // months = Array.from({ length: 12 }, (item, i) => { return new Date(0, i).toLocaleString('en-US', { month: 'long' }); }); @@ -34,33 +35,43 @@ export default class History extends Component { return new Date().getFullYear() - i; }); - @service store; - - @tracked queriedActivityResponse = null; - @tracked barChartSelection = false; @tracked isEditStartMonthOpen = false; - @tracked responseRangeDiffMessage = null; - @tracked startTimeRequested = null; - @tracked startTimeFromResponse = this.args.model.startTimeFromLicense; // ex: ['2021', 3] is April 2021 (0 indexed) - @tracked endTimeFromResponse = this.args.model.endTimeFromResponse; @tracked startMonth = null; @tracked startYear = null; + + // FOR HISTORY COMPONENT // + + // RESPONSE + @tracked endTimeFromResponse = this.args.model.endTimeFromResponse; + @tracked startTimeFromResponse = this.args.model.startTimeFromLicense; // ex: ['2021', 3] is April 2021 (0 indexed) + @tracked startTimeRequested = null; + @tracked queriedActivityResponse = null; + + // VERSION/UPGRADE INFO + @tracked firstUpgradeVersion = this.args.model.versionHistory[0].id || null; // return 1.9.0 or earliest upgrade post 1.9.0 + @tracked upgradeDate = this.args.model.versionHistory[0].timestampInstalled || null; // returns RFC3339 timestamp + + // SEARCH SELECT @tracked selectedNamespace = null; - @tracked noActivityDate = ''; @tracked namespaceArray = this.getActivityResponse.byNamespace.map((namespace) => { return { name: namespace['label'], id: namespace['label'] }; }); - @tracked firstUpgradeVersion = this.args.model.versionHistory[0].id || null; // return 1.9.0 or earliest upgrade post 1.9.0 - @tracked upgradeDate = this.args.model.versionHistory[0].timestampInstalled || null; // returns RFC3339 timestamp + + // TEMPLATE MESSAGING + @tracked noActivityDate = ''; + @tracked responseRangeDiffMessage = null; // on init API response uses license start_date, getter updates when user queries dates get getActivityResponse() { return this.queriedActivityResponse || this.args.model.activity; } + get hasAttributionData() { + return this.totalUsageCounts.clients !== 0 && this.totalClientsData.length !== 0; + } + get startTimeDisplay() { if (!this.startTimeFromResponse) { - // otherwise will return date of new Date(null) return null; } let month = this.startTimeFromResponse[1]; @@ -70,7 +81,6 @@ export default class History extends Component { get endTimeDisplay() { if (!this.endTimeFromResponse) { - // otherwise will return date of new Date(null) return null; } let month = this.endTimeFromResponse[1]; @@ -120,7 +130,6 @@ export default class History extends Component { return isAfter(versionDate, startTimeFromResponseAsDateObject) ? versionDate : false; } - // ACTIONS @action async handleClientActivityQuery(month, year, dateType) { if (dateType === 'cancel') { @@ -134,7 +143,7 @@ export default class History extends Component { // clicked "Edit" Billing start month in Dashboard which opens a modal. if (dateType === 'startTime') { let monthIndex = this.arrayOfMonths.indexOf(month); - this.startTimeRequested = [year.toString(), monthIndex]; // ['2021', 0] (e.g. January 2021) // TODO CHANGE TO ARRAY + this.startTimeRequested = [year.toString(), monthIndex]; // ['2021', 0] (e.g. January 2021) this.endTimeRequested = null; } // clicked "Custom End Month" from the calendar-widget @@ -173,7 +182,7 @@ export default class History extends Component { } this.queriedActivityResponse = response; } catch (e) { - // ARG TODO handle error + return e; } } @@ -188,6 +197,7 @@ export default class History extends Component { this.selectedNamespace = value; } + // FOR START DATE MODAL @action selectStartMonth(month) { this.startMonth = month; @@ -198,7 +208,7 @@ export default class History extends Component { this.startYear = year; } - // HELPERS + // HELPERS // filterByNamespace(namespace) { return this.getActivityResponse.byNamespace.find((ns) => ns.label === namespace); } diff --git a/ui/app/components/date-dropdown.js b/ui/app/components/date-dropdown.js new file mode 100644 index 0000000000..f0d42a7984 --- /dev/null +++ b/ui/app/components/date-dropdown.js @@ -0,0 +1,37 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +/** + * @module DateDropdown + * DateDropdown components are used to display a dropdown of months and years to handle date selection + * + * @example + * ```js + * + * ``` + * @param {function} handleDateSelection - is the action from the parent that the date picker triggers + * @param {string} [name] - optional argument passed from date dropdown to parent function + */ + +export default class DateDropdown extends Component { + @tracked startMonth = null; + @tracked startYear = null; + + months = Array.from({ length: 12 }, (item, i) => { + return new Date(0, i).toLocaleString('en-US', { month: 'long' }); + }); + years = Array.from({ length: 5 }, (item, i) => { + return new Date().getFullYear() - i; + }); + + @action + selectStartMonth(month) { + this.startMonth = month; + } + + @action + selectStartYear(year) { + this.startYear = year; + } +} diff --git a/ui/app/routes/vault/cluster/clients/history.js b/ui/app/routes/vault/cluster/clients/history.js index ed4d9ef108..f7e5c3dc28 100644 --- a/ui/app/routes/vault/cluster/clients/history.js +++ b/ui/app/routes/vault/cluster/clients/history.js @@ -5,19 +5,24 @@ import { action } from '@ember/object'; export default class HistoryRoute extends Route { async getActivity(start_time) { try { - return this.store.queryRecord('clients/activity', { start_time }); + // on init ONLY make network request if we have a start time from the license + // otherwise user needs to manually input + return start_time + ? await this.store.queryRecord('clients/activity', { start_time }) + : { endTime: null }; } catch (e) { - // ARG TODO handle return e; } } - async getLicense() { + async getLicenseStartTime() { try { - return this.store.queryRecord('license', {}); + let license = await this.store.queryRecord('license', {}); + // if license.startTime is 'undefined' return 'null' for consistency + return license.startTime || null; } catch (e) { - // ARG TODO handle - return e; + // if error due to permission denied, return null so user can input date manually + return null; } } @@ -48,18 +53,18 @@ export default class HistoryRoute extends Route { async model() { let config = await this.store.queryRecord('clients/config', {}).catch((e) => { - console.debug(e); // swallowing error so activity can show if no config permissions + console.debug(e); return {}; }); - let license = await this.getLicense(); - let activity = await this.getActivity(license.startTime); + let licenseStart = await this.getLicenseStartTime(); + let activity = await this.getActivity(licenseStart); return RSVP.hash({ config, activity, - startTimeFromLicense: this.parseRFC3339(license.startTime), - endTimeFromResponse: activity ? this.parseRFC3339(activity.endTime) : null, + startTimeFromLicense: this.parseRFC3339(licenseStart), + endTimeFromResponse: this.parseRFC3339(activity?.endTime), versionHistory: this.getVersionHistory(), }); } diff --git a/ui/app/templates/components/clients/attribution.hbs b/ui/app/templates/components/clients/attribution.hbs index b0ced603ce..2a81542f20 100644 --- a/ui/app/templates/components/clients/attribution.hbs +++ b/ui/app/templates/components/clients/attribution.hbs @@ -13,46 +13,37 @@ - {{#if (eq @totalUsageCounts.clients 0)}} -
- -
- {{else}} -
- -
+
+ +
-
-

{{this.chartText.totalCopy}}

-
+
+

{{this.chartText.totalCopy}}

+
-
-

Top {{this.attributionBreakdown}}

-

{{this.topClientCounts.label}}

-
+
+

Top {{this.attributionBreakdown}}

+

{{this.topClientCounts.label}}

+
-
-

Clients in {{this.attributionBreakdown}}

-

{{format-number this.topClientCounts.clients}}

-
+
+

Clients in {{this.attributionBreakdown}}

+

{{format-number this.topClientCounts.clients}}

+
-
- Updated - {{date-format @timestamp "MMM dd yyyy, h:mm:ss aaa"}} -
+
+ Updated + {{date-format @timestamp "MMM dd yyyy, h:mm:ss aaa"}} +
-
- {{capitalize @chartLegend.0.label}} - {{capitalize @chartLegend.1.label}} -
- {{/if}} +
+ {{capitalize @chartLegend.0.label}} + {{capitalize @chartLegend.1.label}} +
{{! MODAL FOR CSV DOWNLOAD }} diff --git a/ui/app/templates/components/clients/current.hbs b/ui/app/templates/components/clients/current.hbs index 8056c127bc..3626d08bed 100644 --- a/ui/app/templates/components/clients/current.hbs +++ b/ui/app/templates/components/clients/current.hbs @@ -13,6 +13,11 @@ {{/if}} + {{else if this.isGatheringData}} + {{else}}
FILTERS @@ -48,7 +53,7 @@ @title={{date-format this.responseTimestamp "MMMM"}} @totalUsageCounts={{this.totalUsageCounts}} /> - {{#if this.totalClientsData}} + {{#if this.hasAttributionData}} {{/if}} - {{else}} - {{/if}} {{/if}} {{/if}} diff --git a/ui/app/templates/components/clients/history.hbs b/ui/app/templates/components/clients/history.hbs index e47363185d..fdb3a0e2b1 100644 --- a/ui/app/templates/components/clients/history.hbs +++ b/ui/app/templates/components/clients/history.hbs @@ -8,10 +8,14 @@ Billing start month
-

{{this.startTimeDisplay}}

- + {{#if this.startTimeDisplay}} +

{{this.startTimeDisplay}}

+ + {{else}} + + {{/if}}

This date comes from your license, and defines when client counting starts. Without this starting point, the data shown @@ -106,7 +110,7 @@ {{else}} {{#if this.totalUsageCounts}} - {{#if this.totalClientsData}} + {{#if this.hasAttributionData}} + {{#if this.version.isEnterprise}} + + {{else}} + + {{/if}} {{/if}} {{/if}} diff --git a/ui/app/templates/components/cluster-info.hbs b/ui/app/templates/components/cluster-info.hbs index 3d94586475..61f3ffb3d9 100644 --- a/ui/app/templates/components/cluster-info.hbs +++ b/ui/app/templates/components/cluster-info.hbs @@ -152,7 +152,7 @@