mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-06 04:46:25 +02:00
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:
parent
d8cb4247c8
commit
164a1af54a
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
? {
|
||||
|
||||
@ -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}}
|
||||
@ -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}}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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'
|
||||
);
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user