diff --git a/changelog/client-counts.txt b/changelog/client-counts.txt
new file mode 100644
index 000000000..271414e45
--- /dev/null
+++ b/changelog/client-counts.txt
@@ -0,0 +1,3 @@
+```release-note:feature
+**UI Client Count Improvements**: Restructures client count dashboard, making use of billing start date to improve accuracy. Adds mount-level distribution and filtering.
+```
\ No newline at end of file
diff --git a/ui/app/components/clients/history-old.js b/ui/app/components/clients/history-old.js
deleted file mode 100644
index 024357449..000000000
--- a/ui/app/components/clients/history-old.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import Component from '@glimmer/component';
-import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
-import { format } from 'date-fns';
-
-export default class HistoryComponent extends Component {
- max_namespaces = 10;
-
- @tracked selectedNamespace = null;
-
- @tracked barChartSelection = false;
-
- // Determine if we have client count data based on the current tab
- get hasClientData() {
- if (this.args.tab === 'current') {
- // Show the current numbers as long as config is on
- return this.args.model.config?.enabled !== 'Off';
- }
- return this.args.model.activity && this.args.model.activity.total;
- }
-
- // Show namespace graph only if we have more than 1
- get showGraphs() {
- return (
- this.args.model.activity &&
- this.args.model.activity.byNamespace &&
- this.args.model.activity.byNamespace.length > 1
- );
- }
-
- // Construct the namespace model for the search select component
- get searchDataset() {
- if (!this.args.model.activity || !this.args.model.activity.byNamespace) {
- return null;
- }
- let dataList = this.args.model.activity.byNamespace;
- return dataList.map((d) => {
- return {
- name: d['namespace_id'],
- id: d['namespace_path'] === '' ? 'root' : d['namespace_path'],
- };
- });
- }
-
- // Construct the namespace model for the bar chart component
- get barChartDataset() {
- if (!this.args.model.activity || !this.args.model.activity.byNamespace) {
- return null;
- }
- let dataset = this.args.model.activity.byNamespace.slice(0, this.max_namespaces);
- return dataset.map((d) => {
- return {
- label: d['namespace_path'] === '' ? 'root' : d['namespace_path'],
- non_entity_tokens: d['counts']['non_entity_tokens'],
- distinct_entities: d['counts']['distinct_entities'],
- total: d['counts']['clients'],
- };
- });
- }
-
- // Create namespaces data for csv format
- get getCsvData() {
- if (!this.args.model.activity || !this.args.model.activity.byNamespace) {
- return null;
- }
- let results = '',
- namespaces = this.args.model.activity.byNamespace,
- fields = ['Namespace path', 'Active clients', 'Unique entities', 'Non-entity tokens'];
-
- results = fields.join(',') + '\n';
-
- namespaces.forEach(function (item) {
- let path = item.namespace_path !== '' ? item.namespace_path : 'root',
- total = item.counts.clients,
- unique = item.counts.distinct_entities,
- non_entity = item.counts.non_entity_tokens;
-
- results += path + ',' + total + ',' + unique + ',' + non_entity + '\n';
- });
- return results;
- }
-
- // Return csv filename with start and end dates
- get getCsvFileName() {
- let defaultFileName = `clients-by-namespace`,
- startDate =
- this.args.model.queryStart || `${format(new Date(this.args.model.activity.startTime), 'MM-yyyy')}`,
- endDate =
- this.args.model.queryEnd || `${format(new Date(this.args.model.activity.endTime), 'MM-yyyy')}`;
- if (startDate && endDate) {
- defaultFileName += `-${startDate}-${endDate}`;
- }
- return defaultFileName;
- }
-
- // Get the namespace by matching the path from the namespace list
- getNamespace(path) {
- return this.args.model.activity.byNamespace.find((ns) => {
- if (path === 'root') {
- return ns.namespace_path === '';
- }
- return ns.namespace_path === path;
- });
- }
-
- @action
- selectNamespace(value) {
- // In case of search select component, value returned is an array
- if (Array.isArray(value)) {
- this.selectedNamespace = this.getNamespace(value[0]);
- this.barChartSelection = false;
- } else if (typeof value === 'object') {
- // While D3 bar selection returns an object
- this.selectedNamespace = this.getNamespace(value.label);
- this.barChartSelection = true;
- }
- }
-
- @action
- resetData() {
- this.barChartSelection = false;
- this.selectedNamespace = null;
- }
-}
diff --git a/ui/app/components/pricing-metrics-dates.js b/ui/app/components/pricing-metrics-dates.js
deleted file mode 100644
index a1d1a5c12..000000000
--- a/ui/app/components/pricing-metrics-dates.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * @module PricingMetricsDates
- * PricingMetricsDates components are used on the Pricing Metrics page to handle queries related to pricing metrics.
- * This component assumes that query parameters (as in, from route params) are being passed in with the format MM-yyyy,
- * while the inputs expect a format of MM/yyyy.
- *
- * @example
- * ```js
- *
- * ```
- * @param {object} resultStart - resultStart is the start date of the metrics returned. Should be a valid date string that the built-in Date() fn can parse
- * @param {object} resultEnd - resultEnd is the end date of the metrics returned. Should be a valid date string that the built-in Date() fn can parse
- * @param {string} [queryStart] - queryStart is the route param (formatted MM-yyyy) that the result will be measured against for showing discrepancy warning
- * @param {string} [queryEnd] - queryEnd is the route param (formatted MM-yyyy) that the result will be measured against for showing discrepancy warning
- * @param {number} [defaultSpan=12] - setting for default time between start and end input dates
- * @param {number} [retentionMonths=24] - setting for the retention months, which informs valid dates to query by
- */
-import { set, computed } from '@ember/object';
-import { inject as service } from '@ember/service';
-import Component from '@ember/component';
-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';
-
-export default Component.extend({
- layout,
- router: service(),
-
- queryStart: null,
- queryEnd: null,
- resultStart: null,
- resultEnd: null,
-
- start: null,
- end: null,
-
- defaultSpan: 12,
- retentionMonths: 24,
-
- startDate: computed('start', function () {
- if (!this.start) return null;
- let date;
- try {
- date = parseDateString(this.start, '/');
- if (date) return date;
- return null;
- } catch (e) {
- return null;
- }
- }),
- endDate: computed('end', function () {
- if (!this.end) return null;
- let date;
- try {
- date = parseDateString(this.end, '/');
- if (date) return endOfMonth(date);
- return null;
- } catch (e) {
- return null;
- }
- }),
-
- error: computed('end', 'endDate', 'retentionMonths', 'start', 'startDate', function () {
- if (!this.startDate) {
- return 'Start date is invalid. Please use format MM/yyyy';
- }
- if (!this.endDate) {
- return 'End date is invalid. Please use format MM/yyyy';
- }
- if (isBefore(this.endDate, this.startDate)) {
- return 'Start date is after end date';
- }
- const lastMonthAvailable = endOfMonth(subMonths(startOfToday(), 1));
- if (isBefore(lastMonthAvailable, this.endDate)) {
- return `Data is not available until the end of the month`;
- }
- const earliestRetained = startOfMonth(subMonths(lastMonthAvailable, this.retentionMonths));
- if (isBefore(this.startDate, earliestRetained)) {
- return `No data retained before ${format(earliestRetained, 'MM/yyyy')} due to your settings`;
- }
-
- return null;
- }),
-
- init() {
- this._super(...arguments);
- let initialEnd;
- let initialStart;
-
- initialEnd = subMonths(startOfToday(), 1);
- if (this.queryEnd) {
- initialEnd = parseDateString(this.queryEnd, '-');
- } else {
- // if query isn't passed in, set it so that showResultsWarning works
- this.queryEnd = format(initialEnd, 'MM-yyyy');
- }
- initialStart = subMonths(initialEnd, this.defaultSpan);
- if (this.queryStart) {
- initialStart = parseDateString(this.queryStart, '-');
- } else {
- // if query isn't passed in, set it so that showResultsWarning works
- this.queryStart = format(initialStart, 'MM-yyyy');
- }
-
- set(this, 'start', format(initialStart, 'MM/yyyy'));
- set(this, 'end', format(initialEnd, 'MM/yyyy'));
- },
-
- actions: {
- handleQuery() {
- const start = format(this.startDate, 'MM-yyyy');
- const end = format(this.endDate, 'MM-yyyy');
- this.router.transitionTo('vault.cluster.clients', {
- queryParams: {
- start,
- end,
- },
- });
- },
- },
-});
diff --git a/ui/app/styles/components/pricing-metrics-dates.scss b/ui/app/styles/components/pricing-metrics-dates.scss
deleted file mode 100644
index b7483fdf3..000000000
--- a/ui/app/styles/components/pricing-metrics-dates.scss
+++ /dev/null
@@ -1,4 +0,0 @@
-.pricing-metrics-date-form {
- display: flex;
- align-items: flex-end;
-}
diff --git a/ui/app/styles/core.scss b/ui/app/styles/core.scss
index 567cbdb6f..21f8a188b 100644
--- a/ui/app/styles/core.scss
+++ b/ui/app/styles/core.scss
@@ -82,7 +82,6 @@
@import './components/navigate-input';
@import './components/page-header';
@import './components/popup-menu';
-@import './components/pricing-metrics-dates';
@import './components/radio-card';
@import './components/radial-progress';
@import './components/raft-join';
diff --git a/ui/app/templates/components/clients/history-old.hbs b/ui/app/templates/components/clients/history-old.hbs
deleted file mode 100644
index f625a276d..000000000
--- a/ui/app/templates/components/clients/history-old.hbs
+++ /dev/null
@@ -1,213 +0,0 @@
-{{#if (and (eq @tab "history") (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.
-
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/bar-chart.js b/ui/lib/core/addon/components/bar-chart.js
deleted file mode 100644
index d9c65ddfe..000000000
--- a/ui/lib/core/addon/components/bar-chart.js
+++ /dev/null
@@ -1,310 +0,0 @@
-/**
- * @module BarChart
- * BarChart components are used to display data in the form of a stacked bar chart, with accompanying legend and tooltip. Anything passed into the block will display in the top right of the chart header.
- *
- * @example
- * ```js
- *
- * />
- *
- *
- * mapLegendSample = [{
- * key: "api_key_for_label",
- * label: "Label Displayed on Legend"
- * }]
- * ```
- *
- * @param {string} title - title of the chart
- * @param {array} mapLegend - array of objects with key names 'key' and 'label' for the map legend
- * @param {object} dataset - dataset for the chart
- * @param {any} tooltipData - misc. information needed to display tooltip (i.e. total clients from query params)
- * @param {string} [description] - description of the chart
- * @param {string} [labelKey=label] - labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis
- * @param {function} [onClick] - takes function from parent and passes it to click event on data bars
- *
- */
-
-import Component from '@glimmer/component';
-import layout from '../templates/components/bar-chart';
-import { setComponentTemplate } from '@ember/component';
-import { assert } from '@ember/debug';
-import { action } from '@ember/object';
-import { guidFor } from '@ember/object/internals';
-import { scaleLinear, scaleBand } from 'd3-scale';
-import { axisLeft } from 'd3-axis';
-import { max } from 'd3-array';
-import { stack } from 'd3-shape';
-// eslint-disable-next-line no-unused-vars
-import { select, event, selectAll } from 'd3-selection';
-// eslint-disable-next-line no-unused-vars
-import { transition } from 'd3-transition';
-
-// SIZING CONSTANTS
-const CHART_MARGIN = { top: 10, left: 137 }; // makes space for y-axis legend
-const CHAR_LIMIT = 18; // character count limit for y-axis labels to trigger truncating
-const LINE_HEIGHT = 24; // each bar w/ padding is 24 pixels thick
-
-// COLOR THEME:
-const LIGHT_AND_DARK_BLUE = ['#BFD4FF', '#8AB1FF'];
-const BAR_COLOR_HOVER = ['#1563FF', '#0F4FD1'];
-const TOOLTIP_BACKGROUND = '#525761';
-const GREY = '#EBEEF2';
-class BarChartComponent extends Component {
- get labelKey() {
- return this.args.labelKey || 'label';
- }
-
- get mapLegend() {
- assert(
- 'map legend is required, must be an array of objects with key names of "key" and "label"',
- this.hasLegend()
- );
- return this.args.mapLegend;
- }
-
- hasLegend() {
- if (!this.args.mapLegend || !Array.isArray(this.args.mapLegend)) {
- return false;
- } else {
- let legendKeys = this.args.mapLegend.map((obj) => Object.keys(obj));
- return legendKeys.map((array) => array.includes('key', 'label')).every((element) => element === true);
- }
- }
-
- @action
- renderBarChart(element, args) {
- let elementId = guidFor(element);
- let dataset = args[0];
- let totalCount = args[1];
- let handleClick = this.args.onClick;
- let labelKey = this.labelKey;
- let stackFunction = stack().keys(this.mapLegend.map((l) => l.key));
- // creates an array of data for each map legend key
- // each array contains coordinates for each data bar
- let stackedData = stackFunction(dataset);
-
- // creates and appends tooltip
- let container = select('.bar-chart-container');
- container
- .append('div')
- .attr('class', 'chart-tooltip')
- .attr('style', 'position: absolute; opacity: 0;')
- .style('color', 'white')
- .style('background', `${TOOLTIP_BACKGROUND}`)
- .style('font-size', '.929rem')
- .style('padding', '10px')
- .style('border-radius', '4px');
-
- let xScale = scaleLinear()
- .domain([0, max(dataset.map((d) => d.total))])
- .range([0, 75]); // 25% reserved for margins
-
- let yScale = scaleBand()
- .domain(dataset.map((d) => d[labelKey]))
- .range([0, dataset.length * LINE_HEIGHT])
- .paddingInner(0.765); // percent of the total width to reserve for padding between bars
-
- let chartSvg = select(element);
- chartSvg.attr('viewBox', `0 0 710 ${(dataset.length + 1) * LINE_HEIGHT}`);
- chartSvg.attr('id', elementId);
-
- // creates group for each array of stackedData
- let groups = chartSvg
- .selectAll('g')
- .remove()
- .exit()
- .data(stackedData)
- .enter()
- .append('g')
- // shifts chart to accommodate y-axis legend
- .attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top})`)
- .style('fill', (d, i) => LIGHT_AND_DARK_BLUE[i]);
-
- let yAxis = axisLeft(yScale).tickSize(0);
- yAxis(chartSvg.append('g').attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top})`));
-
- let truncate = (selection) =>
- selection.text((string) =>
- string.length < CHAR_LIMIT ? string : string.slice(0, CHAR_LIMIT - 3) + '...'
- );
-
- chartSvg.selectAll('.tick text').call(truncate);
-
- groups
- .selectAll('rect')
- // iterate through the stacked data and chart respectively
- .data((stackedData) => stackedData)
- .enter()
- .append('rect')
- .attr('class', 'data-bar')
- .style('cursor', 'pointer')
- .attr('width', (chartData) => `${xScale(chartData[1] - chartData[0]) - 0.25}%`)
- .attr('height', yScale.bandwidth())
- .attr('x', (chartData) => `${xScale(chartData[0])}%`)
- .attr('y', ({ data }) => yScale(data[labelKey]))
- .attr('rx', 3)
- .attr('ry', 3);
-
- let actionBars = chartSvg
- .selectAll('.action-bar')
- .data(dataset)
- .enter()
- .append('rect')
- .style('cursor', 'pointer')
- .attr('class', 'action-bar')
- .attr('width', '100%')
- .attr('height', `${LINE_HEIGHT}px`)
- .attr('x', '0')
- .attr('y', (chartData) => yScale(chartData[labelKey]))
- .style('fill', `${GREY}`)
- .style('opacity', '0')
- .style('mix-blend-mode', 'multiply');
-
- let yLegendBars = chartSvg
- .selectAll('.label-bar')
- .data(dataset)
- .enter()
- .append('rect')
- .style('cursor', 'pointer')
- .attr('class', 'label-action-bar')
- .attr('width', CHART_MARGIN.left)
- .attr('height', `${LINE_HEIGHT}px`)
- .attr('x', '0')
- .attr('y', (chartData) => yScale(chartData[labelKey]))
- .style('opacity', '0')
- .style('mix-blend-mode', 'multiply');
-
- let dataBars = chartSvg.selectAll('rect.data-bar');
- let actionBarSelection = chartSvg.selectAll('rect.action-bar');
- let compareAttributes = (elementA, elementB, attr) =>
- select(elementA).attr(`${attr}`) === elementB.getAttribute(`${attr}`);
-
- // handles click and mouseover/out/move event for data bars
- actionBars
- .on('click', function (chartData) {
- if (handleClick) {
- handleClick(chartData);
- }
- })
- .on('mouseover', function () {
- select(this).style('opacity', 1);
- dataBars
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('fill', (b, i) => `${BAR_COLOR_HOVER[i]}`);
- select('.chart-tooltip').transition().duration(200).style('opacity', 1);
- })
- .on('mouseout', function () {
- select(this).style('opacity', 0);
- select('.chart-tooltip').style('opacity', 0);
- dataBars
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('fill', (b, i) => `${LIGHT_AND_DARK_BLUE[i]}`);
- })
- .on('mousemove', function (chartData) {
- select('.chart-tooltip')
- .style('opacity', 1)
- .style('max-width', '200px')
- .style('left', `${event.pageX - 325}px`)
- .style('top', `${event.pageY - 140}px`)
- .text(
- `${Math.round((chartData.total * 100) / totalCount)}% of total client counts:
- ${chartData.non_entity_tokens} non-entity tokens, ${chartData.distinct_entities} unique entities.
- `
- );
- });
-
- // handles mouseover/out/move event for y-axis legend
- yLegendBars
- .on('click', function (chartData) {
- if (handleClick) {
- handleClick(chartData);
- }
- })
- .on('mouseover', function (chartData) {
- dataBars
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('fill', (b, i) => `${BAR_COLOR_HOVER[i]}`);
- actionBarSelection
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('opacity', '1');
- if (chartData.label.length >= CHAR_LIMIT) {
- select('.chart-tooltip').transition().duration(200).style('opacity', 1);
- }
- })
- .on('mouseout', function () {
- select('.chart-tooltip').style('opacity', 0);
- dataBars
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('fill', (b, i) => `${LIGHT_AND_DARK_BLUE[i]}`);
- actionBarSelection
- .filter(function () {
- return compareAttributes(this, event.target, 'y');
- })
- .style('opacity', '0');
- })
- .on('mousemove', function (chartData) {
- if (chartData.label.length >= CHAR_LIMIT) {
- select('.chart-tooltip')
- .style('left', `${event.pageX - 300}px`)
- .style('top', `${event.pageY - 100}px`)
- .text(`${chartData.label}`)
- .style('max-width', 'fit-content');
- } else {
- select('.chart-tooltip').style('opacity', 0);
- }
- });
-
- chartSvg
- .append('g')
- .attr('transform', `translate(${CHART_MARGIN.left}, ${CHART_MARGIN.top + 2})`)
- .selectAll('text')
- .data(dataset)
- .enter()
- .append('text')
- .text((d) => d.total)
- .attr('fill', '#000')
- .attr('class', 'total-value')
- .style('font-size', '.8rem')
- .attr('text-anchor', 'start')
- .attr('alignment-baseline', 'mathematical')
- .attr('x', (chartData) => `${xScale(chartData.total)}%`)
- .attr('y', (chartData) => yScale(chartData.label));
-
- chartSvg.select('.domain').remove();
-
- // TODO: if mapLegend has more than 4 keys, y attrs ('cy' and 'y') will need to be set to a variable. Currently map keys are centered in the legend SVG (50%)
- // each map key symbol & label takes up 20% of legend SVG width
- let startingXCoordinate = 100 - this.mapLegend.length * 20; // subtract from 100% to find starting x-coordinate
- let legendSvg = select('.legend');
- this.mapLegend.map((legend, i) => {
- let xCoordinate = startingXCoordinate + i * 20;
- legendSvg
- .append('circle')
- .attr('cx', `${xCoordinate}%`)
- .attr('cy', '50%')
- .attr('r', 6)
- .style('fill', `${LIGHT_AND_DARK_BLUE[i]}`);
- legendSvg
- .append('text')
- .attr('x', `${xCoordinate + 2}%`)
- .attr('y', '50%')
- .text(`${legend.label}`)
- .style('font-size', '.8rem')
- .attr('alignment-baseline', 'middle');
- });
- }
-}
-
-export default setComponentTemplate(layout, BarChartComponent);
diff --git a/ui/lib/core/addon/templates/components/bar-chart.hbs b/ui/lib/core/addon/templates/components/bar-chart.hbs
deleted file mode 100644
index 8e4769c40..000000000
--- a/ui/lib/core/addon/templates/components/bar-chart.hbs
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
{{@title}}
- {{#if @description}}
-
{{@description}}
- {{/if}}
-
-
- {{#if @dataset}}
- {{yield}}
- {{/if}}
-
-
- {{#if @dataset}}
-
-
-
-
-
-
-
-
- {{else}}
-
-
- {{/if}}
-
\ No newline at end of file
diff --git a/ui/lib/core/app/components/bar-chart.js b/ui/lib/core/app/components/bar-chart.js
deleted file mode 100644
index 085613131..000000000
--- a/ui/lib/core/app/components/bar-chart.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from 'core/components/bar-chart';
diff --git a/ui/lib/core/stories/bar-chart.md b/ui/lib/core/stories/bar-chart.md
deleted file mode 100644
index 56eff06f0..000000000
--- a/ui/lib/core/stories/bar-chart.md
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-## BarChart
-BarChart components are used to display data in the form of a stacked bar chart, with accompanying legend and tooltip. Anything passed into the block will display in the top right of the chart header.
-
-**Params**
-
-| Param | Type | Default | Description |
-| --- | --- | --- | --- |
-| title | string | | title of the chart |
-| mapLegend | array | | array of objects with key names 'key' and 'label' for the map legend |
-| dataset | object | | dataset for the chart |
-| [description] | string | | description of the chart |
-| [labelKey] | string | "label" | labelKey is the key name in the dataset passed in that corresponds to the value labeling the y-axis |
-| [onClick] | function | | takes function from parent and passes it to click event on data bars |
-
-**Example**
-
-```js
-
- />
-
-
- mapLegendSample = [{
- key: "api_key_for_label",
- label: "Label Displayed on Legend"
- }]
-```
-
-**See**
-
-- [Uses of BarChart](https://github.com/hashicorp/vault/search?l=Handlebars&q=BarChart+OR+bar-chart)
-- [BarChart Source Code](https://github.com/hashicorp/vault/blob/main/ui/lib/core/addon/components/bar-chart.js)
-
----
diff --git a/ui/lib/core/stories/bar-chart.stories.js b/ui/lib/core/stories/bar-chart.stories.js
deleted file mode 100644
index 761e7daab..000000000
--- a/ui/lib/core/stories/bar-chart.stories.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import hbs from 'htmlbars-inline-precompile';
-import { storiesOf } from '@storybook/ember';
-import { object, text, withKnobs } from '@storybook/addon-knobs';
-import notes from './bar-chart.md';
-
-const dataset = [
- {
- namespace_id: 'root',
- namespace_path: 'root',
- counts: {
- distinct_entities: 268,
- non_entity_tokens: 985,
- clients: 1253,
- },
- },
- {
- namespace_id: 'O0i4m',
- namespace_path: 'top-namespace',
- counts: {
- distinct_entities: 648,
- non_entity_tokens: 220,
- clients: 868,
- },
- },
- {
- namespace_id: '1oihz',
- namespace_path: 'anotherNamespace',
- counts: {
- distinct_entities: 547,
- non_entity_tokens: 337,
- clients: 884,
- },
- },
- {
- namespace_id: '1oihz',
- namespace_path: 'someOtherNamespaceawgagawegawgawgawgaweg',
- counts: {
- distinct_entities: 807,
- non_entity_tokens: 234,
- clients: 1041,
- },
- },
-];
-
-const flattenData = () => {
- 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'],
- };
- });
-};
-
-storiesOf('BarChart', module)
- .addParameters({ options: { showPanel: true } })
- .addDecorator(withKnobs())
- .add(
- `BarChart`,
- () => ({
- template: hbs`
-
Bar Chart
-
-
dataset is passed to a function in the parent to format it appropriately for the chart. Any data passed should be flattened (not nested).
-
The legend typically displays within the bar chart border, below the second grey divider. There is also a tooltip that pops up when hovering over the data bars and overflowing labels. Gotta love storybook :)
-
-
-
-
-
-
Legend:
-
-
- `,
- context: {
- title: text('title', 'Top Namespaces'),
- description: text(
- 'description',
- 'Each namespaces client count includes clients in child namespaces.'
- ),
- dataset: object('dataset', flattenData()),
- },
- }),
- { notes }
- );
diff --git a/ui/tests/integration/components/bar-chart-test.js b/ui/tests/integration/components/bar-chart-test.js
deleted file mode 100644
index a87e3dc2b..000000000
--- a/ui/tests/integration/components/bar-chart-test.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
-import { hbs } from 'ember-cli-htmlbars';
-
-module('Integration | Component | bar-chart', function (hooks) {
- setupRenderingTest(hooks);
-
- test('it renders', async function (assert) {
- let dataset = [
- {
- namespace_id: 'root',
- namespace_path: 'root',
- counts: {
- distinct_entities: 268,
- non_entity_tokens: 985,
- clients: 1253,
- },
- },
- {
- namespace_id: 'O0i4m',
- namespace_path: 'top-namespace',
- counts: {
- distinct_entities: 648,
- non_entity_tokens: 220,
- clients: 868,
- },
- },
- {
- namespace_id: '1oihz',
- namespace_path: 'anotherNamespace',
- counts: {
- distinct_entities: 547,
- non_entity_tokens: 337,
- clients: 884,
- },
- },
- {
- namespace_id: '1oihz',
- namespace_path: 'someOtherNamespaceawgagawegawgawgawgaweg',
- counts: {
- distinct_entities: 807,
- non_entity_tokens: 234,
- clients: 1041,
- },
- },
- ];
-
- let flattenData = () => {
- 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'],
- };
- });
- };
-
- this.set('title', 'Top Namespaces');
- this.set('description', 'Each namespaces client count includes clients in child namespaces.');
- this.set('dataset', flattenData());
-
- await render(hbs`
-
-
-
- `);
- assert.dom('[data-test-bar-chart]').exists('bar chart renders');
- assert.dom('[data-test-bar-chart] .chart-title').hasText('Top Namespaces', 'displays title');
- assert
- .dom('[data-test-bar-chart] .chart-description')
- .hasText('Each namespaces client count includes clients in child namespaces.', 'displays description');
- assert.dom('.data-bar').exists({ count: 8 }, 'bars render');
- });
-});
diff --git a/ui/tests/integration/components/pricing-metrics-dates-test.js b/ui/tests/integration/components/pricing-metrics-dates-test.js
deleted file mode 100644
index 4832027f4..000000000
--- a/ui/tests/integration/components/pricing-metrics-dates-test.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { render, fillIn } from '@ember/test-helpers';
-import hbs from 'htmlbars-inline-precompile';
-import { subMonths, startOfToday, format, endOfMonth } from 'date-fns';
-
-module('Integration | Component | pricing-metrics-dates', function (hooks) {
- setupRenderingTest(hooks);
-
- test('by default it sets the start and end inputs', async function (assert) {
- const expectedEnd = subMonths(startOfToday(), 1);
- const expectedStart = subMonths(expectedEnd, 12);
- await render(hbs`
-
- `);
- assert.dom('[data-test-end-input]').hasValue(format(expectedEnd, 'MM/yyyy'), 'End input is last month');
- assert
- .dom('[data-test-start-input]')
- .hasValue(format(expectedStart, 'MM/yyyy'), 'Start input is 12 months before last month');
- });
-
- test('On init if end date passed, start is calculated', async function (assert) {
- const expectedStart = subMonths(new Date(2020, 8, 15), 12);
- this.set('queryEnd', '09-2020');
- await render(hbs`
-
- `);
- assert.dom('[data-test-end-input]').hasValue('09/2020', 'End input matches query');
- assert
- .dom('[data-test-start-input]')
- .hasValue(format(expectedStart, 'MM/yyyy'), 'Start input is 12 months before end input');
- });
-
- test('On init if query start date passed, end is default', async function (assert) {
- const expectedEnd = subMonths(startOfToday(), 1);
- this.set('queryStart', '01-2020');
- await render(hbs`
-
- `);
- assert.dom('[data-test-end-input]').hasValue(format(expectedEnd, 'MM/yyyy'), 'End input is last month');
- assert.dom('[data-test-start-input]').hasValue('01/2020', 'Start input matches query');
- });
-
- test('If result and query dates are within 1 day, warning is not shown', async function (assert) {
- this.set('resultStart', new Date(2020, 1, 1));
- this.set('resultEnd', new Date(2020, 9, 31));
- await render(hbs`
-
- `);
- assert.dom('[data-test-results-date-warning]').doesNotExist('Does not show result states warning');
- });
-
- test('it shows appropriate errors on input form', async function (assert) {
- const lastAvailable = endOfMonth(subMonths(startOfToday(), 1));
- const firstAvailable = subMonths(lastAvailable, 12);
- await render(hbs`
-
- `);
- assert.dom('[data-test-form-error]').doesNotExist('No form error shows by default');
-
- await fillIn('[data-test-start-input]', format(subMonths(firstAvailable, 1), 'MM/yyyy'));
- assert
- .dom('[data-test-form-error]')
- .includesText(
- `No data retained before ${format(firstAvailable, 'MM/yyyy')}`,
- 'shows the correct error message for starting before the configured retainment period'
- );
-
- await fillIn('[data-test-end-input]', format(subMonths(lastAvailable, -1), 'MM/yyyy'));
- assert
- .dom('[data-test-form-error]')
- .includesText(
- 'Data is not available until the end of the month',
- 'shows the correct error message for ending after the end of the last month'
- );
-
- await fillIn('[data-test-end-input]', 'not/date');
- assert
- .dom('[data-test-form-error]')
- .includesText(
- 'End date is invalid. Please use format MM/yyyy',
- 'shows the correct error message for non-date input'
- );
-
- await fillIn('[data-test-start-input]', `13/${format(lastAvailable, 'yyyy')}`);
- assert
- .dom('[data-test-form-error]')
- .includesText(
- 'Start date is invalid. Please use format MM/yyyy',
- 'shows the correct error message for an invalid month'
- );
- });
-});