mirror of
https://github.com/hashicorp/vault.git
synced 2025-11-13 14:51:34 +01:00
* rename query params to match keys from api response * move type guard check to util * update color scheme * remove passing selected namespace to export activity data request * remove namespace and mount filter toolbar * update response, sort by client count number * remove default page size for testing * implement table and filters in overview tab * remove old query params * cleanup unused args * revert page header changes * update mirage, remove month from utils * update client count utils * one more color! * reset table to page 1; * workaround to force Hds::Pagination::Numbered to update when currentPage changes * add empty state test for no attribution * delete unused methods * add test for new utils * add changelog * rename changelog --------- Co-authored-by: claire bontempo <cbontempo@hashicorp.com>
186 lines
8.8 KiB
JavaScript
186 lines
8.8 KiB
JavaScript
/**
|
||
* Copyright (c) HashiCorp, Inc.
|
||
* SPDX-License-Identifier: BUSL-1.1
|
||
*/
|
||
|
||
import { module, test } from 'qunit';
|
||
import { setupRenderingTest } from 'vault/tests/helpers';
|
||
import { click, fillIn, findAll, render, triggerEvent } from '@ember/test-helpers';
|
||
import { hbs } from 'ember-cli-htmlbars';
|
||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||
import { ACTIVITY_RESPONSE_STUB } from 'vault/tests/helpers/clients/client-count-helpers';
|
||
import { CLIENT_COUNT, FILTERS } from 'vault/tests/helpers/clients/client-count-selectors';
|
||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||
import sinon from 'sinon';
|
||
import { ClientFilters, flattenMounts } from 'core/utils/client-count-utils';
|
||
|
||
module('Integration | Component | clients/page/overview', function (hooks) {
|
||
setupRenderingTest(hooks);
|
||
setupMirage(hooks);
|
||
|
||
hooks.beforeEach(async function () {
|
||
this.server.get('sys/internal/counters/activity', () => {
|
||
return {
|
||
request_id: 'some-activity-id',
|
||
data: ACTIVITY_RESPONSE_STUB,
|
||
};
|
||
});
|
||
|
||
this.store = this.owner.lookup('service:store');
|
||
this.activity = await this.store.queryRecord('clients/activity', {});
|
||
this.mostRecentMonth = this.activity.byMonth[this.activity.byMonth.length - 1];
|
||
this.onFilterChange = sinon.spy();
|
||
this.filterQueryParams = { namespace_path: '', mount_path: '', mount_type: '' };
|
||
this.renderComponent = () =>
|
||
render(hbs`
|
||
<Clients::Page::Overview
|
||
@activity={{this.activity}}
|
||
@onFilterChange={{this.onFilterChange}}
|
||
@filterQueryParams={{this.filterQueryParams}}
|
||
/>`);
|
||
});
|
||
|
||
test('it hides attribution when there is no data', async function (assert) {
|
||
// Stub activity response when there's no activity data
|
||
this.server.get('sys/internal/counters/activity', () => {
|
||
return {
|
||
request_id: 'some-activity-id',
|
||
data: {
|
||
by_namespace: [],
|
||
end_time: '2024-08-31T23:59:59Z',
|
||
months: [],
|
||
start_time: '2024-01-01T00:00:00Z',
|
||
total: {
|
||
distinct_entities: 0,
|
||
entity_clients: 0,
|
||
non_entity_tokens: 0,
|
||
non_entity_clients: 0,
|
||
clients: 0,
|
||
secret_syncs: 0,
|
||
},
|
||
},
|
||
};
|
||
});
|
||
this.activity = await this.store.queryRecord('clients/activity', {});
|
||
await this.renderComponent();
|
||
assert.dom(CLIENT_COUNT.card('Client attribution')).doesNotExist('it does not render attribution card');
|
||
assert.dom(GENERAL.selectByAttr('attribution-month')).doesNotExist('it hides months dropdown');
|
||
});
|
||
|
||
test('it shows correct state message when selected month has no data', async function (assert) {
|
||
await this.renderComponent();
|
||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), '2023-06-01T00:00:00Z');
|
||
|
||
assert
|
||
.dom(CLIENT_COUNT.card('table empty state'))
|
||
.hasText('No data found Clear or change filters to view client count data. Client count documentation');
|
||
});
|
||
|
||
test('it shows table when month selection has data', async function (assert) {
|
||
await this.renderComponent();
|
||
|
||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), '9/23');
|
||
|
||
assert.dom(CLIENT_COUNT.card('table empty state')).doesNotExist('does not show card when table has data');
|
||
assert.dom(GENERAL.table('attribution')).exists('shows table');
|
||
assert.dom(GENERAL.paginationInfo).hasText('1–6 of 6', 'shows correct pagination info');
|
||
assert.dom(GENERAL.paginationSizeSelector).hasValue('10', 'page size selector defaults to "10"');
|
||
});
|
||
|
||
test('it shows correct month options for billing period', async function (assert) {
|
||
await this.renderComponent();
|
||
|
||
assert.dom(GENERAL.selectByAttr('attribution-month')).exists('shows month selection dropdown');
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), '');
|
||
await triggerEvent(GENERAL.selectByAttr('attribution-month'), 'change');
|
||
|
||
// assert that months options in select are those of selected billing period
|
||
// '' represents default state of 'Select month'
|
||
const expectedOptions = ['', ...this.activity.byMonth.reverse().map((m) => m.timestamp)];
|
||
const actualOptions = findAll(`${GENERAL.selectByAttr('attribution-month')} option`).map(
|
||
(option) => option.value
|
||
);
|
||
assert.deepEqual(actualOptions, expectedOptions, 'All <option> values match expected list');
|
||
});
|
||
|
||
test('it initially renders attribution with by_namespace data', async function (assert) {
|
||
await this.renderComponent();
|
||
const topNamespace = this.activity.byNamespace[0];
|
||
const topMount = topNamespace.mounts[0];
|
||
// Assert table renders namespace with the highest counts at the top
|
||
assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText(topNamespace.label);
|
||
assert.dom(GENERAL.tableData(0, 'clients')).hasText(`${topMount.clients}`);
|
||
});
|
||
|
||
test('it renders dropdown lists from activity response to filter table data', async function (assert) {
|
||
const expectedNamespaces = this.activity.byNamespace.map((n) => n.label);
|
||
const mounts = flattenMounts(this.mostRecentMonth.new_clients.namespaces);
|
||
const expectedMountPaths = [...new Set(mounts.map((m) => m.mount_path))];
|
||
const expectedMountTypes = [...new Set(mounts.map((m) => m.mount_type))];
|
||
await this.renderComponent();
|
||
// Assert filters do not exist until month is selected
|
||
assert.dom(FILTERS.dropdownToggle(ClientFilters.NAMESPACE)).doesNotExist();
|
||
assert.dom(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH)).doesNotExist();
|
||
assert.dom(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE)).doesNotExist();
|
||
// Select month
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||
// Select each filter
|
||
await click(FILTERS.dropdownToggle(ClientFilters.NAMESPACE));
|
||
findAll(`${FILTERS.dropdown(ClientFilters.NAMESPACE)} li button`).forEach((item, idx) => {
|
||
const expected = expectedNamespaces[idx];
|
||
assert.dom(item).hasText(expected, `namespace dropdown renders: ${expected}`);
|
||
});
|
||
|
||
await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_PATH));
|
||
findAll(`${FILTERS.dropdown(ClientFilters.MOUNT_PATH)} li button`).forEach((item, idx) => {
|
||
const expected = expectedMountPaths[idx];
|
||
assert.dom(item).hasText(expected, `mount_path dropdown renders: ${expected}`);
|
||
});
|
||
|
||
await click(FILTERS.dropdownToggle(ClientFilters.MOUNT_TYPE));
|
||
findAll(`${FILTERS.dropdown(ClientFilters.MOUNT_TYPE)} li button`).forEach((item, idx) => {
|
||
const expected = expectedMountTypes[idx];
|
||
assert.dom(item).hasText(expected, `mount_type dropdown renders: ${expected}`);
|
||
});
|
||
});
|
||
|
||
// * FILTERING ASSERTIONS
|
||
// Filtering tests are split between integration and acceptance tests
|
||
// because changing filters updates the URL query params/
|
||
test('it filters attribution table by month', async function (assert) {
|
||
await this.renderComponent();
|
||
const mostRecentMonth = this.mostRecentMonth;
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), mostRecentMonth.timestamp);
|
||
// Drill down to new_clients then grab the first mount
|
||
const sortedMounts = flattenMounts(mostRecentMonth.new_clients.namespaces).sort(
|
||
(a, b) => b.clients - a.clients
|
||
);
|
||
const topMount = sortedMounts[0];
|
||
assert.dom(GENERAL.tableData(0, 'namespace_path')).hasText(topMount.namespace_path);
|
||
assert.dom(GENERAL.tableData(0, 'clients')).hasText(`${topMount.clients}`);
|
||
assert.dom(GENERAL.tableData(0, 'mount_path')).hasText(topMount.mount_path);
|
||
});
|
||
|
||
test('it resets pagination when a month is selected change', async function (assert) {
|
||
const attributionByMount = flattenMounts(this.activity.byNamespace);
|
||
await this.renderComponent();
|
||
// Decrease page size for test so we don't have to seed more data
|
||
await fillIn(GENERAL.paginationSizeSelector, '5');
|
||
assert.dom(GENERAL.paginationInfo).hasText(`1–5 of ${attributionByMount.length}`);
|
||
// Change pages because we should go back to page 1 when a month is selected
|
||
await click(GENERAL.nextPage);
|
||
assert.dom(GENERAL.tableRow()).exists({ count: 1 }, '1 row render');
|
||
assert.dom(GENERAL.paginationInfo).hasText(`6–6 of ${attributionByMount.length}`);
|
||
// Select a month and assert table resets to page 1
|
||
await fillIn(GENERAL.selectByAttr('attribution-month'), this.mostRecentMonth.timestamp);
|
||
const monthMounts = flattenMounts(this.mostRecentMonth.new_clients.namespaces);
|
||
assert
|
||
.dom(GENERAL.paginationInfo)
|
||
.hasText(`1–5 of ${monthMounts.length}`, 'pagination resets to page one');
|
||
assert.dom(GENERAL.tableRow()).exists({ count: 5 }, '5 rows render');
|
||
assert.dom(GENERAL.paginationSizeSelector).hasValue('5', 'size selector does not reset to 10');
|
||
});
|
||
});
|