From 050a90b2ab904383c4448c21903d69ba6d6e4ffc Mon Sep 17 00:00:00 2001
From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
Date: Wed, 17 Jul 2024 11:48:44 -0500
Subject: [PATCH] UI: Update date-picker UX in Client Counts (#27796)
* Create date-range component with tests
* update selectors and callback behavior
* update cc tests & selectors
* cleanup
* RIP calendar-widget and date-dropdown -- you were good components
* reset on close
* Add changelog
* use parseApiTimestamp
* fix test
* cleanup
* make date-range typescript, update behavior
* add words
* minor styling
* fix test
---
changelog/27796.txt | 3 +
ui/app/components/calendar-widget.js | 103 --------
ui/app/components/clients/date-range.hbs | 102 ++++++++
ui/app/components/clients/date-range.ts | 125 +++++++++
ui/app/components/clients/page/counts.hbs | 60 +----
ui/app/components/clients/page/counts.ts | 32 +--
ui/app/components/date-dropdown.js | 78 ------
ui/app/routes/vault/cluster/clients/counts.ts | 2 -
.../styles/components/clients-date-range.scss | 14 +
ui/app/styles/core.scss | 1 +
ui/app/templates/components/date-dropdown.hbs | 38 ---
ui/tests/acceptance/clients/counts-test.js | 20 +-
.../clients/counts/overview-test.js | 71 +++---
.../helpers/clients/client-count-helpers.js | 14 +-
.../helpers/clients/client-count-selectors.ts | 36 +--
.../components/calendar-widget-test.js | 240 ------------------
.../components/clients/date-range-test.js | 106 ++++++++
.../components/clients/page/counts-test.js | 82 +++---
.../components/date-dropdown-test.js | 165 ------------
19 files changed, 454 insertions(+), 838 deletions(-)
create mode 100644 changelog/27796.txt
delete mode 100644 ui/app/components/calendar-widget.js
create mode 100644 ui/app/components/clients/date-range.hbs
create mode 100644 ui/app/components/clients/date-range.ts
delete mode 100644 ui/app/components/date-dropdown.js
create mode 100644 ui/app/styles/components/clients-date-range.scss
delete mode 100644 ui/app/templates/components/date-dropdown.hbs
delete mode 100644 ui/tests/integration/components/calendar-widget-test.js
create mode 100644 ui/tests/integration/components/clients/date-range-test.js
delete mode 100644 ui/tests/integration/components/date-dropdown-test.js
diff --git a/changelog/27796.txt b/changelog/27796.txt
new file mode 100644
index 0000000000..7a1e7ebac3
--- /dev/null
+++ b/changelog/27796.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+ui: simplify the date range editing experience in the client counts dashboard.
+```
\ No newline at end of file
diff --git a/ui/app/components/calendar-widget.js b/ui/app/components/calendar-widget.js
deleted file mode 100644
index 804c9af196..0000000000
--- a/ui/app/components/calendar-widget.js
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Component from '@glimmer/component';
-import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
-import { ARRAY_OF_MONTHS, parseAPITimestamp } from 'core/utils/date-formatters';
-import { addYears, isSameYear, subYears } from 'date-fns';
-import timestamp from 'core/utils/timestamp';
-/**
- * @module CalendarWidget
- * CalendarWidget component is used in the client counts dashboard to select a month/year to query the /activity endpoint.
- * The component returns an object with selected date info, example: { dateType: 'endDate', monthIdx: 0, monthName: 'January', year: 2022 }
- *
- * @example
- * ```js
- *
- *
- * @param {string} startTimestamp - ISO timestamp string of the calendar widget's start time, displays in dropdown trigger
- * @param {string} endTimestamp - ISO timestamp string for the calendar widget's end time, displays in dropdown trigger
- * @param {function} selectMonth - callback function from parent - fires when selecting a month or clicking "Current billing period"
- * />
- * ```
- */
-export default class CalendarWidget extends Component {
- currentDate = timestamp.now();
- @tracked calendarDisplayDate = this.currentDate; // init to current date, updates when user clicks on calendar chevrons
- @tracked showCalendar = false;
-
- // both date getters return a date object
- get startDate() {
- return parseAPITimestamp(this.args.startTimestamp);
- }
- get endDate() {
- return parseAPITimestamp(this.args.endTimestamp);
- }
- get displayYear() {
- return this.calendarDisplayDate.getFullYear();
- }
- get disableFutureYear() {
- return isSameYear(this.calendarDisplayDate, this.currentDate);
- }
- get disablePastYear() {
- // calendar widget should only go as far back as the passed in start time
- return isSameYear(this.calendarDisplayDate, this.startDate);
- }
- get widgetMonths() {
- const startYear = this.startDate.getFullYear();
- const startMonthIdx = this.startDate.getMonth();
- return ARRAY_OF_MONTHS.map((month, index) => {
- let readonly = false;
-
- // if widget is showing same year as @startTimestamp year, disable if month is before start month
- if (startYear === this.displayYear && index < startMonthIdx) {
- readonly = true;
- }
-
- // if widget showing current year, disable if month is later than current month
- if (this.displayYear === this.currentDate.getFullYear() && index > this.currentDate.getMonth()) {
- readonly = true;
- }
- return {
- index,
- year: this.displayYear,
- name: month,
- readonly,
- };
- });
- }
-
- @action
- addYear() {
- this.calendarDisplayDate = addYears(this.calendarDisplayDate, 1);
- }
-
- @action
- subYear() {
- this.calendarDisplayDate = subYears(this.calendarDisplayDate, 1);
- }
-
- @action
- toggleShowCalendar() {
- this.showCalendar = !this.showCalendar;
- this.calendarDisplayDate = this.endDate;
- }
-
- @action
- handleDateShortcut(dropdown, { target }) {
- this.args.selectMonth({ dateType: target.name }); // send clicked shortcut to parent callback
- this.showCalendar = false;
- dropdown.close();
- }
-
- @action
- selectMonth(month, dropdown) {
- const { index, year, name } = month;
- this.toggleShowCalendar();
- this.args.selectMonth({ monthIdx: index, monthName: name, year, dateType: 'endDate' });
- dropdown.close();
- }
-}
diff --git a/ui/app/components/clients/date-range.hbs b/ui/app/components/clients/date-range.hbs
new file mode 100644
index 0000000000..0d5ed63634
--- /dev/null
+++ b/ui/app/components/clients/date-range.hbs
@@ -0,0 +1,102 @@
+{{!
+ Copyright (c) HashiCorp, Inc.
+ SPDX-License-Identifier: BUSL-1.1
+~}}
+
+
+
+ Date range
+
+
+
+ {{#if (and @startTime @endTime)}}
+
{{this.formattedDate @startTime}}
+
—
+
{{this.formattedDate @endTime}}
+
+ {{else}}
+
+ {{/if}}
+
+
+ {{#if this.showEditModal}}
+
+
+ Edit date range
+
+
+
+ The start date will be used as the client counting start time and all clients in that month will be considered new.
+ {{#if this.version.isEnterprise}}
+ We recommend setting this date as your license or billing start date to get the most accurate new and total
+ client count estimations. These dates are only for querying data in storage. Editing the date range does not
+ change any license or billing configurations.
+ {{/if}}
+
+
+
+ Start
+
+
+
+ End
+
+
+
+
+ {{#if this.validationError}}
+ {{this.validationError}}
+ {{/if}}
+ {{#if this.useDefaultDates}}
+
+ Dashboard will use the default date range from the API.
+
+ {{/if}}
+
+
+
+
+
+
+ {{/if}}
+
\ No newline at end of file
diff --git a/ui/app/components/clients/date-range.ts b/ui/app/components/clients/date-range.ts
new file mode 100644
index 0000000000..4704a7c745
--- /dev/null
+++ b/ui/app/components/clients/date-range.ts
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { action } from '@ember/object';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { formatDateObject } from 'core/utils/client-count-utils';
+import { parseAPITimestamp } from 'core/utils/date-formatters';
+import timestamp from 'core/utils/timestamp';
+import { format } from 'date-fns';
+import type VersionService from 'vault/services/version';
+import type { HTMLElementEvent } from 'forms';
+
+interface OnChangeParams {
+ start_time: number | undefined;
+ end_time: number | undefined;
+}
+interface Args {
+ onChange: (callback: OnChangeParams) => void;
+ startTime: string;
+ endTime: string;
+}
+/**
+ * @module ClientsDateRange
+ * ClientsDateRange components are used to display the current date range and provide a modal interface for editing the date range.
+ *
+ * @example
+ *
+ *
+ *
+ * @param {function} onChange - callback when a new range is saved.
+ * @param {string} [startTime] - ISO string timestamp of the current start date
+ * @param {string} [endTime] - ISO string timestamp of the current end date
+ */
+
+export default class ClientsDateRangeComponent extends Component {
+ @service declare readonly version: VersionService;
+
+ @tracked showEditModal = false;
+ @tracked startDate = ''; // format yyyy-MM
+ @tracked endDate = ''; // format yyyy-MM
+ currentMonth = format(timestamp.now(), 'yyyy-MM');
+
+ constructor(owner: unknown, args: Args) {
+ super(owner, args);
+ this.setTrackedFromArgs();
+ }
+
+ setTrackedFromArgs() {
+ if (this.args.startTime) {
+ this.startDate = parseAPITimestamp(this.args.startTime, 'yyyy-MM') as string;
+ }
+ if (this.args.endTime) {
+ this.endDate = parseAPITimestamp(this.args.endTime, 'yyyy-MM') as string;
+ }
+ }
+
+ formattedDate = (isoTimestamp: string) => {
+ return parseAPITimestamp(isoTimestamp, 'MMMM yyyy');
+ };
+
+ get useDefaultDates() {
+ return !this.startDate && !this.endDate;
+ }
+
+ get validationError() {
+ if (this.useDefaultDates) {
+ // this means we want to reset, which is fine
+ return null;
+ }
+ if (!this.startDate || !this.endDate) {
+ return 'You must supply both start and end dates.';
+ }
+ if (this.startDate > this.endDate) {
+ return 'Start date must be before end date.';
+ }
+ return null;
+ }
+
+ @action onClose() {
+ // since the component never gets torn down, we have to manually re-set this on close
+ this.setTrackedFromArgs();
+ this.showEditModal = false;
+ }
+
+ @action resetDates() {
+ this.startDate = '';
+ this.endDate = '';
+ }
+
+ @action updateDate(evt: HTMLElementEvent) {
+ const { name, value } = evt.target;
+ if (name === 'end') {
+ this.endDate = value;
+ } else {
+ this.startDate = value;
+ }
+ }
+
+ @action handleSave() {
+ if (this.validationError) return;
+ const params: OnChangeParams = {
+ start_time: undefined,
+ end_time: undefined,
+ };
+ if (this.startDate) {
+ const [year, month] = this.startDate.split('-');
+ if (year && month) {
+ params.start_time = formatDateObject({ monthIdx: parseInt(month) - 1, year: parseInt(year) }, false);
+ }
+ }
+ if (this.endDate) {
+ const [year, month] = this.endDate.split('-');
+ if (year && month) {
+ params.end_time = formatDateObject({ monthIdx: parseInt(month) - 1, year: parseInt(year) }, true);
+ }
+ }
+
+ this.args.onChange(params);
+ this.onClose();
+ }
+}
diff --git a/ui/app/components/clients/page/counts.hbs b/ui/app/components/clients/page/counts.hbs
index 91c401bda7..bb369ced1f 100644
--- a/ui/app/components/clients/page/counts.hbs
+++ b/ui/app/components/clients/page/counts.hbs
@@ -18,35 +18,12 @@
Date queries are sent in UTC.
-
- {{this.versionText.label}}
-
-
-
- {{#if this.formattedStartDate}}
-
{{this.formattedStartDate}}
-
- {{else}}
-
- {{/if}}
-
-
-
- {{this.versionText.description}}
-
+
{{#if (eq @activity.id "no-data")}}
@@ -76,11 +53,6 @@
-
{{#if (or @namespace this.namespaces)}}
{{/if}}
{{/if}}
-
-
-{{#if this.showBillingStartModal}}
-
-
- Edit start month
-
-
-
- {{this.versionText.description}}
-
- {{this.versionText.label}}
-
-
-
-
-
-
-{{/if}}
\ No newline at end of file
+
\ No newline at end of file
diff --git a/ui/app/components/clients/page/counts.ts b/ui/app/components/clients/page/counts.ts
index 265c90da17..b2cf003ddd 100644
--- a/ui/app/components/clients/page/counts.ts
+++ b/ui/app/components/clients/page/counts.ts
@@ -6,10 +6,9 @@
import Component from '@glimmer/component';
import { service } from '@ember/service';
import { action } from '@ember/object';
-import { fromUnixTime, getUnixTime, isSameMonth, isAfter } from 'date-fns';
+import { fromUnixTime, isSameMonth, isAfter } from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
-import { filterVersionHistory, formatDateObject } from 'core/utils/client-count-utils';
-import timestamp from 'core/utils/timestamp';
+import { filterVersionHistory } from 'core/utils/client-count-utils';
import type AdapterError from '@ember-data/adapter';
import type FlagsService from 'vault/services/flags';
@@ -94,17 +93,11 @@ export default class ClientsCountsPageComponent extends Component {
get versionText() {
return this.version.isEnterprise
? {
- label: 'Billing start month',
- description:
- 'This date comes from your license, and defines when client counting starts. Without this starting point, the data shown is not reliable.',
title: 'No billing start date found',
message:
'In order to get the most from this data, please enter your billing period start month. This will ensure that the resulting data is accurate.',
}
: {
- label: 'Client counting start date',
- description:
- 'This date is when client counting starts. Without this starting point, the data shown is not reliable.',
title: 'No start date found',
message:
'In order to get the most from this data, please enter a start month above. Vault will calculate new clients starting from that month.',
@@ -174,25 +167,8 @@ export default class ClientsCountsPageComponent extends Component {
}
@action
- onDateChange(dateObject: { dateType: string; monthIdx: number; year: number }) {
- const { dateType, monthIdx, year } = dateObject;
- const { config } = this.args;
- const currentTimestamp = getUnixTime(timestamp.now());
-
- // converts the selectedDate to unix timestamp for activity query
- const selectedDate = formatDateObject({ monthIdx, year }, dateType === 'endDate');
-
- if (dateType !== 'cancel') {
- const start_time = {
- reset: getUnixTime(config?.billingStartTimestamp) || null, // clicked 'Current billing period' in calendar widget -> resets to billing start date
- currentMonth: currentTimestamp, // clicked 'Current month' from calendar widget -> defaults to currentTimestamp
- startDate: selectedDate, // from "Edit billing start" modal
- }[dateType];
- // endDate type is selection from calendar widget
- const end_time = dateType === 'endDate' ? selectedDate : currentTimestamp; // defaults to currentTimestamp
- const params = start_time !== undefined ? { start_time, end_time } : { end_time };
- this.args.onFilterChange(params);
- }
+ onDateChange(params: { start_time: number | undefined; end_time: number | undefined }) {
+ this.args.onFilterChange(params);
}
@action
diff --git a/ui/app/components/date-dropdown.js b/ui/app/components/date-dropdown.js
deleted file mode 100644
index 7fee2b4551..0000000000
--- a/ui/app/components/date-dropdown.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import Component from '@glimmer/component';
-import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
-import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
-import timestamp from 'core/utils/timestamp';
-/**
- * @module DateDropdown
- * DateDropdown components are used to display a dropdown of months and years to handle date selection. Future dates are disabled (current month and year are selectable).
- * The component returns an object with selected date info, example: { dateType: 'start', monthIdx: 0, monthName: 'January', year: 2022 }
- *
- * @example
- * ```js
- *
- * ```
- * @param {function} handleSubmit - callback function from parent that the date picker triggers on submit
- * @param {string} [dateType] - optional argument to give the selected month/year a type
- * @param {string} [submitText] - optional argument to change submit button text
- * @param {function} [validateDate] - parent function to validate date selection, receives date object and returns an error message that's passed to the inline alert
- */
-export default class DateDropdown extends Component {
- currentDate = timestamp.now();
- currentYear = this.currentDate.getFullYear(); // integer of year
- currentMonthIdx = this.currentDate.getMonth(); // integer of month, 0 indexed
- dropdownMonths = ARRAY_OF_MONTHS.map((m, i) => ({ name: m, index: i }));
- dropdownYears = Array.from({ length: 5 }, (item, i) => this.currentYear - i);
-
- @tracked maxMonthIdx = 11; // disables months with index greater than this number, initially all months are selectable
- @tracked disabledYear = null; // year as integer if current year should be disabled
- @tracked selectedMonth = null;
- @tracked selectedYear = null;
- @tracked invalidDate = null;
-
- @action
- selectMonth(month, dropdown) {
- this.selectedMonth = month;
- // disable current year if selected month is later than current month
- this.disabledYear = month.index > this.currentMonthIdx ? this.currentYear : null;
- dropdown.close();
- }
-
- @action
- selectYear(year, dropdown) {
- this.selectedYear = year;
- // disable months after current month if selected year is current year
- this.maxMonthIdx = year === this.currentYear ? this.currentMonthIdx : 11;
- dropdown.close();
- }
-
- @action
- handleSubmit() {
- if (this.args.validateDate) {
- this.invalidDate = null;
- this.invalidDate = this.args.validateDate(new Date(this.selectedYear, this.selectedMonth.index));
- if (this.invalidDate) return;
- }
- const { index, name } = this.selectedMonth;
- this.args.handleSubmit({
- monthIdx: index,
- monthName: name,
- year: this.selectedYear,
- dateType: this.args.dateType,
- });
- this.resetDropdown();
- }
-
- resetDropdown() {
- this.maxMonthIdx = 11;
- this.disabledYear = null;
- this.selectedMonth = null;
- this.selectedYear = null;
- this.invalidDate = null;
- }
-}
diff --git a/ui/app/routes/vault/cluster/clients/counts.ts b/ui/app/routes/vault/cluster/clients/counts.ts
index 3c87c2922e..b2322970db 100644
--- a/ui/app/routes/vault/cluster/clients/counts.ts
+++ b/ui/app/routes/vault/cluster/clients/counts.ts
@@ -13,8 +13,6 @@ import type StoreService from 'vault/services/store';
import type VersionService from 'vault/services/version';
import type { ModelFrom } from 'vault/vault/route';
import type ClientsRoute from '../clients';
-import type ClientsActivityModel from 'vault/models/clients/activity';
-import type ClientsConfigModel from 'vault/models/clients/config';
import type ClientsCountsController from 'vault/controllers/vault/cluster/clients/counts';
import { setStartTimeQuery } from 'core/utils/client-count-utils';
diff --git a/ui/app/styles/components/clients-date-range.scss b/ui/app/styles/components/clients-date-range.scss
new file mode 100644
index 0000000000..e908211109
--- /dev/null
+++ b/ui/app/styles/components/clients-date-range.scss
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+.clients-date-range-display {
+ display: flex;
+ align-items: bottom;
+ > * {
+ margin-right: $spacing-8;
+ vertical-align: bottom;
+ align-self: end;
+ }
+}
diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss
index 84882a7bd3..2d020970b7 100644
--- a/ui/app/styles/core.scss
+++ b/ui/app/styles/core.scss
@@ -59,6 +59,7 @@
@import './components/box-label';
@import './components/calendar-widget';
@import './components/chart-container';
+@import './components/clients-date-range';
@import './components/cluster-banners';
@import './components/codemirror';
@import './components/console-ui-panel';
diff --git a/ui/app/templates/components/date-dropdown.hbs b/ui/app/templates/components/date-dropdown.hbs
deleted file mode 100644
index d154886f6a..0000000000
--- a/ui/app/templates/components/date-dropdown.hbs
+++ /dev/null
@@ -1,38 +0,0 @@
-{{!
- Copyright (c) HashiCorp, Inc.
- SPDX-License-Identifier: BUSL-1.1
-~}}
-
-
-
-
- {{#each this.dropdownMonths as |month|}}
-
- {{/each}}
-
-
-
- {{#each this.dropdownYears as |year|}}
-
- {{/each}}
-
-
-
-{{#if this.invalidDate}}
-
-{{/if}}
\ No newline at end of file
diff --git a/ui/tests/acceptance/clients/counts-test.js b/ui/tests/acceptance/clients/counts-test.js
index f598b75cf6..4c283f4b83 100644
--- a/ui/tests/acceptance/clients/counts-test.js
+++ b/ui/tests/acceptance/clients/counts-test.js
@@ -8,7 +8,7 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import clientsHandler, { STATIC_NOW } from 'vault/mirage/handlers/clients';
import sinon from 'sinon';
-import { visit, click, currentURL } from '@ember/test-helpers';
+import { visit, click, currentURL, fillIn } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
@@ -42,14 +42,20 @@ 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.rangeDropdown);
- await click(CLIENT_COUNT.currentBillingPeriod);
- const timeQueryRegex = /end_time=\d+&start_time=\d+/g;
- assert.ok(currentURL().match(timeQueryRegex).length, 'Start and end times added as query params');
+ 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 click(GENERAL.saveButton);
+ assert.strictEqual(
+ currentURL(),
+ '/vault/clients/counts/overview?end_time=1706659200&start_time=1643673600',
+ 'Start and end times added as query params'
+ );
await click(GENERAL.tab('token'));
- assert.ok(
- currentURL().match(timeQueryRegex).length,
+ assert.strictEqual(
+ currentURL(),
+ '/vault/clients/counts/token?end_time=1706659200&start_time=1643673600',
'Start and end times persist through child route change'
);
diff --git a/ui/tests/acceptance/clients/counts/overview-test.js b/ui/tests/acceptance/clients/counts/overview-test.js
index 2251f64db8..6e5ba2dedc 100644
--- a/ui/tests/acceptance/clients/counts/overview-test.js
+++ b/ui/tests/acceptance/clients/counts/overview-test.js
@@ -9,9 +9,8 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import clientsHandler, { STATIC_NOW, LICENSE_START, UPGRADE_DATE } from 'vault/mirage/handlers/clients';
import syncHandler from 'vault/mirage/handlers/sync';
import sinon from 'sinon';
-import { visit, click, findAll, settled } from '@ember/test-helpers';
+import { visit, click, findAll, settled, fillIn } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth';
-import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { CHARTS, CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
import { create } from 'ember-cli-page-object';
@@ -19,6 +18,7 @@ import { clickTrigger } from 'ember-power-select/test-support/helpers';
import { formatNumber } from 'core/helpers/format-number';
import timestamp from 'core/utils/timestamp';
import ss from 'vault/tests/pages/components/search-select';
+import { format } from 'date-fns';
const searchSelect = create(ss);
@@ -36,11 +36,11 @@ module('Acceptance | clients | overview', function (hooks) {
test('it should render charts', async function (assert) {
assert
- .dom(CLIENT_COUNT.counts.startMonth)
+ .dom(CLIENT_COUNT.dateRange.dateDisplay('start'))
.hasText('July 2023', 'billing start month is correctly parsed from license');
assert
- .dom(CLIENT_COUNT.rangeDropdown)
- .hasText('Jul 2023 - Jan 2024', 'Date range shows dates correctly parsed activity response');
+ .dom(CLIENT_COUNT.dateRange.dateDisplay('end'))
+ .hasText('January 2024', 'billing start month is correctly parsed from license');
assert.dom(CLIENT_COUNT.attributionBlock).exists('Shows attribution area');
assert
.dom(CHARTS.container('Vault client counts'))
@@ -53,12 +53,13 @@ module('Acceptance | clients | overview', function (hooks) {
test('it should update charts when querying date ranges', async function (assert) {
// query for single, historical month with no new counts (July 2023)
- await click(CLIENT_COUNT.rangeDropdown);
- await click(CLIENT_COUNT.calendarWidget.customEndMonth);
- await click(CLIENT_COUNT.calendarWidget.previousYear);
+ const licenseStartMonth = format(LICENSE_START, 'yyyy-MM');
+ const upgradeMonth = format(UPGRADE_DATE, 'yyyy-MM');
+ await click(CLIENT_COUNT.dateRange.edit);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), licenseStartMonth);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('end'), licenseStartMonth);
- const month = ARRAY_OF_MONTHS[LICENSE_START.getMonth()];
- await click(CLIENT_COUNT.calendarWidget.calendarMonth(month));
+ await click(GENERAL.saveButton);
assert
.dom(CLIENT_COUNT.usageStats('Vault client counts'))
.doesNotExist('running total single month stat boxes do not show');
@@ -75,16 +76,15 @@ module('Acceptance | clients | overview', function (hooks) {
assert.dom(CHARTS.container('total-clients')).exists('total client attribution chart shows');
// reset to billing period
- await click(CLIENT_COUNT.rangeDropdown);
- await click(CLIENT_COUNT.currentBillingPeriod);
+ await click(CLIENT_COUNT.dateRange.edit);
+ await click(CLIENT_COUNT.dateRange.reset);
+ await click(GENERAL.saveButton);
+
+ // change to start on month/year of upgrade to 1.10
+ await click(CLIENT_COUNT.dateRange.edit);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), upgradeMonth);
+ await click(GENERAL.saveButton);
- // change billing start to month/year of upgrade to 1.10
- await click(CLIENT_COUNT.counts.startEdit);
- await click(CLIENT_COUNT.monthDropdown);
- await click(CLIENT_COUNT.dateDropdown.selectMonth(ARRAY_OF_MONTHS[UPGRADE_DATE.getMonth()]));
- await click(CLIENT_COUNT.yearDropdown);
- await click(CLIENT_COUNT.dateDropdown.selectYear(UPGRADE_DATE.getFullYear()));
- await click(CLIENT_COUNT.dateDropdown.submit);
assert.dom(CLIENT_COUNT.attributionBlock).exists('Shows attribution area');
assert
.dom(CHARTS.container('Vault client counts'))
@@ -95,11 +95,11 @@ module('Acceptance | clients | overview', function (hooks) {
assert.strictEqual(findAll(CHARTS.plotPoint).length, 5, 'line chart plots 5 points to match query');
// query for single, historical month (upgrade month)
- await click(CLIENT_COUNT.rangeDropdown);
- await click(CLIENT_COUNT.calendarWidget.customEndMonth);
- assert.dom(CLIENT_COUNT.calendarWidget.displayYear).hasText('2024');
- await click(CLIENT_COUNT.calendarWidget.previousYear);
- await click(CLIENT_COUNT.calendarWidget.calendarMonth('September'));
+ await click(CLIENT_COUNT.dateRange.edit);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), upgradeMonth);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('end'), upgradeMonth);
+ await click(GENERAL.saveButton);
+
assert
.dom(CLIENT_COUNT.usageStats('Vault client counts'))
.exists('running total single month usage stats show');
@@ -111,9 +111,10 @@ module('Acceptance | clients | overview', function (hooks) {
assert.dom(CHARTS.container('total-clients')).exists('total client attribution chart shows');
// query historical date range (from September 2023 to December 2023)
- await click(CLIENT_COUNT.rangeDropdown);
- await click(CLIENT_COUNT.calendarWidget.customEndMonth);
- await click(CLIENT_COUNT.calendarWidget.calendarMonth('December'));
+ await click(CLIENT_COUNT.dateRange.edit);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2023-09');
+ await fillIn(CLIENT_COUNT.dateRange.editDate('end'), '2023-12');
+ await click(GENERAL.saveButton);
assert.dom(CLIENT_COUNT.attributionBlock).exists('Shows attribution area');
assert
@@ -126,15 +127,15 @@ module('Acceptance | clients | overview', function (hooks) {
.hasText('12/23', 'x-axis labels end with queried end month');
// reset to billing period
- await click(CLIENT_COUNT.rangeDropdown);
- await click(CLIENT_COUNT.currentBillingPeriod);
+ await click(CLIENT_COUNT.dateRange.edit);
+ await click(CLIENT_COUNT.dateRange.reset);
+ await click(GENERAL.saveButton);
+
// query month older than count start date
- await click(CLIENT_COUNT.counts.startEdit);
- await click(CLIENT_COUNT.monthDropdown);
- await click(CLIENT_COUNT.dateDropdown.selectMonth(ARRAY_OF_MONTHS[LICENSE_START.getMonth()]));
- await click(CLIENT_COUNT.yearDropdown);
- await click(CLIENT_COUNT.dateDropdown.selectYear(LICENSE_START.getFullYear() - 3));
- await click(CLIENT_COUNT.dateDropdown.submit);
+ await click(CLIENT_COUNT.dateRange.edit);
+ await fillIn(CLIENT_COUNT.dateRange.editDate('start'), '2020-07');
+ await click(GENERAL.saveButton);
+
assert
.dom(CLIENT_COUNT.counts.startDiscrepancy)
.hasTextContaining(
diff --git a/ui/tests/helpers/clients/client-count-helpers.js b/ui/tests/helpers/clients/client-count-helpers.js
index d433c9f095..a4d38a723a 100644
--- a/ui/tests/helpers/clients/client-count-helpers.js
+++ b/ui/tests/helpers/clients/client-count-helpers.js
@@ -3,18 +3,8 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { click, findAll } from '@ember/test-helpers';
-import { CLIENT_COUNT, CHARTS } from './client-count-selectors';
-
-export async function dateDropdownSelect(month, year) {
- const { dateDropdown, counts } = CLIENT_COUNT;
- await click(counts.startEdit);
- await click(dateDropdown.toggleMonth);
- await click(dateDropdown.selectMonth(month));
- await click(dateDropdown.toggleYear);
- await click(dateDropdown.selectYear(year));
- await click(dateDropdown.submit);
-}
+import { findAll } from '@ember/test-helpers';
+import { CHARTS } from './client-count-selectors';
export function assertBarChart(assert, chartName, byMonthData, isStacked = false) {
// assertion count is byMonthData.length, plus 2
diff --git a/ui/tests/helpers/clients/client-count-selectors.ts b/ui/tests/helpers/clients/client-count-selectors.ts
index 4d329ff619..53691cfabf 100644
--- a/ui/tests/helpers/clients/client-count-selectors.ts
+++ b/ui/tests/helpers/clients/client-count-selectors.ts
@@ -6,44 +6,28 @@
// TODO: separate nested into distinct exported consts
export const CLIENT_COUNT = {
counts: {
- startLabel: '[data-test-counts-start-label]',
description: '[data-test-counts-description]',
- startMonth: '[data-test-counts-start-month]',
- startEdit: '[data-test-counts-start-edit]',
- startDropdown: '[data-test-counts-start-dropdown]',
configDisabled: '[data-test-counts-disabled]',
namespaces: '[data-test-counts-namespaces]',
mountPaths: '[data-test-counts-auth-mounts]',
startDiscrepancy: '[data-test-counts-start-discrepancy]',
},
+ 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}"]`,
+ reset: '[data-test-date-edit="reset"]',
+ defaultRangeAlert: '[data-test-range-default-alert]',
+ validation: '[data-test-date-range-validation]',
+ },
statText: (label: string) => `[data-test-stat-text="${label}"]`,
statTextValue: (label: string) =>
label ? `[data-test-stat-text="${label}"] .stat-value` : '[data-test-stat-text]',
usageStats: (title: string) => `[data-test-usage-stats="${title}"]`,
- dateDisplay: '[data-test-date-display]',
attributionBlock: '[data-test-clients-attribution]',
filterBar: '[data-test-clients-filter-bar]',
- rangeDropdown: '[data-test-calendar-widget-trigger]',
- monthDropdown: '[data-test-toggle-month]',
- yearDropdown: '[data-test-toggle-year]',
- currentBillingPeriod: '[data-test-current-billing-period]',
- dateDropdown: {
- toggleMonth: '[data-test-toggle-month]',
- toggleYear: '[data-test-toggle-year]',
- selectMonth: (month: string) => `[data-test-dropdown-month="${month}"]`,
- selectYear: (year: string) => `[data-test-dropdown-year="${year}"]`,
- submit: '[data-test-date-dropdown-submit]',
- },
- calendarWidget: {
- trigger: '[data-test-calendar-widget-trigger]',
- currentMonth: '[data-test-current-month]',
- currentBillingPeriod: '[data-test-current-billing-period]',
- customEndMonth: '[data-test-show-calendar]',
- previousYear: '[data-test-previous-year]',
- nextYear: '[data-test-next-year]',
- displayYear: '[data-test-display-year]',
- calendarMonth: (month: string) => `[data-test-calendar-month="${month}"]`,
- },
selectedAuthMount: 'div#mounts-search-select [data-test-selected-option] div',
selectedNs: 'div#namespace-search-select [data-test-selected-option] div',
upgradeWarning: '[data-test-clients-upgrade-warning]',
diff --git a/ui/tests/integration/components/calendar-widget-test.js b/ui/tests/integration/components/calendar-widget-test.js
deleted file mode 100644
index 46a1a19061..0000000000
--- a/ui/tests/integration/components/calendar-widget-test.js
+++ /dev/null
@@ -1,240 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, click } from '@ember/test-helpers';
-import sinon from 'sinon';
-import hbs from 'htmlbars-inline-precompile';
-import calendarDropdown from 'vault/tests/pages/components/calendar-widget';
-import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
-import { subMonths, subYears } from 'date-fns';
-import timestamp from 'core/utils/timestamp';
-
-module('Integration | Component | calendar-widget', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- sinon.replace(timestamp, 'now', sinon.fake.returns(new Date('2018-04-03T14:15:30')));
- const CURRENT_DATE = timestamp.now();
- this.set('currentDate', CURRENT_DATE);
- this.set('calendarStartDate', subMonths(CURRENT_DATE, 12));
- this.set('calendarEndDate', CURRENT_DATE);
- this.set('startTimestamp', subMonths(CURRENT_DATE, 12).toISOString());
- this.set('endTimestamp', CURRENT_DATE.toISOString());
- this.set('handleClientActivityQuery', sinon.spy());
- });
-
- test('it renders and disables correct months when start date is 12 months ago', async function (assert) {
- assert.expect(14);
- await render(hbs`
-
- `);
- assert
- .dom('[data-test-calendar-widget-trigger]')
- .hasText(`Apr 2017 - Apr 2018`, 'renders and formats start and end dates');
- await calendarDropdown.openCalendar();
- assert.ok(calendarDropdown.showsCalendar, 'renders the calendar component');
- // assert months in current year are disabled/enabled correctly
- const enabledMonths = ['January', 'February', 'March', 'April'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (enabledMonths.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is disabled`);
- }
- });
- });
-
- test('it renders and disables months before start timestamp', async function (assert) {
- await render(hbs`
-
- `);
-
- await calendarDropdown.openCalendar();
- assert.dom('[data-test-next-year]').isDisabled('Future year is disabled');
- await calendarDropdown.clickPreviousYear();
- assert
- .dom('[data-test-display-year]')
- .hasText(`${subYears(this.currentDate, 1).getFullYear()}`, 'shows the previous year');
- assert.dom('[data-test-previous-year]').isDisabled('disables previous year');
-
- // assert months in previous year are disabled/enabled correctly
- const disabledMonths = ['January', 'February', 'March'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (disabledMonths.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is disabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- }
- });
- });
-
- test('it calls parent callback with correct arg when clicking "Current billing period"', async function (assert) {
- await render(hbs`
-
- `);
- await calendarDropdown.menuToggle();
- await calendarDropdown.clickCurrentBillingPeriod();
- assert.propEqual(
- this.handleClientActivityQuery.args[0][0],
- { dateType: 'reset' },
- 'it calls parent function with reset dateType'
- );
- });
-
- test('it calls parent callback with correct arg when clicking "Current month"', async function (assert) {
- await render(hbs`
-
- `);
- await calendarDropdown.menuToggle();
- await calendarDropdown.clickCurrentMonth();
- assert.propEqual(
- this.handleClientActivityQuery.args[0][0],
- { dateType: 'currentMonth' },
- 'it calls parent function with currentMoth dateType'
- );
- });
-
- test('it calls parent callback with correct arg when selecting a month', async function (assert) {
- await render(hbs`
-
- `);
- await calendarDropdown.openCalendar();
- await click(`[data-test-calendar-month="April"`);
- assert.propEqual(
- this.handleClientActivityQuery.lastCall.lastArg,
- {
- dateType: 'endDate',
- monthIdx: 3,
- monthName: 'April',
- year: 2018,
- },
- 'it calls parent function with end date (current) month/year'
- );
-
- await calendarDropdown.openCalendar();
- await calendarDropdown.clickPreviousYear();
- await click(`[data-test-calendar-month="May"]`);
- assert.propEqual(
- this.handleClientActivityQuery.lastCall.lastArg,
- {
- dateType: 'endDate',
- monthIdx: 4,
- monthName: 'May',
- year: 2017,
- },
- 'it calls parent function with selected start date month/year'
- );
- });
-
- test('it disables correct months when start date 6 months ago', async function (assert) {
- this.set('calendarStartDate', subMonths(this.currentDate, 6)); // Nov 3, 2017
- this.set('startTimestamp', subMonths(this.currentDate, 6).toISOString());
- await render(hbs`
-
- `);
-
- await calendarDropdown.openCalendar();
- assert.dom('[data-test-next-year]').isDisabled('Future year is disabled');
-
- // Check start year disables correct months
- await calendarDropdown.clickPreviousYear();
- assert.dom('[data-test-previous-year]').isDisabled('previous year is disabled');
- const prevYearEnabled = ['October', 'November', 'December'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (prevYearEnabled.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is read only`);
- }
- });
-
- // Check end year disables correct months
- await click('[data-test-next-year]');
- const currYearEnabled = ['January', 'February', 'March', 'April'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (currYearEnabled.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is disabled`);
- }
- });
- });
-
- test('it disables correct months when start date 36 months ago', async function (assert) {
- this.set('calendarStartDate', subMonths(this.currentDate, 36)); // April 3 2015
- this.set('startTimestamp', subMonths(this.currentDate, 36).toISOString());
- await render(hbs`
-
- `);
-
- await calendarDropdown.openCalendar();
- assert.dom('[data-test-next-year]').isDisabled('Future year is disabled');
- for (const year of [2017, 2016, 2015]) {
- await calendarDropdown.clickPreviousYear();
- assert.dom('[data-test-display-year]').hasText(year.toString());
- }
-
- assert.dom('[data-test-previous-year]').isDisabled('previous year is disabled');
- assert.dom('[data-test-next-year]').isEnabled('next year is enabled');
-
- const disabledMonths = ['January', 'February', 'March'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (disabledMonths.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is disabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- }
- });
-
- await click('[data-test-next-year]');
- ARRAY_OF_MONTHS.forEach(function (month) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled for 2016`);
- });
- await click('[data-test-next-year]');
- ARRAY_OF_MONTHS.forEach(function (month) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled for 2017`);
- });
- await click('[data-test-next-year]');
-
- const enabledMonths = ['January', 'February', 'March', 'April'];
- ARRAY_OF_MONTHS.forEach(function (month) {
- if (enabledMonths.includes(month)) {
- assert.dom(`[data-test-calendar-month="${month}"]`).isNotDisabled(`${month} is enabled`);
- } else {
- assert.dom(`[data-test-calendar-month="${month}"]`).isDisabled(`${month} is disabled`);
- }
- });
- });
-});
diff --git a/ui/tests/integration/components/clients/date-range-test.js b/ui/tests/integration/components/clients/date-range-test.js
new file mode 100644
index 0000000000..beec2fe491
--- /dev/null
+++ b/ui/tests/integration/components/clients/date-range-test.js
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'vault/tests/helpers';
+import { click, fillIn, render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+import Sinon from 'sinon';
+import timestamp from 'core/utils/timestamp';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
+
+const DATE_RANGE = CLIENT_COUNT.dateRange;
+module('Integration | Component | clients/date-range', function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(function () {
+ Sinon.replace(timestamp, 'now', Sinon.fake.returns(new Date('2018-04-03T14:15:30')));
+ this.now = timestamp.now();
+ this.startTime = '2018-01-01T14:15:30';
+ this.endTime = '2019-01-31T14:15:30';
+ this.onChange = Sinon.spy();
+ this.renderComponent = async () => {
+ await render(
+ hbs` `
+ );
+ };
+ });
+
+ test('it renders prompt to set dates if no start time', async function (assert) {
+ this.startTime = undefined;
+ await this.renderComponent();
+
+ assert.dom(DATE_RANGE.set).exists();
+
+ await click(DATE_RANGE.set);
+ assert.dom(DATE_RANGE.editModal).exists();
+ assert.dom(DATE_RANGE.editDate('start')).hasValue('');
+ await fillIn(DATE_RANGE.editDate('start'), '2018-01');
+ await fillIn(DATE_RANGE.editDate('end'), '2019-01');
+ await click(GENERAL.saveButton);
+ assert.deepEqual(this.onChange.args[0], [
+ {
+ end_time: 1548892800,
+ start_time: 1514764800,
+ },
+ ]);
+ assert.dom(DATE_RANGE.editModal).doesNotExist('closes modal');
+ });
+
+ test('it renders the date range passed and can reset it', async function (assert) {
+ await this.renderComponent();
+
+ assert.dom(DATE_RANGE.dateDisplay('start')).hasText('January 2018');
+ assert.dom(DATE_RANGE.dateDisplay('end')).hasText('January 2019');
+
+ 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.defaultRangeAlert).exists();
+ await click(GENERAL.saveButton);
+ assert.deepEqual(this.onChange.args[0], [{ start_time: undefined, end_time: undefined }]);
+ });
+
+ test('it does not trigger onChange if date range invalid', async function (assert) {
+ await this.renderComponent();
+
+ await click(DATE_RANGE.edit);
+ await click(DATE_RANGE.editDate('reset'));
+ await fillIn(DATE_RANGE.editDate('end'), '2017-05');
+ assert.dom(DATE_RANGE.validation).hasText('You must supply both start and end dates.');
+ await click(GENERAL.saveButton);
+ assert.false(this.onChange.called);
+
+ await fillIn(DATE_RANGE.editDate('start'), '2018-01');
+ assert.dom(DATE_RANGE.validation).hasText('Start date must be before end date.');
+ await click(GENERAL.saveButton);
+ assert.false(this.onChange.called);
+
+ await click(GENERAL.cancelButton);
+ assert.false(this.onChange.called);
+ assert.dom(DATE_RANGE.editModal).doesNotExist();
+ });
+
+ test('it resets the tracked values on close', async function (assert) {
+ await this.renderComponent();
+
+ await click(DATE_RANGE.edit);
+ await click(DATE_RANGE.editDate('reset'));
+ assert.dom(DATE_RANGE.editDate('start')).hasValue('');
+ assert.dom(DATE_RANGE.editDate('end')).hasValue('');
+ await click(GENERAL.cancelButton);
+
+ await click(DATE_RANGE.edit);
+ assert.dom(DATE_RANGE.editDate('start')).hasValue('2018-01');
+ assert.dom(DATE_RANGE.editDate('end')).hasValue('2019-01');
+ });
+});
diff --git a/ui/tests/integration/components/clients/page/counts-test.js b/ui/tests/integration/components/clients/page/counts-test.js
index d4a3cca34c..4577d695c6 100644
--- a/ui/tests/integration/components/clients/page/counts-test.js
+++ b/ui/tests/integration/components/clients/page/counts-test.js
@@ -6,13 +6,12 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
-import { render, click, settled, findAll } from '@ember/test-helpers';
+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 { GENERAL } from 'vault/tests/helpers/general-selectors';
import { CLIENT_COUNT } from 'vault/tests/helpers/clients/client-count-selectors';
-import { dateDropdownSelect } from 'vault/tests/helpers/clients/client-count-helpers';
import { selectChoose } from 'ember-power-select/test-support';
import timestamp from 'core/utils/timestamp';
import sinon from 'sinon';
@@ -57,38 +56,11 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
`);
});
- test('it should render start date label and description based on version', async function (assert) {
- const versionService = this.owner.lookup('service:version');
-
- await this.renderComponent();
-
- assert.dom(CLIENT_COUNT.counts.startLabel).hasText('Client counting start date', 'Label renders for OSS');
- assert
- .dom(CLIENT_COUNT.counts.description)
- .hasText(
- 'This date is when client counting starts. Without this starting point, the data shown is not reliable.',
- 'Description renders for OSS'
- );
-
- versionService.set('type', 'enterprise');
- await settled();
-
- assert.dom(CLIENT_COUNT.counts.startLabel).hasText('Billing start month', 'Label renders for Enterprise');
- assert
- .dom(CLIENT_COUNT.counts.description)
- .hasText(
- 'This date comes from your license, and defines when client counting starts. Without this starting point, the data shown is not reliable.',
- 'Description renders for Enterprise'
- );
- });
-
test('it should populate start and end month displays', async function (assert) {
await this.renderComponent();
- assert.dom(CLIENT_COUNT.counts.startMonth).hasText('July 2023', 'Start month renders');
- assert
- .dom(CLIENT_COUNT.calendarWidget.trigger)
- .hasText('Jul 2023 - Jan 2024', 'Start and end months render in filter bar');
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('start')).hasText('July 2023', 'Start month renders');
+ assert.dom(CLIENT_COUNT.dateRange.dateDisplay('end')).hasText('January 2024', 'End month renders');
});
test('it should render no data empty state', async function (assert) {
@@ -123,31 +95,41 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
});
test('it should send correct values on start and end date change', async function (assert) {
- assert.expect(4);
+ 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'));
- let expected = { start_time: getUnixTime(new Date('2023-01-01T00:00:00Z')), end_time: END_TIME };
+ const expected = { start_time: START_TIME, end_time: END_TIME };
this.onFilterChange = (params) => {
assert.deepEqual(params, expected, 'Correct values sent on filter change');
- this.startTimestamp = params.start_time || START_TIME;
- this.endTimestamp = params.end_time || END_TIME;
+ 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();
- await dateDropdownSelect('January', '2023');
- expected.start_time = END_TIME;
- await click(CLIENT_COUNT.calendarWidget.trigger);
- await click(CLIENT_COUNT.calendarWidget.currentMonth);
+ // 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);
- expected.start_time = getUnixTime(this.config.billingStartTimestamp);
- await click(CLIENT_COUNT.calendarWidget.trigger);
- await click(CLIENT_COUNT.calendarWidget.currentBillingPeriod);
+ // 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);
- expected = { end_time: getUnixTime(new Date('2023-12-31T00:00:00Z')) };
- await click(CLIENT_COUNT.calendarWidget.trigger);
- await click(CLIENT_COUNT.calendarWidget.customEndMonth);
- await click(CLIENT_COUNT.calendarWidget.previousYear);
- await click(CLIENT_COUNT.calendarWidget.calendarMonth('December'));
+ // 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);
});
test('it should render namespace and auth mount filters', async function (assert) {
@@ -254,9 +236,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.counts.startDropdown)
- .exists('Date dropdown renders when start time is not provided');
+ assert.dom(CLIENT_COUNT.dateRange.set).exists();
});
test('it should render catch all empty state', async function (assert) {
diff --git a/ui/tests/integration/components/date-dropdown-test.js b/ui/tests/integration/components/date-dropdown-test.js
deleted file mode 100644
index 56a5654754..0000000000
--- a/ui/tests/integration/components/date-dropdown-test.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import sinon from 'sinon';
-import { setupRenderingTest } from 'ember-qunit';
-import { click, render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
-import timestamp from 'core/utils/timestamp';
-
-const SELECTORS = {
- monthDropdown: '[data-test-toggle-month]',
- specificMonth: (m) => `[data-test-dropdown-month="${m}"]`,
- yearDropdown: '[data-test-toggle-year]',
- specificYear: (y) => `[data-test-dropdown-year="${y}"]`,
- submitButton: '[data-test-date-dropdown-submit]',
- monthOptions: '[data-test-dropdown-month]',
-};
-
-module('Integration | Component | date-dropdown', function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- sinon.replace(timestamp, 'now', sinon.fake.returns(new Date('2018-04-03T14:15:30')));
- });
-
- test('it renders dropdown', async function (assert) {
- await render(hbs`
-
-
-
- `);
- assert.dom(SELECTORS.submitButton).hasText('Submit', 'button renders default text');
- });
-
- test('it renders dropdown and selects month and year', async function (assert) {
- assert.expect(26);
- const parentAction = (args) => {
- assert.propEqual(
- args,
- {
- dateType: 'start',
- monthIdx: 1,
- monthName: 'February',
- year: 2016,
- },
- 'sends correct args to parent'
- );
- };
- this.set('parentAction', parentAction);
-
- await render(hbs`
-
-
-
- `);
- assert.dom(SELECTORS.submitButton).isDisabled('button is disabled when no month or year selected');
-
- await click(SELECTORS.monthDropdown);
-
- assert.dom(SELECTORS.monthOptions).exists({ count: 12 }, 'dropdown has 12 months');
- ARRAY_OF_MONTHS.forEach((month) => {
- assert.dom(SELECTORS.specificMonth(month)).hasText(`${month}`, `dropdown includes ${month}`);
- });
-
- await click(SELECTORS.specificMonth('February'));
- assert.dom(SELECTORS.monthDropdown).hasText('February', 'dropdown shows selected month');
- assert.dom('.ember-basic-dropdown-content').doesNotExist('dropdown closes after selecting month');
-
- await click(SELECTORS.yearDropdown);
-
- assert.dom('[data-test-dropdown-year]').exists({ count: 5 }, 'dropdown has 5 years');
- for (const year of [2018, 2017, 2016, 2015, 2014]) {
- assert.dom(SELECTORS.specificYear(year)).exists();
- }
-
- await click('[data-test-dropdown-year="2016"]');
- assert.dom(SELECTORS.yearDropdown).hasText(`2016`, `dropdown shows selected year`);
- assert.dom('.ember-basic-dropdown-content').doesNotExist('dropdown closes after selecting year');
- assert.dom(SELECTORS.submitButton).isNotDisabled('button enabled when month and year selected');
-
- await click(SELECTORS.submitButton);
- });
-
- test('selecting month first: current year enabled when current month selected', async function (assert) {
- assert.expect(5);
- await render(hbs`
-
-
-
- `);
- // select current month
- await click(SELECTORS.monthDropdown);
- await click(SELECTORS.specificMonth('January'));
- await click(SELECTORS.yearDropdown);
- // all years should be selectable
- for (const year of [2018, 2017, 2016, 2015, 2014]) {
- assert.dom(SELECTORS.specificYear(year)).isNotDisabled(`year ${year} is selectable`);
- }
- });
-
- test('selecting month first: it disables current year when future months selected', async function (assert) {
- assert.expect(5);
- await render(hbs`
-
-
-
- `);
-
- // select future month
- await click(SELECTORS.monthDropdown);
- await click(SELECTORS.specificMonth('June'));
- await click(SELECTORS.yearDropdown);
-
- assert.dom(SELECTORS.specificYear(2018)).isDisabled(`current year is disabled`);
- // previous years should be selectable
- for (const year of [2017, 2016, 2015, 2014]) {
- assert.dom(SELECTORS.specificYear(year)).isNotDisabled(`year ${year} is selectable`);
- }
- });
-
- test('selecting year first: it disables future months when current year selected', async function (assert) {
- assert.expect(12);
- await render(hbs`
-
-
-
- `);
- await click(SELECTORS.yearDropdown);
- await click(SELECTORS.specificYear(2018));
- await click(SELECTORS.monthDropdown);
-
- const expectedSelectable = ['January', 'February', 'March', 'April'];
- ARRAY_OF_MONTHS.forEach((month) => {
- if (expectedSelectable.includes(month)) {
- assert.dom(SELECTORS.specificMonth(month)).isNotDisabled(`${month} is selectable for current year`);
- } else {
- assert.dom(SELECTORS.specificMonth(month)).isDisabled(`${month} is disabled for current year`);
- }
- });
- });
-
- test('selecting year first: it enables all months when past year is selected', async function (assert) {
- assert.expect(12);
- await render(hbs`
-
-
-
- `);
-
- await click(SELECTORS.yearDropdown);
- await click(SELECTORS.specificYear(2017));
- await click(SELECTORS.monthDropdown);
-
- ARRAY_OF_MONTHS.forEach((month) => {
- assert.dom(SELECTORS.specificMonth(month)).isNotDisabled(`${month} is selectable for previous year`);
- });
- });
-});