UI: Return upgrade banner to refactored client count UI (#25733)

* refactor upgradesDuringActivity to return an array

* move filterVersionHistory to util

* remove icon from inline link

* chance copy

* VAULT-24541 change ticket purpose

* add empty string for default break

* remove 1.16 for now

* update copy

* update test param

* add test for upgrade alert banner

* add version-history to mirage and consolidate time variables

* cleanup/fix imports after removing consts from helper file

* update more test dates

* fix attribution date format

* refactor util to just take timestamps

* util test

* use isWithinInterval instead

* finish count and overview updates after fixing mirage

* use the same static_time for all clients/ test files

* remove floating dot
This commit is contained in:
claire bontempo 2024-03-04 21:33:43 -08:00 committed by GitHub
parent d8cb4247c8
commit 164a1af54a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 793 additions and 620 deletions

View File

@ -7,9 +7,10 @@
// contains getters that filter and extract data from activity model for use in charts
import Component from '@glimmer/component';
import { isAfter, isBefore, isSameMonth, fromUnixTime } from 'date-fns';
import { isSameMonth, fromUnixTime } from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { calculateAverage } from 'vault/utils/chart-helpers';
import { filterVersionHistory } from 'core/utils/client-count-utils';
import type ClientsActivityModel from 'vault/models/clients/activity';
import type {
@ -112,25 +113,9 @@ export default class ClientsActivityComponent extends Component<Args> {
return namespace ? this.filteredActivity : activity.total;
}
get upgradeDuringActivity() {
get upgradesDuringActivity() {
const { versionHistory, activity } = this.args;
if (versionHistory) {
// filter for upgrade data of noteworthy upgrades (1.9 and/or 1.10)
const upgradeVersionHistory = versionHistory.filter(
({ version }) => version.match('1.9') || version.match('1.10')
);
if (upgradeVersionHistory.length) {
const activityStart = parseAPITimestamp(activity.startTime) as Date;
const activityEnd = parseAPITimestamp(activity.endTime) as Date;
// filter and return all upgrades that happened within date range of queried activity
const upgradesWithinData = upgradeVersionHistory.filter(({ timestampInstalled }) => {
const upgradeDate = parseAPITimestamp(timestampInstalled) as Date;
return isAfter(upgradeDate, activityStart) && isBefore(upgradeDate, activityEnd);
});
return upgradesWithinData.length === 0 ? null : upgradesWithinData;
}
}
return null;
return filterVersionHistory(versionHistory, activity.startTime, activity.endTime);
}
// (object) single month new client data with total counts + array of namespace breakdown
@ -178,21 +163,4 @@ export default class ClientsActivityComponent extends Component<Args> {
return false;
}
get upgradeExplanation() {
if (this.upgradeDuringActivity) {
if (this.upgradeDuringActivity.length === 1) {
const version = this.upgradeDuringActivity[0]?.version || '';
if (version.match('1.9')) {
return ' How we count clients changed in 1.9, so keep that in mind when looking at the data.';
}
if (version.match('1.10')) {
return ' We added monthly breakdowns and mount level attribution starting in 1.10, so keep that in mind when looking at the data.';
}
}
// return combined explanation if spans multiple upgrades
return ' How we count clients changed in 1.9 and we added monthly breakdowns and mount level attribution starting in 1.10. Keep this in mind when looking at the data.';
}
return null;
}
}

View File

@ -102,7 +102,7 @@
</p>
<p class="has-bottom-margin-s is-subtitle-gray">SELECTED DATE {{if this.formattedEndDate " RANGE"}}</p>
<p class="has-bottom-margin-s" data-test-export-date-range>
{{this.formattedStartDate}}
{{this.parseAPITimestamp @startTimestamp "MMMM yyyy"}}
{{if this.formattedEndDate "-"}}
{{this.formattedEndDate}}</p>
</M.Body>
@ -111,15 +111,24 @@
<Hds::Button @text="Export" {{on "click" (fn this.exportChartData this.formattedCsvFileName)}} />
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} />
</Hds::ButtonSet>
{{#if @upgradeExplanation}}
{{#if @upgradesDuringActivity}}
<Hds::Alert class="has-top-padding-m" @type="compact" @color="warning" as |A|>
<A.Description>
<strong>Your data contains an upgrade.</strong>
{{@upgradeExplanation}}
<strong>Data contains {{pluralize @upgradesDuringActivity.length "upgrade"}}:</strong>
</A.Description>
<A.Description>
<ul class="bullet">
{{#each @upgradesDuringActivity as |upgrade|}}
<li>
{{upgrade.version}}
{{this.parseAPITimestamp upgrade.timestampInstalled "(MMM d, yyyy)"}}
</li>
{{/each}}
</ul>
</A.Description>
<A.Description>
Visit our
<Hds::Link::Inline
@icon="docs-link"
@iconPosition="trailing"
@isHrefExternal={{true}}
@href={{doc-link
"/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"

View File

@ -16,7 +16,6 @@ import { format, isSameMonth } from 'date-fns';
* A horizontal bar chart shows on the right, with the top namespace/auth method and respective client totals on the left.
*
* @example
* ```js
* <Clients::Attribution
* @totalUsageCounts={{this.totalUsageCounts}}
* @newUsageCounts={{this.newUsageCounts}}
@ -27,9 +26,9 @@ import { format, isSameMonth } from 'date-fns';
* @endTimestamp={{this.endTime}}
* @isHistoricalMonth={{false}}
* @responseTimestamp={{this.responseTimestamp}}
* @upgradeExplanation="We added monthly breakdowns and mount level attribution starting in 1.10, so keep that in mind when looking at the data."
* @upgradesDuringActivity={{array (hash version="1.10.1" previousVersion="1.9.1" timestampInstalled= "2021-11-18T10:23:16Z") }}
* />
* ```
*
* @param {object} totalUsageCounts - object with total client counts for chart tooltip text
* @param {object} newUsageCounts - object with new client counts for chart tooltip text
* @param {array} totalClientAttribution - array of objects containing a label and breakdown of client counts for total clients
@ -39,22 +38,22 @@ import { format, isSameMonth } from 'date-fns';
* @param {string} endTimestamp - timestamp string from activity response to render end date for CSV modal and whether copy reads 'month' or 'date range'
* @param {string} responseTimestamp - ISO timestamp created in serializer to timestamp the response, renders in bottom left corner below attribution chart
* @param {boolean} isHistoricalMonth - when true data is from a single, historical month so side-by-side charts should display for attribution data
* @param {boolean} upgradeExplanation - if data contains an upgrade, explanation is generated by the parent to be rendered in the export modal
* @param {array} upgradesDuringActivity - array of objects containing version history upgrade data
*/
export default class Attribution extends Component {
@tracked showCSVDownloadModal = false;
@service download;
@tracked showCSVDownloadModal = false;
parseAPITimestamp = (time, format) => parseAPITimestamp(time, format);
attributionLegend = [
{ key: 'entity_clients', label: 'entity clients' },
{ key: 'non_entity_clients', label: 'non-entity clients' },
{ key: 'secret_syncs', label: 'secrets sync clients' },
];
get formattedStartDate() {
if (!this.args.startTimestamp) return null;
return parseAPITimestamp(this.args.startTimestamp, 'MMMM yyyy');
}
get formattedEndDate() {
if (!this.args.startTimestamp && !this.args.endTimestamp) return null;
// displays on CSV export modal, no need to display duplicate months and years
@ -149,7 +148,7 @@ export default class Attribution extends Component {
const csvData = [];
// added to clarify that the row of namespace totals without an auth method (blank) are not additional clients
// but indicate the total clients for that ns, including its auth methods
const upgrade = this.args.upgradeExplanation
const upgrade = this.args.upgradesDuringActivity.length
? `\n **data contains an upgrade, mount summation may not equal namespace totals`
: '';
const descriptionOfBlanks = this.isSingleNamespace

View File

@ -79,7 +79,8 @@ export default class LineChart extends Component<Args> {
}
}
get upgradedMonths() {
return this.data.filter((datum) => datum.tooltipUpgrade);
// only render upgrade month circle if datum has client count data (the y value)
return this.data.filter((datum) => datum.tooltipUpgrade && datum.y);
}
// Domains
get yDomain() {

View File

@ -102,9 +102,36 @@
{{/if}}
{{#if this.filteredActivity}}
{{#if this.upgradeExplanations}}
<Hds::Alert data-test-clients-upgrade-warning @type="inline" @color="warning" class="has-bottom-margin-s" as |A|>
<A.Title>
Client count data contains
{{pluralize this.upgradeExplanations.length "upgrade"}}
</A.Title>
<A.Description>
Vault was upgraded during this time period. Keep this in mind while looking at the data. Visit our
<Hds::Link::Inline
@isHrefExternal={{true}}
@href={{doc-link
"/vault/docs/concepts/client-count/faq#q-which-vault-version-reflects-the-most-accurate-client-counts"
}}
>
Client count FAQ
</Hds::Link::Inline>
for more information.
</A.Description>
<A.Description>
<ul class="bullet">
{{#each this.upgradeExplanations as |info|}}
<li>{{info}}</li>
{{/each}}
</ul>
</A.Description>
</Hds::Alert>
{{/if}}
{{#if this.startTimeDiscrepancy}}
<Hds::Alert data-test-counts-start-discrepancy @type="inline" @color="warning" class="has-bottom-margin-s" as |A|>
<A.Title>Warning</A.Title>
<Hds::Alert data-test-counts-start-discrepancy @type="inline" @color="neutral" class="has-bottom-margin-s" as |A|>
<A.Description data-test-counts-start-discrepancy>
{{this.startTimeDiscrepancy}}
</A.Description>

View File

@ -8,17 +8,19 @@ import { service } from '@ember/service';
import { action } from '@ember/object';
import { fromUnixTime, getUnixTime, isSameMonth, isAfter } from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { formatDateObject } from 'core/utils/client-count-utils';
import { filterVersionHistory, formatDateObject } from 'core/utils/client-count-utils';
import timestamp from 'core/utils/timestamp';
import type VersionService from 'vault/services/version';
import type ClientsActivityModel from 'vault/models/clients/activity';
import type ClientsConfigModel from 'vault/models/clients/config';
import type ClientsVersionHistoryModel from 'vault/models/clients/version-history';
import type StoreService from 'vault/services/store';
import timestamp from 'core/utils/timestamp';
interface Args {
activity: ClientsActivityModel;
config: ClientsConfigModel;
versionHistory: ClientsVersionHistoryModel[];
startTimestamp: number;
endTimestamp: number;
namespace: string;
@ -57,6 +59,32 @@ export default class ClientsCountsPageComponent extends Component<Args> {
return null;
}
get upgradeExplanations() {
const { versionHistory, activity } = this.args;
const upgradesDuringActivity = filterVersionHistory(versionHistory, activity.startTime, activity.endTime);
if (upgradesDuringActivity.length) {
return upgradesDuringActivity.map((upgrade: ClientsVersionHistoryModel) => {
let explanation;
const date = parseAPITimestamp(upgrade.timestampInstalled, 'MMM d, yyyy');
const version = upgrade.version || '';
switch (true) {
case version.includes('1.9'):
explanation =
'- We introduced changes to non-entity token and local auth mount logic for client counting in 1.9.';
break;
case version.includes('1.10'):
explanation = '- We added monthly breakdowns and mount level attribution starting in 1.10.';
break;
default:
explanation = '';
break;
}
return `${version} (upgraded on ${date}) ${explanation}`;
});
}
return null;
}
get versionText() {
return this.version.isEnterprise
? {

View File

@ -8,7 +8,7 @@
@isHistoricalMonth={{and (not this.isCurrentMonth) (not this.isDateRange)}}
@isCurrentMonth={{this.isCurrentMonth}}
@runningTotals={{this.totalUsageCounts}}
@upgradeData={{this.upgradeDuringActivity}}
@upgradeData={{this.upgradesDuringActivity}}
@responseTimestamp={{@activity.responseTimestamp}}
/>
{{#if this.hasAttributionData}}
@ -22,6 +22,6 @@
@endTimestamp={{this.endTimeISO}}
@responseTimestamp={{@activity.responseTimestamp}}
@isHistoricalMonth={{and (not this.isCurrentMonth) (not this.isDateRange)}}
@upgradeExplanation={{this.upgradeExplanation}}
@upgradesDuringActivity={{this.upgradesDuringActivity}}
/>
{{/if}}

View File

@ -7,6 +7,7 @@
@activity={{this.model.activity}}
@activityError={{this.model.activityError}}
@config={{this.model.config}}
@versionHistory={{this.model.versionHistory}}
@startTimestamp={{this.model.startTimestamp}}
@endTimestamp={{this.model.endTimestamp}}
@namespace={{this.ns}}

View File

@ -4,7 +4,36 @@
*/
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { compareAsc, getUnixTime } from 'date-fns';
import { compareAsc, getUnixTime, isWithinInterval } from 'date-fns';
// returns array of VersionHistoryModels for noteworthy upgrades: 1.9, 1.10
// that occurred between timestamps (i.e. queried activity data)
export const filterVersionHistory = (versionHistory, start, end) => {
if (versionHistory) {
const upgrades = versionHistory.reduce((array, upgradeData) => {
const includesVersion = (v) =>
// only add first match, disregard subsequent patch releases of the same version
upgradeData.version.match(v) && !array.some((d) => d.version.match(v));
['1.9', '1.10'].forEach((v) => {
if (includesVersion(v)) array.push(upgradeData);
});
return array;
}, []);
// if there are noteworthy upgrades, only return those during queried date range
if (upgrades.length) {
const startDate = parseAPITimestamp(start);
const endDate = parseAPITimestamp(end);
return upgrades.filter(({ timestampInstalled }) => {
const upgradeDate = parseAPITimestamp(timestampInstalled);
return isWithinInterval(upgradeDate, { start: startDate, end: endDate });
});
}
}
return [];
};
export const formatDateObject = (dateObj, isEnd) => {
if (dateObj) {

View File

@ -4,25 +4,25 @@
*/
import {
isBefore,
startOfMonth,
endOfMonth,
addMonths,
subMonths,
differenceInCalendarMonths,
endOfMonth,
formatRFC3339,
fromUnixTime,
isAfter,
formatRFC3339,
isBefore,
isSameMonth,
isWithinInterval,
startOfMonth,
subMonths,
} from 'date-fns';
import { parseAPITimestamp } from 'core/utils/date-formatters';
// Matches mocked date in client-dashboard-test file
const CURRENT_DATE = new Date('2023-01-13T14:15:00');
const COUNTS_START = subMonths(CURRENT_DATE, 12); // pretend vault user started cluster 6 months ago
// for testing, we're in the middle of a license/billing period
const LICENSE_START = startOfMonth(subMonths(CURRENT_DATE, 6));
// upgrade happened 1 month after license start
const UPGRADE_DATE = addMonths(LICENSE_START, 1);
export const LICENSE_START = new Date('2023-07-02T00:00:00Z');
export const STATIC_NOW = new Date('2024-01-25T23:59:59Z');
const COUNTS_START = subMonths(STATIC_NOW, 12); // user started Vault cluster on 2023-01-25
// upgrade happened 2 month after license start
export const UPGRADE_DATE = addMonths(LICENSE_START, 2); // monthly attribution added
function getSum(array, key) {
return array.reduce((sum, { counts }) => sum + counts[key], 0);
@ -105,39 +105,40 @@ function generateNamespaceBlock(idx = 0, isLowerCounts = false, ns) {
}
function generateMonths(startDate, endDate, namespaces) {
const startDateObject = startOfMonth(parseAPITimestamp(startDate));
const endDateObject = startOfMonth(parseAPITimestamp(endDate));
const startDateObject = parseAPITimestamp(startDate);
const endDateObject = parseAPITimestamp(endDate);
const numberOfMonths = differenceInCalendarMonths(endDateObject, startDateObject) + 1;
const months = [];
if (isBefore(startDateObject, UPGRADE_DATE) && isBefore(endDateObject, UPGRADE_DATE)) {
// months block is empty if dates do not span an upgrade
return [];
}
for (let i = 0; i < numberOfMonths; i++) {
const month = addMonths(startDateObject, i);
const hasNoData = isBefore(month, UPGRADE_DATE);
if (hasNoData) {
// only generate monthly block if queried dates span an upgrade
if (isWithinInterval(UPGRADE_DATE, { start: startDateObject, end: endDateObject })) {
for (let i = 0; i < numberOfMonths; i++) {
const month = addMonths(startOfMonth(startDateObject), i);
const hasNoData = isBefore(month, UPGRADE_DATE) && !isSameMonth(month, UPGRADE_DATE);
if (hasNoData) {
months.push({
timestamp: formatRFC3339(month),
counts: null,
namespaces: null,
new_clients: null,
});
continue;
}
const monthNs = namespaces.map((ns, idx) => generateNamespaceBlock(idx, false, ns));
const newClients = namespaces.map((ns, idx) => generateNamespaceBlock(idx, true, ns));
months.push({
timestamp: formatRFC3339(month),
counts: null,
namespaces: null,
new_clients: null,
counts: getTotalCounts(monthNs),
namespaces: monthNs.sort((a, b) => b.counts.clients - a.counts.clients),
new_clients: {
counts: getTotalCounts(newClients),
namespaces: newClients.sort((a, b) => b.counts.clients - a.counts.clients),
},
});
continue;
}
const monthNs = namespaces.map((ns, idx) => generateNamespaceBlock(idx, false, ns));
const newClients = namespaces.map((ns, idx) => generateNamespaceBlock(idx, true, ns));
months.push({
timestamp: formatRFC3339(month),
counts: getTotalCounts(monthNs),
namespaces: monthNs.sort((a, b) => b.counts.clients - a.counts.clients),
new_clients: {
counts: getTotalCounts(newClients),
namespaces: newClients.sort((a, b) => b.counts.clients - a.counts.clients),
},
});
}
return months;
}
@ -159,7 +160,7 @@ export default function (server) {
autoloaded: {
license_id: 'my-license-id',
start_time: formatRFC3339(LICENSE_START),
expiration_time: formatRFC3339(endOfMonth(addMonths(CURRENT_DATE, 6))),
expiration_time: formatRFC3339(endOfMonth(addMonths(STATIC_NOW, 6))),
},
},
};
@ -195,4 +196,47 @@ export default function (server) {
auth: null,
};
});
// client counting has changed in different ways since 1.9 see link below for details
// https://developer.hashicorp.com/vault/docs/concepts/client-count/faq#client-count-faq
server.get('sys/version-history', function () {
return {
request_id: 'version-history-request-id',
data: {
keys: ['1.9.0', '1.9.1', '1.10.1', '1.14.4', '1.16.0'],
key_info: {
// entity/non-entity breakdown added
'1.9.0': {
// we don't currently use build_date, including for accuracy. it's only tracked in versions >= 1.110
build_date: null,
previous_version: null,
timestamp_installed: LICENSE_START.toISOString(),
},
'1.9.1': {
build_date: null,
previous_version: '1.9.0',
timestamp_installed: addMonths(LICENSE_START, 1).toISOString(),
},
// auth mount attribution added in 1.10.0
'1.10.1': {
build_date: null,
previous_version: '1.9.1',
timestamp_installed: UPGRADE_DATE.toISOString(),
},
// no notable UI changes
'1.14.4': {
build_date: addMonths(LICENSE_START, 3).toISOString(),
previous_version: '1.10.1',
timestamp_installed: addMonths(LICENSE_START, 3).toISOString(),
},
// sync clients added
'1.16.0': {
build_date: addMonths(LICENSE_START, 4).toISOString(),
previous_version: '1.14.4',
timestamp_installed: addMonths(LICENSE_START, 4).toISOString(),
},
},
},
};
});
}

View File

@ -6,11 +6,11 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { STATIC_NOW } from 'vault/mirage/handlers/clients';
import sinon from 'sinon';
import { visit, click, currentURL } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth';
import { SELECTORS as ts, STATIC_NOW } from 'vault/tests/helpers/clients';
import { SELECTORS as ts } from 'vault/tests/helpers/clients';
import timestamp from 'core/utils/timestamp';
module('Acceptance | clients | counts', function (hooks) {

View File

@ -6,12 +6,12 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { STATIC_NOW, LICENSE_START, UPGRADE_DATE } from 'vault/mirage/handlers/clients';
import sinon from 'sinon';
import { visit, click, findAll, find, settled } from '@ember/test-helpers';
import { visit, click, findAll, settled } from '@ember/test-helpers';
import authPage from 'vault/tests/pages/auth';
import { ARRAY_OF_MONTHS } from 'core/utils/date-formatters';
import { SELECTORS, STATIC_NOW, LICENSE_START, UPGRADE_DATE } from 'vault/tests/helpers/clients';
import { SELECTORS } from 'vault/tests/helpers/clients';
import { create } from 'ember-cli-page-object';
import { clickTrigger } from 'ember-power-select/test-support/helpers';
import { formatNumber } from 'core/helpers/format-number';
@ -42,31 +42,29 @@ module('Acceptance | clients | overview', function (hooks) {
test('it should render charts', async function (assert) {
assert
.dom(SELECTORS.counts.startMonth)
.hasText('July 2022', 'billing start month is correctly parsed from license');
.hasText('July 2023', 'billing start month is correctly parsed from license');
assert
.dom(SELECTORS.rangeDropdown)
.hasText(`Jul 2022 - Jan 2023`, 'Date range shows dates correctly parsed activity response');
.hasText('Jul 2023 - Jan 2024', 'Date range shows dates correctly parsed activity response');
assert.dom(SELECTORS.attributionBlock).exists('Shows attribution area');
assert
.dom(SELECTORS.charts.chart('running total'))
.exists('Shows running totals with monthly breakdown charts');
assert
.dom(SELECTORS.charts.line.xAxisLabel)
.hasText(`7/22`, 'x-axis labels start with billing start date');
.hasText('7/23', 'x-axis labels start with billing start date');
assert.strictEqual(
findAll('[data-test-line-chart="plot-point"]').length,
6,
`line chart plots 6 points to match query`
5,
'line chart plots 5 points to match query'
);
});
test('it should update charts when querying date ranges', async function (assert) {
// query for single, historical month with no new counts
// query for single, historical month with no new counts (July 2023)
await click(SELECTORS.rangeDropdown);
await click('[data-test-show-calendar]');
if (parseInt(find('[data-test-display-year]').innerText) > LICENSE_START.getFullYear()) {
await click('[data-test-previous-year]');
}
await click('[data-test-previous-year]');
await click(`[data-test-calendar-month=${ARRAY_OF_MONTHS[LICENSE_START.getMonth()]}]`);
assert
.dom(SELECTORS.runningTotalMonthStats)
@ -87,7 +85,7 @@ module('Acceptance | clients | overview', function (hooks) {
await click(SELECTORS.rangeDropdown);
await click('[data-test-current-billing-period]');
// change billing start to month/year of first upgrade
// change billing start to month/year of upgrade to 1.10
await click(SELECTORS.counts.startEdit);
await click(SELECTORS.monthDropdown);
await click(`[data-test-dropdown-month="${ARRAY_OF_MONTHS[UPGRADE_DATE.getMonth()]}"]`);
@ -100,39 +98,19 @@ module('Acceptance | clients | overview', function (hooks) {
.exists('Shows running totals with monthly breakdown charts');
assert
.dom(SELECTORS.charts.line.xAxisLabel)
.hasText(`8/22`, 'x-axis labels start with updated billing start month');
.hasText('9/23', 'x-axis labels start with queried start month (upgrade date)');
assert.strictEqual(
findAll('[data-test-line-chart="plot-point"]').length,
6,
`line chart plots 6 points to match query`
5,
'line chart plots 5 points to match query'
);
// query three months ago (Oct 2022)
await click(SELECTORS.rangeDropdown);
await click('[data-test-show-calendar]');
await click('[data-test-previous-year]');
await click('[data-test-calendar-month="October"]');
assert.dom(SELECTORS.attributionBlock).exists('Shows attribution area');
assert
.dom(SELECTORS.charts.chart('running total'))
.exists('Shows running totals with monthly breakdown charts');
assert.strictEqual(
findAll('[data-test-line-chart="plot-point"]').length,
3,
`line chart plots 3 points to match query`
);
const xAxisLabels = findAll(SELECTORS.charts.line.xAxisLabel);
assert
.dom(xAxisLabels[xAxisLabels.length - 1])
.hasText(`10/22`, 'x-axis labels end with queried end month');
// query for single, historical month (upgrade month)
await click(SELECTORS.rangeDropdown);
await click('[data-test-show-calendar]');
assert.dom('[data-test-display-year]').hasText('2022');
await click('[data-test-calendar-month="August"]');
assert.dom('[data-test-display-year]').hasText('2024');
await click('[data-test-previous-year]');
await click('[data-test-calendar-month="September"]');
assert.dom(SELECTORS.runningTotalMonthStats).exists('running total single month stat boxes show');
assert
.dom(SELECTORS.charts.chart('running total'))
@ -141,6 +119,25 @@ module('Acceptance | clients | overview', function (hooks) {
assert.dom('[data-test-chart-container="new-clients"]').exists('new client attribution chart shows');
assert.dom('[data-test-chart-container="total-clients"]').exists('total client attribution chart shows');
// query historical date range (from September 2023 to December 2023)
await click(SELECTORS.rangeDropdown);
await click('[data-test-show-calendar]');
await click('[data-test-calendar-month="December"]');
assert.dom(SELECTORS.attributionBlock).exists('Shows attribution area');
assert
.dom(SELECTORS.charts.chart('running total'))
.exists('Shows running totals with monthly breakdown charts');
assert.strictEqual(
findAll('[data-test-line-chart="plot-point"]').length,
4,
'line chart plots 4 points to match query'
);
const xAxisLabels = findAll(SELECTORS.charts.line.xAxisLabel);
assert
.dom(xAxisLabels[xAxisLabels.length - 1])
.hasText('12/23', 'x-axis labels end with queried end month');
// reset to billing period
await click(SELECTORS.rangeDropdown);
await click('[data-test-current-billing-period]');
@ -154,7 +151,7 @@ module('Acceptance | clients | overview', function (hooks) {
assert
.dom(SELECTORS.counts.startDiscrepancy)
.hasTextContaining(
`We only have data from January 2022`,
'You requested data from July 2020. We only have data from January 2023, and that is what is being shown here.',
'warning banner displays that date queried was prior to count start date'
);
});

View File

@ -6,7 +6,6 @@
import { Response } from 'miragejs';
import { SELECTORS as GENERAL } from 'vault/tests/helpers/general-selectors';
import { click } from '@ember/test-helpers';
import { addMonths, startOfMonth, subMonths } from 'date-fns';
/** Scenarios
Config off, no data
@ -134,9 +133,3 @@ export async function dateDropdownSelect(month, year) {
await click(dateDropdown.selectYear(year));
await click(dateDropdown.submit);
}
export const STATIC_NOW = new Date('2023-01-13T14:15:00');
// for testing, we're in the middle of a license/billing period
export const LICENSE_START = startOfMonth(subMonths(STATIC_NOW, 6)); // 2022-07-01
// upgrade happened 1 month after license start
export const UPGRADE_DATE = addMonths(LICENSE_START, 1); // 2022-08-01

View File

@ -6,17 +6,16 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { render, click, settled } from '@ember/test-helpers';
import { render, click, settled, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
import { getUnixTime } from 'date-fns';
import { SELECTORS as ts, dateDropdownSelect } from 'vault/tests/helpers/clients';
import { selectChoose } from 'ember-power-select/test-support/helpers';
import timestamp from 'core/utils/timestamp';
import sinon from 'sinon';
const STATIC_NOW = new Date('2024-01-25T23:59:59Z');
const START_TIME = getUnixTime(new Date('2023-10-01T00:00:00Z'));
const START_TIME = getUnixTime(LICENSE_START);
const END_TIME = getUnixTime(STATIC_NOW);
module('Integration | Component | clients | Page::Counts', function (hooks) {
@ -29,21 +28,23 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
hooks.beforeEach(async function () {
clientsHandler(this.server);
const store = this.owner.lookup('service:store');
this.store = this.owner.lookup('service:store');
const activityQuery = {
start_time: { timestamp: START_TIME },
end_time: { timestamp: END_TIME },
};
this.activity = await store.queryRecord('clients/activity', activityQuery);
this.config = await store.queryRecord('clients/config', {});
this.activity = await this.store.queryRecord('clients/activity', activityQuery);
this.config = await this.store.queryRecord('clients/config', {});
this.startTimestamp = START_TIME;
this.endTimestamp = END_TIME;
this.versionHistory = [];
this.renderComponent = () =>
render(hbs`
<Clients::Page::Counts
@activity={{this.activity}}
@activityError={{this.activityError}}
@config={{this.config}}
@versionHistory={{this.versionHistory}}
@startTimestamp={{this.startTimestamp}}
@endTimestamp={{this.endTimestamp}}
@namespace={{this.namespace}}
@ -86,10 +87,10 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
test('it should populate start and end month displays', async function (assert) {
await this.renderComponent();
assert.dom(ts.counts.startMonth).hasText('October 2023', 'Start month renders');
assert.dom(ts.counts.startMonth).hasText('July 2023', 'Start month renders');
assert
.dom(ts.calendarWidget.trigger)
.hasText('Oct 2023 - Jan 2024', 'Start and end months render in filter bar');
.hasText('Jul 2023 - Jan 2024', 'Start and end months render in filter bar');
});
test('it should render no data empty state', async function (assert) {
@ -99,7 +100,7 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
assert
.dom(ts.emptyStateTitle)
.hasText('No data received from October 2023 to January 2024', 'No data empty state renders');
.hasText('No data received from July 2023 to January 2024', 'No data empty state renders');
});
test('it should render activity error', async function (assert) {
@ -191,11 +192,53 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
assert
.dom(ts.counts.startDiscrepancy)
.hasText(
'Warning You requested data from June 2022. We only have data from October 2023, and that is what is being shown here.',
'You requested data from June 2022. We only have data from July 2023, and that is what is being shown here.',
'Start discrepancy alert renders'
);
});
test('it renders alert if upgrade happened within queried activity', async function (assert) {
assert.expect(4);
this.versionHistory = await this.store.findAll('clients/version-history').then((resp) => {
return resp.map(({ version, previousVersion, timestampInstalled }) => {
return {
version,
previousVersion,
timestampInstalled,
};
});
});
await this.renderComponent();
assert
.dom(ts.upgradeWarning)
.hasTextContaining(
`Client count data contains 2 upgrades Vault was upgraded during this time period. Keep this in mind while looking at the data. Visit our Client count FAQ for more information.`,
'it renders title and subtext'
);
assert
.dom(`${ts.upgradeWarning} ul`)
.doesNotHaveTextContaining(
'1.9.1',
'Warning does not include subsequent patch releases (e.g. 1.9.1) of the same notable upgrade.'
);
const [first, second] = findAll(`${ts.upgradeWarning} li`);
assert
.dom(first)
.hasText(
`1.9.0 (upgraded on Jul 2, 2023) - We introduced changes to non-entity token and local auth mount logic for client counting in 1.9.`,
'alert includes 1.9.0 upgrade'
);
assert
.dom(second)
.hasTextContaining(
`1.10.1 (upgraded on Sep 2, 2023) - We added monthly breakdowns and mount level attribution starting in 1.10.`,
'alert includes 1.10.1 upgrade'
);
});
test('it should render empty state for no start or license start time', async function (assert) {
this.startTimestamp = null;
this.config.billingStartTimestamp = null;
@ -214,6 +257,6 @@ module('Integration | Component | clients | Page::Counts', function (hooks) {
assert
.dom(ts.emptyStateTitle)
.hasText('No data received from October 2023 to January 2024', 'Empty state renders');
.hasText('No data received from July 2023 to January 2024', 'Empty state renders');
});
});

View File

@ -8,15 +8,15 @@ import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { render, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
import { getUnixTime } from 'date-fns';
import { SELECTORS } from 'vault/tests/helpers/clients';
import { formatNumber } from 'core/helpers/format-number';
import { calculateAverage } from 'vault/utils/chart-helpers';
import { dateFormat } from 'core/helpers/date-format';
const START_TIME = getUnixTime(new Date('2023-10-01T00:00:00Z'));
const END_TIME = getUnixTime(new Date('2024-01-31T23:59:59Z'));
const START_TIME = getUnixTime(LICENSE_START);
const END_TIME = getUnixTime(STATIC_NOW);
const { syncTab, charts, usageStats } = SELECTORS;
module('Integration | Component | clients | Clients::Page::Sync', function (hooks) {
@ -78,12 +78,9 @@ module('Integration | Component | clients | Clients::Page::Sync', function (hook
`renders x-axis labels for bar chart: ${this.activity.byMonth[i].month}`
);
});
assert
.dom(charts.dataBar)
.exists(
{ count: this.activity.byMonth.filter((m) => m.counts !== null).length },
'renders correct number of data bars'
);
const dataBars = findAll(charts.dataBar).filter((b) => b.hasAttribute('height'));
assert.strictEqual(dataBars.length, this.activity.byMonth.filter((m) => m.counts !== null).length);
});
test('it should render empty state for no monthly data', async function (assert) {

View File

@ -8,15 +8,15 @@ import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { render, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
import { getUnixTime } from 'date-fns';
import { calculateAverage } from 'vault/utils/chart-helpers';
import { formatNumber } from 'core/helpers/format-number';
import { dateFormat } from 'core/helpers/date-format';
import { SELECTORS as ts } from 'vault/tests/helpers/clients';
const START_TIME = getUnixTime(new Date('2023-10-01T00:00:00Z'));
const END_TIME = getUnixTime(new Date('2024-01-31T23:59:59Z'));
const START_TIME = getUnixTime(LICENSE_START);
const END_TIME = getUnixTime(STATIC_NOW);
module('Integration | Component | clients | Page::Token', function (hooks) {
setupRenderingTest(hooks);

View File

@ -8,7 +8,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import clientsHandler from 'vault/mirage/handlers/clients';
import clientsHandler, { LICENSE_START, STATIC_NOW } from 'vault/mirage/handlers/clients';
import sinon from 'sinon';
import { formatRFC3339, getUnixTime } from 'date-fns';
import { findAll } from '@ember/test-helpers';
@ -17,14 +17,14 @@ import timestamp from 'core/utils/timestamp';
import { setRunOptions } from 'ember-a11y-testing/test-support';
import { SELECTORS as ts } from 'vault/tests/helpers/clients';
const START_TIME = getUnixTime(new Date('2023-10-01T00:00:00Z'));
const START_TIME = getUnixTime(LICENSE_START);
module('Integration | Component | clients/running-total', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
hooks.before(function () {
sinon.stub(timestamp, 'now').callsFake(() => new Date('2024-01-31T23:59:59Z'));
sinon.stub(timestamp, 'now').callsFake(() => STATIC_NOW);
});
hooks.beforeEach(async function () {
@ -63,7 +63,7 @@ module('Integration | Component | clients/running-total', function (hooks) {
<Clients::RunningTotal
@byMonthActivityData={{this.activity.byMonth}}
@runningTotals={{this.totalUsageCounts}}
@upgradeData={{this.upgradeDuringActivity}}
@upgradeData={{this.upgradesDuringActivity}}
@responseTimestamp={{this.timestamp}}
@isHistoricalMonth={{false}}
/>

File diff suppressed because it is too large Load Diff