From 35c188fba7d8164fbe56662757f44d5b7ac0e938 Mon Sep 17 00:00:00 2001 From: Arnav Palnitkar Date: Thu, 16 Sep 2021 15:28:03 -0700 Subject: [PATCH] Client count updates (#12554) * Client count updates - Added Current month tab which leverages partial monthly activity api - Refactored Vault usage to Monthly history - New client count history component based on StatText and BarChart component - Restrict bar chart to showcase only top 10 namespaces - Removed config route, as config and history component will be rendered based on query param - Updated all metrics reference to clients - Removed old tests and added integration test for current month * Fixed navbar permission - Added changelog * Updated the model for current month data * Fixed current month tests * Fixed indentation and chart label --- changelog/12554.txt | 3 + .../adapters/{metrics => clients}/activity.js | 6 +- .../adapters/{metrics => clients}/config.js | 0 .../config.js} | 12 +- ui/app/components/clients/history.js | 40 +++++ ui/app/components/pricing-metrics-dates.js | 42 +---- .../vault/cluster/clients/index.js | 8 + .../vault/cluster/metrics/index.js | 8 - .../models/{metrics => clients}/activity.js | 4 + ui/app/models/{metrics => clients}/config.js | 0 ui/app/router.js | 3 +- .../cluster/{metrics => clients}/edit.js | 2 +- .../cluster/{metrics => clients}/index.js | 37 +++-- ui/app/routes/vault/cluster/metrics/config.js | 8 - .../{metrics => clients}/activity.js | 0 .../{metrics => clients}/config.js | 0 ui/app/services/permissions.js | 2 +- ui/app/styles/components/bar-chart.scss | 5 +- ui/app/styles/components/stat-text.scss | 4 + .../config.hbs} | 3 +- .../templates/components/clients/history.hbs | 149 ++++++++++++++++++ ui/app/templates/components/cluster-info.hbs | 6 +- .../components/pricing-metrics-dates.hbs | 10 -- ui/app/templates/vault/cluster.hbs | 11 +- .../cluster/{metrics => clients}/edit.hbs | 2 +- .../templates/vault/cluster/clients/index.hbs | 46 ++++++ .../vault/cluster/metrics/config.hbs | 36 ----- .../templates/vault/cluster/metrics/index.hbs | 78 --------- ui/lib/core/addon/components/bar-chart.js | 8 +- ui/mirage/config.js | 4 +- ui/mirage/scenarios/default.js | 2 +- ui/tests/acceptance/usage-metrics-test.js | 106 ------------- ...-config-test.js => clients-config-test.js} | 12 +- .../components/clients-current-test.js | 55 +++++++ .../components/clients-history-test.js | 67 ++++++++ .../components/pricing-metrics-dates-test.js | 28 ---- 36 files changed, 440 insertions(+), 367 deletions(-) create mode 100644 changelog/12554.txt rename ui/app/adapters/{metrics => clients}/activity.js (77%) rename ui/app/adapters/{metrics => clients}/config.js (100%) rename ui/app/components/{pricing-metrics-config.js => clients/config.js} (82%) create mode 100644 ui/app/components/clients/history.js create mode 100644 ui/app/controllers/vault/cluster/clients/index.js delete mode 100644 ui/app/controllers/vault/cluster/metrics/index.js rename ui/app/models/{metrics => clients}/activity.js (55%) rename ui/app/models/{metrics => clients}/config.js (100%) rename ui/app/routes/vault/cluster/{metrics => clients}/edit.js (62%) rename ui/app/routes/vault/cluster/{metrics => clients}/index.js (51%) delete mode 100644 ui/app/routes/vault/cluster/metrics/config.js rename ui/app/serializers/{metrics => clients}/activity.js (100%) rename ui/app/serializers/{metrics => clients}/config.js (100%) rename ui/app/templates/components/{pricing-metrics-config.hbs => clients/config.hbs} (98%) create mode 100644 ui/app/templates/components/clients/history.hbs rename ui/app/templates/vault/cluster/{metrics => clients}/edit.hbs (71%) create mode 100644 ui/app/templates/vault/cluster/clients/index.hbs delete mode 100644 ui/app/templates/vault/cluster/metrics/config.hbs delete mode 100644 ui/app/templates/vault/cluster/metrics/index.hbs delete mode 100644 ui/tests/acceptance/usage-metrics-test.js rename ui/tests/integration/components/{pricing-metrics-config-test.js => clients-config-test.js} (91%) create mode 100644 ui/tests/integration/components/clients-current-test.js create mode 100644 ui/tests/integration/components/clients-history-test.js diff --git a/changelog/12554.txt b/changelog/12554.txt new file mode 100644 index 000000000..e3549ecdf --- /dev/null +++ b/changelog/12554.txt @@ -0,0 +1,3 @@ +```release-note:feature +ui: client count monthly view +``` \ No newline at end of file diff --git a/ui/app/adapters/metrics/activity.js b/ui/app/adapters/clients/activity.js similarity index 77% rename from ui/app/adapters/metrics/activity.js rename to ui/app/adapters/clients/activity.js index 09ea312b0..df54b0ea4 100644 --- a/ui/app/adapters/metrics/activity.js +++ b/ui/app/adapters/clients/activity.js @@ -5,7 +5,11 @@ export default Application.extend({ return 'internal/counters/activity'; }, queryRecord(store, type, query) { - const url = this.urlForQuery(null, type); + let url = this.urlForQuery(null, type); + if (query.tab === 'current') { + url = `${url}/monthly`; + query = null; + } // API accepts start and end as query params return this.ajax(url, 'GET', { data: query }).then(resp => { let response = resp || {}; diff --git a/ui/app/adapters/metrics/config.js b/ui/app/adapters/clients/config.js similarity index 100% rename from ui/app/adapters/metrics/config.js rename to ui/app/adapters/clients/config.js diff --git a/ui/app/components/pricing-metrics-config.js b/ui/app/components/clients/config.js similarity index 82% rename from ui/app/components/pricing-metrics-config.js rename to ui/app/components/clients/config.js index a61ef3d34..9b093ecae 100644 --- a/ui/app/components/pricing-metrics-config.js +++ b/ui/app/components/clients/config.js @@ -1,12 +1,12 @@ /** - * @module PricingMetricsConfig - * PricingMetricsConfig components are used to show and edit the pricing metrics config information. + * @module ClientsConfig + * ClientsConfig components are used to show and edit the client count config information. * * @example * ```js - * + * * ``` - * @param {object} model - model is the DS metrics/config model which should be passed in + * @param {object} model - model is the DS clients/config model which should be passed in * @param {string} [mode=show] - mode is either show or edit. Show results in a table with the config, show has a form. */ @@ -16,7 +16,7 @@ import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; -export default class PricingMetricsConfigComponent extends Component { +export default class ConfigComponent extends Component { @service router; @tracked mode = 'show'; @tracked modalOpen = false; @@ -57,7 +57,7 @@ export default class PricingMetricsConfigComponent extends Component { this.error = err.message; return; } - this.router.transitionTo('vault.cluster.metrics.config'); + this.router.transitionTo('vault.cluster.clients.index'); }).drop()) save; diff --git a/ui/app/components/clients/history.js b/ui/app/components/clients/history.js new file mode 100644 index 000000000..cb4ba01fb --- /dev/null +++ b/ui/app/components/clients/history.js @@ -0,0 +1,40 @@ +import Component from '@glimmer/component'; + +export default class HistoryComponent extends Component { + max_namespaces = 10; + + get hasClientData() { + if (this.args.tab === 'current') { + return this.args.model.activity && this.args.model.activity.clients; + } + return this.args.model.activity && this.args.model.activity.total; + } + + get barChartDataset() { + if (!this.args.model.activity || !this.args.model.activity.byNamespace) { + return null; + } + let dataset = this.args.model.activity.byNamespace; + // Filter out root data + dataset = dataset.filter(item => { + return item.namespace_id !== 'root'; + }); + // Show only top 10 namespaces + dataset = dataset.slice(0, this.max_namespaces); + return dataset.map(d => { + return { + label: d['namespace_path'], + non_entity_tokens: d['counts']['non_entity_tokens'], + distinct_entities: d['counts']['distinct_entities'], + total: d['counts']['clients'], + }; + }); + } + + get showGraphs() { + if (!this.args.model.activity || !this.args.model.activity.byNamespace) { + return null; + } + return this.args.model.activity.byNamespace.length > 1; + } +} diff --git a/ui/app/components/pricing-metrics-dates.js b/ui/app/components/pricing-metrics-dates.js index 2189ae0f8..0cc7a3b76 100644 --- a/ui/app/components/pricing-metrics-dates.js +++ b/ui/app/components/pricing-metrics-dates.js @@ -18,16 +18,7 @@ import { set, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; -import { - differenceInSeconds, - isValid, - subMonths, - startOfToday, - format, - endOfMonth, - startOfMonth, - isBefore, -} from 'date-fns'; +import { subMonths, startOfToday, format, endOfMonth, startOfMonth, isBefore } from 'date-fns'; import layout from '../templates/components/pricing-metrics-dates'; import { parseDateString } from 'vault/helpers/parse-date-string'; @@ -69,35 +60,6 @@ export default Component.extend({ } }), - // We don't want the warning to show when inputs are being updated before query is made - /* eslint-disable-next-line ember/require-computed-property-dependencies */ - showResultsWarning: computed('resultEnd', 'resultStart', function() { - if (!this.queryStart || !this.queryEnd || !this.resultStart || !this.resultEnd) { - return false; - } - const resultStart = new Date(this.resultStart); - const resultEnd = new Date(this.resultEnd); - let queryStart, queryEnd; - try { - queryStart = parseDateString(this.queryStart, '-'); - queryEnd = parseDateString(this.queryEnd, '-'); - } catch (e) { - // Log error for debugging purposes - console.debug(e); - } - - if (!queryStart || !queryEnd || !isValid(resultStart) || !isValid(resultEnd)) { - return false; - } - if (Math.abs(differenceInSeconds(queryStart, resultStart)) >= 86400) { - return true; - } - if (Math.abs(differenceInSeconds(resultEnd, endOfMonth(queryEnd))) >= 86400) { - return true; - } - return false; - }), - error: computed('end', 'endDate', 'retentionMonths', 'start', 'startDate', function() { if (!this.startDate) { return 'Start date is invalid. Please use format MM/yyyy'; @@ -148,7 +110,7 @@ export default Component.extend({ handleQuery() { const start = format(this.startDate, 'MM-yyyy'); const end = format(this.endDate, 'MM-yyyy'); - this.router.transitionTo('vault.cluster.metrics', { + this.router.transitionTo('vault.cluster.clients', { queryParams: { start, end, diff --git a/ui/app/controllers/vault/cluster/clients/index.js b/ui/app/controllers/vault/cluster/clients/index.js new file mode 100644 index 000000000..8cb0262e7 --- /dev/null +++ b/ui/app/controllers/vault/cluster/clients/index.js @@ -0,0 +1,8 @@ +import Controller from '@ember/controller'; + +export default class ClientsController extends Controller { + queryParams = ['tab', 'start', 'end']; + tab = null; + start = null; + end = null; +} diff --git a/ui/app/controllers/vault/cluster/metrics/index.js b/ui/app/controllers/vault/cluster/metrics/index.js deleted file mode 100644 index e355af5b8..000000000 --- a/ui/app/controllers/vault/cluster/metrics/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import Controller from '@ember/controller'; - -export default Controller.extend({ - queryParams: ['start', 'end'], - - start: null, - end: null, -}); diff --git a/ui/app/models/metrics/activity.js b/ui/app/models/clients/activity.js similarity index 55% rename from ui/app/models/metrics/activity.js rename to ui/app/models/clients/activity.js index d220bf184..4cbd4fb11 100644 --- a/ui/app/models/metrics/activity.js +++ b/ui/app/models/clients/activity.js @@ -2,6 +2,10 @@ import Model, { attr } from '@ember-data/model'; export default Model.extend({ total: attr('object'), + byNamespace: attr('array'), endTime: attr('string'), startTime: attr('string'), + clients: attr('number'), + distinct_entities: attr('number'), + non_entity_tokens: attr('number'), }); diff --git a/ui/app/models/metrics/config.js b/ui/app/models/clients/config.js similarity index 100% rename from ui/app/models/metrics/config.js rename to ui/app/models/clients/config.js diff --git a/ui/app/router.js b/ui/app/router.js index ee964bfb7..090def1fa 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -15,9 +15,8 @@ Router.map(function() { this.route('logout'); this.mount('open-api-explorer', { path: '/api-explorer' }); this.route('license'); - this.route('metrics', function() { + this.route('clients', function() { this.route('index', { path: '/' }); - this.route('config'); this.route('edit'); }); this.route('storage', { path: '/storage/raft' }); diff --git a/ui/app/routes/vault/cluster/metrics/edit.js b/ui/app/routes/vault/cluster/clients/edit.js similarity index 62% rename from ui/app/routes/vault/cluster/metrics/edit.js rename to ui/app/routes/vault/cluster/clients/edit.js index ddb4f482a..51f0bb9b8 100644 --- a/ui/app/routes/vault/cluster/metrics/edit.js +++ b/ui/app/routes/vault/cluster/clients/edit.js @@ -2,6 +2,6 @@ import Route from '@ember/routing/route'; export default Route.extend({ model() { - return this.store.queryRecord('metrics/config', {}); + return this.store.queryRecord('clients/config', {}); }, }); diff --git a/ui/app/routes/vault/cluster/metrics/index.js b/ui/app/routes/vault/cluster/clients/index.js similarity index 51% rename from ui/app/routes/vault/cluster/metrics/index.js rename to ui/app/routes/vault/cluster/clients/index.js index 59e7b5a3c..b08d01e07 100644 --- a/ui/app/routes/vault/cluster/metrics/index.js +++ b/ui/app/routes/vault/cluster/clients/index.js @@ -4,23 +4,27 @@ import { hash } from 'rsvp'; import { getTime } from 'date-fns'; import { parseDateString } from 'vault/helpers/parse-date-string'; -const getActivityParams = ({ start, end }) => { +const getActivityParams = ({ tab, start, end }) => { // Expects MM-yyyy format // TODO: minStart, maxEnd let params = {}; - if (start) { - let startDate = parseDateString(start); - if (startDate) { - // TODO: Replace with formatRFC3339 when date-fns is updated - // converts to milliseconds, divide by 1000 to get epoch - params.start_time = getTime(startDate) / 1000; + if (tab === 'current') { + params.tab = tab; + } else if (tab === 'history') { + if (start) { + let startDate = parseDateString(start); + if (startDate) { + // TODO: Replace with formatRFC3339 when date-fns is updated + // converts to milliseconds, divide by 1000 to get epoch + params.start_time = getTime(startDate) / 1000; + } } - } - if (end) { - let endDate = parseDateString(end); - if (endDate) { - // TODO: Replace with formatRFC3339 when date-fns is updated - params.end_time = getTime(endDate) / 1000; + if (end) { + let endDate = parseDateString(end); + if (endDate) { + // TODO: Replace with formatRFC3339 when date-fns is updated + params.end_time = getTime(endDate) / 1000; + } } } return params; @@ -28,6 +32,9 @@ const getActivityParams = ({ start, end }) => { export default Route.extend(ClusterRoute, { queryParams: { + tab: { + refreshModel: true, + }, start: { refreshModel: true, }, @@ -37,13 +44,13 @@ export default Route.extend(ClusterRoute, { }, model(params) { - let config = this.store.queryRecord('metrics/config', {}).catch(e => { + let config = this.store.queryRecord('clients/config', {}).catch(e => { console.debug(e); // swallowing error so activity can show if no config permissions return {}; }); const activityParams = getActivityParams(params); - let activity = this.store.queryRecord('metrics/activity', activityParams); + let activity = this.store.queryRecord('clients/activity', activityParams); return hash({ queryStart: params.start, diff --git a/ui/app/routes/vault/cluster/metrics/config.js b/ui/app/routes/vault/cluster/metrics/config.js deleted file mode 100644 index ed1a32d5e..000000000 --- a/ui/app/routes/vault/cluster/metrics/config.js +++ /dev/null @@ -1,8 +0,0 @@ -import Route from '@ember/routing/route'; -import ClusterRoute from 'vault/mixins/cluster-route'; - -export default Route.extend(ClusterRoute, { - model() { - return this.store.queryRecord('metrics/config', {}); - }, -}); diff --git a/ui/app/serializers/metrics/activity.js b/ui/app/serializers/clients/activity.js similarity index 100% rename from ui/app/serializers/metrics/activity.js rename to ui/app/serializers/clients/activity.js diff --git a/ui/app/serializers/metrics/config.js b/ui/app/serializers/clients/config.js similarity index 100% rename from ui/app/serializers/metrics/config.js rename to ui/app/serializers/clients/config.js diff --git a/ui/app/services/permissions.js b/ui/app/services/permissions.js index c58bb3f39..aa67e2128 100644 --- a/ui/app/services/permissions.js +++ b/ui/app/services/permissions.js @@ -29,7 +29,7 @@ const API_PATHS = { seal: 'sys/seal', raft: 'sys/storage/raft/configuration', }, - metrics: { + clients: { activity: 'sys/internal/counters/activity', config: 'sys/internal/counters/config', }, diff --git a/ui/app/styles/components/bar-chart.scss b/ui/app/styles/components/bar-chart.scss index 1cc9bc126..44458d04c 100644 --- a/ui/app/styles/components/bar-chart.scss +++ b/ui/app/styles/components/bar-chart.scss @@ -7,14 +7,11 @@ > div.is-border { border: 0.3px solid $ui-gray-200; - width: 94%; - margin-left: 3%; margin-bottom: $spacing-xxs; } } .chart-header { - margin-left: $spacing-l; display: grid; grid-template-columns: 3fr 1fr; @@ -47,7 +44,7 @@ } .bar-chart-container { - padding: $spacing-m $spacing-l $spacing-m $spacing-l; + padding: $spacing-m 0; } .bar-chart { diff --git a/ui/app/styles/components/stat-text.scss b/ui/app/styles/components/stat-text.scss index 814d8a87b..efd76fa62 100644 --- a/ui/app/styles/components/stat-text.scss +++ b/ui/app/styles/components/stat-text.scss @@ -1,5 +1,8 @@ .stat-text-container { line-height: normal; + height: 100%; + display: flex; + flex-direction: column; &.l, &.m { @@ -13,6 +16,7 @@ font-weight: $font-weight-normal; color: $ui-gray-700; line-height: inherit; + flex-grow: 1; } .stat-value { font-size: $size-3; diff --git a/ui/app/templates/components/pricing-metrics-config.hbs b/ui/app/templates/components/clients/config.hbs similarity index 98% rename from ui/app/templates/components/pricing-metrics-config.hbs rename to ui/app/templates/components/clients/config.hbs index b68137357..0551956de 100644 --- a/ui/app/templates/components/pricing-metrics-config.hbs +++ b/ui/app/templates/components/clients/config.hbs @@ -54,7 +54,8 @@ Save Cancel diff --git a/ui/app/templates/components/clients/history.hbs b/ui/app/templates/components/clients/history.hbs new file mode 100644 index 000000000..6936c11cd --- /dev/null +++ b/ui/app/templates/components/clients/history.hbs @@ -0,0 +1,149 @@ +{{#if (eq @model.config.queriesAvailable false)}} + {{#if (eq @model.config.enabled 'On')}} + + {{else}} + + {{#if @model.config.configPath.canUpdate}} +

+ + Go to configuration + +

+ {{/if}} +
+ {{/if}} +{{else}} +
+ {{#if (eq @tab 'current')}} +

+ Current month +

+

+ The below data is for the current month starting from the first day. For historical data, see the monthly + history tab. +

+ {{#if (eq @model.config.enabled 'Off')}} + + {{#if @model.config.configPath.canUpdate}} + + Go to configuration + + {{/if}} + + {{/if}} + {{else}} + {{#if (eq @model.config.enabled 'Off')}} + + Tracking is currently disabled and data is not being collected. Historical data can be searched, but you will + need to + + edit the configuration + + to enable tracking again. + + {{/if}} +

+ Monthly history +

+

+ This data is presented by full month. If there is data missing, it's possible that tracking was turned off at + the time. Vault will only show data for contiguous blocks of time during which tracking was on. +

+ + {{/if}} + {{#unless this.hasClientData}} + {{#if (eq @tab 'current')}} + {{#if (eq @model.config.enabled 'On')}} + + {{/if}} + {{else}} + + {{/if}} + {{else}} +
+
+
+
+

+ Total usage +

+

+ These totals are within this namespace and all its children. +

+
+ + Learn more + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+ {{#if this.showGraphs}} +
+
+ +
+
+
+ {{/if}} + {{/unless}} +
+{{/if}} \ No newline at end of file diff --git a/ui/app/templates/components/cluster-info.hbs b/ui/app/templates/components/cluster-info.hbs index c2a28a49c..c404841d7 100644 --- a/ui/app/templates/components/cluster-info.hbs +++ b/ui/app/templates/components/cluster-info.hbs @@ -116,12 +116,12 @@ {{/if}} {{/if}} - {{#if ( and (has-permission 'metrics' routeParams='activity') (not @cluster.dr.isSecondary) this.auth.currentToken)}} + {{#if ( and (has-permission 'clients' routeParams='activity') (not @cluster.dr.isSecondary) this.auth.currentToken)}}