diff --git a/ui/app/components/clients/activity.ts b/ui/app/components/clients/activity.ts
index 7e57618cc7..7fde09627c 100644
--- a/ui/app/components/clients/activity.ts
+++ b/ui/app/components/clients/activity.ts
@@ -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 {
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 {
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;
- }
}
diff --git a/ui/app/components/clients/attribution.hbs b/ui/app/components/clients/attribution.hbs
index a88f4f74fb..75cf41fe0e 100644
--- a/ui/app/components/clients/attribution.hbs
+++ b/ui/app/components/clients/attribution.hbs
@@ -102,7 +102,7 @@
SELECTED DATE {{if this.formattedEndDate " RANGE"}}
- {{this.formattedStartDate}}
+ {{this.parseAPITimestamp @startTimestamp "MMMM yyyy"}}
{{if this.formattedEndDate "-"}}
{{this.formattedEndDate}}
@@ -111,15 +111,24 @@
- {{#if @upgradeExplanation}}
+ {{#if @upgradesDuringActivity}}
- Your data contains an upgrade.
- {{@upgradeExplanation}}
+ Data contains {{pluralize @upgradesDuringActivity.length "upgrade"}}:
+
+
+
+ {{#each @upgradesDuringActivity as |upgrade|}}
+ -
+ {{upgrade.version}}
+ {{this.parseAPITimestamp upgrade.timestampInstalled "(MMM d, yyyy)"}}
+
+ {{/each}}
+
+
+
Visit our
- * ```
+ *
* @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
diff --git a/ui/app/components/clients/charts/line.ts b/ui/app/components/clients/charts/line.ts
index e94c57d3b0..da1dda11ab 100644
--- a/ui/app/components/clients/charts/line.ts
+++ b/ui/app/components/clients/charts/line.ts
@@ -79,7 +79,8 @@ export default class LineChart extends Component {
}
}
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() {
diff --git a/ui/app/components/clients/page/counts.hbs b/ui/app/components/clients/page/counts.hbs
index b6140df84e..c00b523353 100644
--- a/ui/app/components/clients/page/counts.hbs
+++ b/ui/app/components/clients/page/counts.hbs
@@ -102,9 +102,36 @@
{{/if}}
{{#if this.filteredActivity}}
+ {{#if this.upgradeExplanations}}
+
+
+ Client count data contains
+ {{pluralize this.upgradeExplanations.length "upgrade"}}
+
+
+ Vault was upgraded during this time period. Keep this in mind while looking at the data. Visit our
+
+ Client count FAQ
+
+ for more information.
+
+
+
+ {{#each this.upgradeExplanations as |info|}}
+ - {{info}}
+ {{/each}}
+
+
+
+ {{/if}}
+
{{#if this.startTimeDiscrepancy}}
-
- Warning
+
{{this.startTimeDiscrepancy}}
diff --git a/ui/app/components/clients/page/counts.ts b/ui/app/components/clients/page/counts.ts
index 55ccb21e7e..23da4c52f0 100644
--- a/ui/app/components/clients/page/counts.ts
+++ b/ui/app/components/clients/page/counts.ts
@@ -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 {
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
? {
diff --git a/ui/app/components/clients/page/overview.hbs b/ui/app/components/clients/page/overview.hbs
index 8ab73f4343..6d5af12704 100644
--- a/ui/app/components/clients/page/overview.hbs
+++ b/ui/app/components/clients/page/overview.hbs
@@ -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}}
\ No newline at end of file
diff --git a/ui/app/templates/vault/cluster/clients/counts.hbs b/ui/app/templates/vault/cluster/clients/counts.hbs
index 648851ed88..ff188039c8 100644
--- a/ui/app/templates/vault/cluster/clients/counts.hbs
+++ b/ui/app/templates/vault/cluster/clients/counts.hbs
@@ -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}}
diff --git a/ui/lib/core/addon/utils/client-count-utils.js b/ui/lib/core/addon/utils/client-count-utils.js
index 770e45f41d..c04905d2e1 100644
--- a/ui/lib/core/addon/utils/client-count-utils.js
+++ b/ui/lib/core/addon/utils/client-count-utils.js
@@ -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) {
diff --git a/ui/mirage/handlers/clients.js b/ui/mirage/handlers/clients.js
index a2e3cb0f57..5669fd322b 100644
--- a/ui/mirage/handlers/clients.js
+++ b/ui/mirage/handlers/clients.js
@@ -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(),
+ },
+ },
+ },
+ };
+ });
}
diff --git a/ui/tests/acceptance/clients/counts-test.js b/ui/tests/acceptance/clients/counts-test.js
index 2b9cfdf855..723d085e88 100644
--- a/ui/tests/acceptance/clients/counts-test.js
+++ b/ui/tests/acceptance/clients/counts-test.js
@@ -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) {
diff --git a/ui/tests/acceptance/clients/counts/overview-test.js b/ui/tests/acceptance/clients/counts/overview-test.js
index 10215a2270..516348d8d3 100644
--- a/ui/tests/acceptance/clients/counts/overview-test.js
+++ b/ui/tests/acceptance/clients/counts/overview-test.js
@@ -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'
);
});
diff --git a/ui/tests/helpers/clients.js b/ui/tests/helpers/clients.js
index 2bbbdfae19..68c0b8e248 100644
--- a/ui/tests/helpers/clients.js
+++ b/ui/tests/helpers/clients.js
@@ -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
diff --git a/ui/tests/integration/components/clients/page/counts-test.js b/ui/tests/integration/components/clients/page/counts-test.js
index 03e8602cb1..b65b797850 100644
--- a/ui/tests/integration/components/clients/page/counts-test.js
+++ b/ui/tests/integration/components/clients/page/counts-test.js
@@ -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`
{
+ 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');
});
});
diff --git a/ui/tests/integration/components/clients/page/sync-test.js b/ui/tests/integration/components/clients/page/sync-test.js
index 1e3c45588d..b005a299d6 100644
--- a/ui/tests/integration/components/clients/page/sync-test.js
+++ b/ui/tests/integration/components/clients/page/sync-test.js
@@ -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) {
diff --git a/ui/tests/integration/components/clients/page/token-test.js b/ui/tests/integration/components/clients/page/token-test.js
index 15368fd1dd..f442790693 100644
--- a/ui/tests/integration/components/clients/page/token-test.js
+++ b/ui/tests/integration/components/clients/page/token-test.js
@@ -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);
diff --git a/ui/tests/integration/components/clients/running-total-test.js b/ui/tests/integration/components/clients/running-total-test.js
index 8fdcc1d02c..c3a43a6280 100644
--- a/ui/tests/integration/components/clients/running-total-test.js
+++ b/ui/tests/integration/components/clients/running-total-test.js
@@ -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) {
diff --git a/ui/tests/integration/utils/client-count-utils-test.js b/ui/tests/integration/utils/client-count-utils-test.js
index 9a15309eeb..4d4fb56883 100644
--- a/ui/tests/integration/utils/client-count-utils-test.js
+++ b/ui/tests/integration/utils/client-count-utils-test.js
@@ -6,268 +6,358 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import {
+ filterVersionHistory,
flattenDataset,
formatByMonths,
formatByNamespace,
homogenizeClientNaming,
- sortMonthsByTimestamp,
namespaceArrayToObject,
+ sortMonthsByTimestamp,
} from 'core/utils/client-count-utils';
+import { LICENSE_START } from 'vault/mirage/handlers/clients';
import { parseAPITimestamp } from 'core/utils/date-formatters';
-import isBefore from 'date-fns/isBefore';
-import isAfter from 'date-fns/isAfter';
+import { addMonths, isAfter, isBefore } from 'date-fns';
-// import { setupMirage } from 'ember-cli-mirage/test-support';
-// import ENV from 'vault/config/environment';
-// import { formatRFC3339 } from 'date-fns';
-
-module('Integration | Util | client count utils', function (hooks) {
- setupTest(hooks);
- // setupMirage(hooks);
-
- // TODO: wire up to stubbed API/mirage?
- // hooks.before(function () {
- // ENV['ember-cli-mirage'].handler = 'clients';
- // });
- // hooks.after(function () {
- // ENV['ember-cli-mirage'].handler = null;
- // });
-
- /* MONTHS array contains: (update when backend work done on months )
- - one month with only old client naming
- */
-
- const MONTHS = [
- {
- timestamp: '2021-05-01T00:00:00Z',
- counts: {
- distinct_entities: 25,
- non_entity_tokens: 25,
- clients: 50,
- },
- namespaces: [
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 13,
- non_entity_tokens: 7,
- clients: 20,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 8,
- non_entity_tokens: 0,
- clients: 8,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 7,
- clients: 7,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 5,
- non_entity_tokens: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 5,
- non_entity_tokens: 0,
- clients: 5,
- },
- },
- ],
- },
- ],
- new_clients: {
+const MONTHS = [
+ {
+ timestamp: '2021-05-01T00:00:00Z',
+ counts: {
+ distinct_entities: 25,
+ non_entity_tokens: 25,
+ clients: 50,
+ },
+ namespaces: [
+ {
+ namespace_id: 'root',
+ namespace_path: '',
counts: {
- distinct_entities: 3,
- non_entity_tokens: 2,
- clients: 5,
+ distinct_entities: 13,
+ non_entity_tokens: 7,
+ clients: 20,
},
- namespaces: [
+ mounts: [
{
- namespace_id: 'root',
- namespace_path: '',
+ mount_path: 'auth/up2/',
counts: {
- distinct_entities: 3,
- non_entity_tokens: 2,
- clients: 5,
+ distinct_entities: 8,
+ non_entity_tokens: 0,
+ clients: 8,
+ },
+ },
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ non_entity_tokens: 7,
+ clients: 7,
},
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 3,
- non_entity_tokens: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- non_entity_tokens: 2,
- clients: 2,
- },
- },
- ],
},
],
},
- },
- {
- timestamp: '2021-10-01T00:00:00Z',
+ {
+ namespace_id: 's07UR',
+ namespace_path: 'ns1/',
+ counts: {
+ distinct_entities: 5,
+ non_entity_tokens: 5,
+ clients: 10,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ non_entity_tokens: 5,
+ clients: 5,
+ },
+ },
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 5,
+ non_entity_tokens: 0,
+ clients: 5,
+ },
+ },
+ ],
+ },
+ ],
+ new_clients: {
counts: {
- distinct_entities: 20,
- entity_clients: 20,
- non_entity_tokens: 20,
- non_entity_clients: 20,
- clients: 40,
+ distinct_entities: 3,
+ non_entity_tokens: 2,
+ clients: 5,
},
namespaces: [
{
namespace_id: 'root',
namespace_path: '',
counts: {
- distinct_entities: 8,
- entity_clients: 8,
- non_entity_tokens: 7,
- non_entity_clients: 7,
- clients: 15,
+ distinct_entities: 3,
+ non_entity_tokens: 2,
+ clients: 5,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
- distinct_entities: 8,
- entity_clients: 8,
+ distinct_entities: 3,
non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 8,
+ clients: 3,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 7,
- non_entity_clients: 7,
- clients: 7,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 5,
- entity_clients: 5,
- non_entity_tokens: 5,
- non_entity_clients: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 5,
- non_entity_clients: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 5,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 5,
+ non_entity_tokens: 2,
+ clients: 2,
},
},
],
},
],
- new_clients: {
+ },
+ },
+ {
+ timestamp: '2021-10-01T00:00:00Z',
+ counts: {
+ distinct_entities: 20,
+ entity_clients: 20,
+ non_entity_tokens: 20,
+ non_entity_clients: 20,
+ clients: 40,
+ },
+ namespaces: [
+ {
+ namespace_id: 'root',
+ namespace_path: '',
counts: {
- distinct_entities: 3,
+ distinct_entities: 8,
+ entity_clients: 8,
+ non_entity_tokens: 7,
+ non_entity_clients: 7,
+ clients: 15,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 8,
+ entity_clients: 8,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 8,
+ },
+ },
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 7,
+ non_entity_clients: 7,
+ clients: 7,
+ },
+ },
+ ],
+ },
+ {
+ namespace_id: 's07UR',
+ namespace_path: 'ns1/',
+ counts: {
+ distinct_entities: 5,
+ entity_clients: 5,
+ non_entity_tokens: 5,
+ non_entity_clients: 5,
+ clients: 10,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 5,
+ non_entity_clients: 5,
+ clients: 5,
+ },
+ },
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 5,
+ entity_clients: 5,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 5,
+ },
+ },
+ ],
+ },
+ ],
+ new_clients: {
+ counts: {
+ distinct_entities: 3,
+ entity_clients: 3,
+ non_entity_tokens: 2,
+ non_entity_clients: 2,
+ clients: 5,
+ },
+ namespaces: [
+ {
+ namespace_id: 'root',
+ namespace_path: '',
+ counts: {
+ distinct_entities: 3,
+ entity_clients: 3,
+ non_entity_tokens: 2,
+ non_entity_clients: 2,
+ clients: 5,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 3,
+ entity_clients: 3,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 3,
+ },
+ },
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 2,
+ non_entity_clients: 2,
+ clients: 2,
+ },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ {
+ timestamp: '2021-09-01T00:00:00Z',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 17,
+ non_entity_tokens: 0,
+ non_entity_clients: 18,
+ clients: 35,
+ },
+ namespaces: [
+ {
+ namespace_id: 'oImjk',
+ namespace_path: 'ns2/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 5,
+ non_entity_tokens: 0,
+ non_entity_clients: 5,
+ clients: 10,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 0,
+ non_entity_clients: 5,
+ clients: 5,
+ },
+ },
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 5,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 5,
+ },
+ },
+ ],
+ },
+ {
+ namespace_id: 'root',
+ namespace_path: '',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 2,
+ non_entity_tokens: 0,
+ non_entity_clients: 3,
+ clients: 5,
+ },
+ mounts: [
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 0,
+ non_entity_clients: 3,
+ clients: 3,
+ },
+ },
+ {
+ mount_path: 'auth/up2/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 2,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 2,
+ },
+ },
+ ],
+ },
+ {
+ namespace_id: 's07UR',
+ namespace_path: 'ns1/',
+ counts: {
+ distinct_entities: 0,
entity_clients: 3,
- non_entity_tokens: 2,
+ non_entity_tokens: 0,
non_entity_clients: 2,
clients: 5,
},
- namespaces: [
+ mounts: [
{
- namespace_id: 'root',
- namespace_path: '',
+ mount_path: 'auth/up2/',
counts: {
- distinct_entities: 3,
+ distinct_entities: 0,
entity_clients: 3,
- non_entity_tokens: 2,
- non_entity_clients: 2,
- clients: 5,
+ non_entity_tokens: 0,
+ non_entity_clients: 0,
+ clients: 3,
+ },
+ },
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 0,
+ non_entity_tokens: 0,
+ non_entity_clients: 2,
+ clients: 2,
},
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 3,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 2,
- non_entity_clients: 2,
- clients: 2,
- },
- },
- ],
},
],
},
- },
- {
- timestamp: '2021-09-01T00:00:00Z',
+ ],
+ new_clients: {
counts: {
distinct_entities: 0,
- entity_clients: 17,
+ entity_clients: 10,
non_entity_tokens: 0,
- non_entity_clients: 18,
- clients: 35,
+ non_entity_clients: 10,
+ clients: 20,
},
namespaces: [
{
@@ -370,244 +460,191 @@ module('Integration | Util | client count utils', function (hooks) {
],
},
],
- new_clients: {
+ },
+ },
+];
+
+const BY_NAMESPACE = [
+ {
+ namespace_id: '96OwG',
+ namespace_path: 'test-ns/',
+ counts: {
+ distinct_entities: 18290,
+ entity_clients: 18290,
+ non_entity_tokens: 18738,
+ non_entity_clients: 18738,
+ clients: 37028,
+ },
+ mounts: [
+ {
+ mount_path: 'path-1',
+ counts: {
+ distinct_entities: 6403,
+ entity_clients: 6403,
+ non_entity_tokens: 6300,
+ non_entity_clients: 6300,
+ clients: 12703,
+ },
+ },
+ {
+ mount_path: 'path-2',
+ counts: {
+ distinct_entities: 5699,
+ entity_clients: 5699,
+ non_entity_tokens: 6777,
+ non_entity_clients: 6777,
+ clients: 12476,
+ },
+ },
+ {
+ mount_path: 'path-3',
+ counts: {
+ distinct_entities: 6188,
+ entity_clients: 6188,
+ non_entity_tokens: 5661,
+ non_entity_clients: 5661,
+ clients: 11849,
+ },
+ },
+ ],
+ },
+ {
+ namespace_id: 'root',
+ namespace_path: '',
+ counts: {
+ distinct_entities: 19099,
+ entity_clients: 19099,
+ non_entity_tokens: 17781,
+ non_entity_clients: 17781,
+ clients: 36880,
+ },
+ mounts: [
+ {
+ mount_path: 'path-3',
+ counts: {
+ distinct_entities: 6863,
+ entity_clients: 6863,
+ non_entity_tokens: 6801,
+ non_entity_clients: 6801,
+ clients: 13664,
+ },
+ },
+ {
+ mount_path: 'path-2',
+ counts: {
+ distinct_entities: 6047,
+ entity_clients: 6047,
+ non_entity_tokens: 5957,
+ non_entity_clients: 5957,
+ clients: 12004,
+ },
+ },
+ {
+ mount_path: 'path-1',
+ counts: {
+ distinct_entities: 6189,
+ entity_clients: 6189,
+ non_entity_tokens: 5023,
+ non_entity_clients: 5023,
+ clients: 11212,
+ },
+ },
+ {
+ mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
- entity_clients: 10,
+ entity_clients: 50,
non_entity_tokens: 0,
- non_entity_clients: 10,
- clients: 20,
+ non_entity_clients: 23,
+ clients: 73,
},
- namespaces: [
- {
- namespace_id: 'oImjk',
- namespace_path: 'ns2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 10,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 5,
- clients: 5,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 5,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 5,
- },
- },
- ],
- },
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 3,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 2,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 2,
- },
- },
- ],
- },
- {
- namespace_id: 's07UR',
- namespace_path: 'ns1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 5,
- },
- mounts: [
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 3,
- non_entity_tokens: 0,
- non_entity_clients: 0,
- clients: 3,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 0,
- non_entity_tokens: 0,
- non_entity_clients: 2,
- clients: 2,
- },
- },
- ],
- },
- ],
},
- },
- ];
-
- const BY_NAMESPACE = [
- {
- namespace_id: '96OwG',
- namespace_path: 'test-ns/',
- counts: {
- distinct_entities: 18290,
- entity_clients: 18290,
- non_entity_tokens: 18738,
- non_entity_clients: 18738,
- clients: 37028,
+ {
+ mount_path: 'auth/up1/',
+ counts: {
+ distinct_entities: 0,
+ entity_clients: 25,
+ non_entity_tokens: 0,
+ non_entity_clients: 15,
+ clients: 40,
+ },
},
- mounts: [
- {
- mount_path: 'path-1',
- counts: {
- distinct_entities: 6403,
- entity_clients: 6403,
- non_entity_tokens: 6300,
- non_entity_clients: 6300,
- clients: 12703,
- },
- },
- {
- mount_path: 'path-2',
- counts: {
- distinct_entities: 5699,
- entity_clients: 5699,
- non_entity_tokens: 6777,
- non_entity_clients: 6777,
- clients: 12476,
- },
- },
- {
- mount_path: 'path-3',
- counts: {
- distinct_entities: 6188,
- entity_clients: 6188,
- non_entity_tokens: 5661,
- non_entity_clients: 5661,
- clients: 11849,
- },
- },
- ],
- },
- {
- namespace_id: 'root',
- namespace_path: '',
- counts: {
- distinct_entities: 19099,
- entity_clients: 19099,
- non_entity_tokens: 17781,
- non_entity_clients: 17781,
- clients: 36880,
+ ],
+ },
+];
+
+const EMPTY_MONTHS = [
+ {
+ timestamp: '2021-06-01T00:00:00Z',
+ counts: null,
+ namespaces: null,
+ new_clients: null,
+ },
+ {
+ timestamp: '2021-07-01T00:00:00Z',
+ counts: null,
+ namespaces: null,
+ new_clients: null,
+ },
+];
+
+const SOME_OBJECT = { foo: 'bar' };
+
+module('Integration | Util | client count utils', function (hooks) {
+ setupTest(hooks);
+
+ test('filterVersionHistory: returns version data that occurred during activity date range', async function (assert) {
+ assert.expect(1);
+ // LICENSE_START: '2023-07-02T00:00:00Z'
+ const versionHistory = [
+ {
+ version: '1.9.0',
+ previousVersion: null,
+ timestampInstalled: LICENSE_START.toISOString(),
},
- mounts: [
- {
- mount_path: 'path-3',
- counts: {
- distinct_entities: 6863,
- entity_clients: 6863,
- non_entity_tokens: 6801,
- non_entity_clients: 6801,
- clients: 13664,
- },
- },
- {
- mount_path: 'path-2',
- counts: {
- distinct_entities: 6047,
- entity_clients: 6047,
- non_entity_tokens: 5957,
- non_entity_clients: 5957,
- clients: 12004,
- },
- },
- {
- mount_path: 'path-1',
- counts: {
- distinct_entities: 6189,
- entity_clients: 6189,
- non_entity_tokens: 5023,
- non_entity_clients: 5023,
- clients: 11212,
- },
- },
- {
- mount_path: 'auth/up2/',
- counts: {
- distinct_entities: 0,
- entity_clients: 50,
- non_entity_tokens: 0,
- non_entity_clients: 23,
- clients: 73,
- },
- },
- {
- mount_path: 'auth/up1/',
- counts: {
- distinct_entities: 0,
- entity_clients: 25,
- non_entity_tokens: 0,
- non_entity_clients: 15,
- clients: 40,
- },
- },
- ],
- },
- ];
-
- const EMPTY_MONTHS = [
- {
- timestamp: '2021-06-01T00:00:00Z',
- counts: null,
- namespaces: null,
- new_clients: null,
- },
- {
- timestamp: '2021-07-01T00:00:00Z',
- counts: null,
- namespaces: null,
- new_clients: null,
- },
- ];
-
- const SOME_OBJECT = { foo: 'bar' };
+ {
+ version: '1.9.1',
+ previousVersion: '1.9.0',
+ timestampInstalled: addMonths(LICENSE_START, 1).toISOString(),
+ },
+ {
+ version: '1.10.1',
+ previousVersion: '1.9.1',
+ timestampInstalled: addMonths(LICENSE_START, 2).toISOString(),
+ },
+ {
+ version: '1.14.4',
+ previousVersion: '1.10.1',
+ timestampInstalled: addMonths(LICENSE_START, 3).toISOString(),
+ },
+ {
+ version: '1.16.0',
+ previousVersion: '1.14.4',
+ timestampInstalled: addMonths(LICENSE_START, 4).toISOString(),
+ },
+ ];
+ const expected = [
+ {
+ previousVersion: null,
+ timestampInstalled: '2023-07-02T00:00:00.000Z',
+ version: '1.9.0',
+ },
+ {
+ previousVersion: '1.9.1',
+ timestampInstalled: '2023-09-02T00:00:00.000Z',
+ version: '1.10.1',
+ },
+ ];
+ const activity = {
+ startTime: '2023-07-02T00:00:00Z', // same as license start to catch same day edge cases
+ endTime: '2024-03-04T16:14:21.000Z',
+ };
+ assert.propEqual(
+ filterVersionHistory(versionHistory, activity.startTime, activity.endTime),
+ expected,
+ 'it returns only upgrades that happened between given start and end times.'
+ );
+ });
test('formatByMonths: formats the months array', async function (assert) {
assert.expect(103);
@@ -686,7 +723,7 @@ module('Integration | Util | client count utils', function (hooks) {
},
];
assert.strictEqual(formatByMonths(SOME_OBJECT), SOME_OBJECT, 'it returns if arg is not an array');
- assert.propEqual(expected, formatByMonths(EMPTY_MONTHS), 'it does not error with null months');
+ assert.propEqual(formatByMonths(EMPTY_MONTHS), expected, 'it does not error with null months');
assert.ok(formatByMonths([...EMPTY_MONTHS, ...MONTHS]), 'it does not error with combined data');
});
@@ -834,8 +871,8 @@ module('Integration | Util | client count utils', function (hooks) {
);
assert.propEqual(
- ['some array'],
flattenDataset(['some array']),
+ ['some array'],
'it fails gracefully if an array is passed in'
);
assert.strictEqual(flattenDataset(null), null, 'it fails gracefully if null is passed in');
@@ -845,8 +882,8 @@ module('Integration | Util | client count utils', function (hooks) {
'it fails gracefully if a string is passed in'
);
assert.propEqual(
- new Object(),
flattenDataset(new Object()),
+ new Object(),
'it fails gracefully if an empty object is passed in'
);
});
@@ -887,13 +924,13 @@ module('Integration | Util | client count utils', function (hooks) {
);
assert.propEqual(
- totalClientsByNamespace,
formatByNamespace(MONTHS[1].namespaces),
+ totalClientsByNamespace,
'it does not modify original array'
);
assert.propEqual(
- newClientsByNamespace,
formatByNamespace(MONTHS[1].new_clients.namespaces),
+ newClientsByNamespace,
'it does not modify original array'
);
@@ -923,8 +960,8 @@ module('Integration | Util | client count utils', function (hooks) {
});
assert.propEqual(
- {},
namespaceArrayToObject(null, null, '10/21', 'timestamp-here'),
+ {},
'returns an empty object when totalClientsByNamespace = null'
);
});