Skip to content

Commit

Permalink
UI/Client count running totals component (#14967)
Browse files Browse the repository at this point in the history
* wire up running total component

* remove waitUntil

* remove unused functions

* abstract x/y keys

* adjust tick size

* refactor helpers into utils

* cleanup

* cleanup utils files and imports

* update variables to const

* mock empty monthly data
  • Loading branch information
hellobontempo authored and Matt Schultz committed May 2, 2022
1 parent 4a8bb39 commit d8b0c4b
Show file tree
Hide file tree
Showing 12 changed files with 675 additions and 633 deletions.
8 changes: 8 additions & 0 deletions ui/app/components/clients/history.js
Expand Up @@ -168,6 +168,14 @@ export default class History extends Component {
return this.getActivityResponse.responseTimestamp;
}

get byMonthTotalClients() {
return this.getActivityResponse?.byMonthTotalClients;
}

get byMonthNewClients() {
return this.getActivityResponse?.byMonthNewClients;
}

get countsIncludeOlderData() {
let firstUpgrade = this.args.model.versionHistory[0];
if (!firstUpgrade) {
Expand Down
27 changes: 14 additions & 13 deletions ui/app/components/clients/line-chart.js
Expand Up @@ -17,7 +17,8 @@ import { LIGHT_AND_DARK_BLUE, SVG_DIMENSIONS, formatNumbers } from '../../utils/
* ```js
* <LineChart @dataset={dataset} />
* ```
* @param {array} dataset - dataset is an array of objects
* @param {string} xKey - string denoting key for x-axis data (data[xKey]) of dataset
* @param {string} yKey - string denoting key for y-axis data (data[yKey]) of dataset
*/

export default class LineChart extends Component {
Expand All @@ -28,7 +29,7 @@ export default class LineChart extends Component {
@tracked tooltipNew = '';
get yKey() {
return this.args.yKey || 'clients';
return this.args.yKey || 'total';
}
get xKey() {
Expand All @@ -41,42 +42,42 @@ export default class LineChart extends Component {
@action
renderChart(element, args) {
let dataset = args[0];
let chartSvg = select(element);
const dataset = args[0];
const chartSvg = select(element);
chartSvg.attr('viewBox', `-50 20 600 ${SVG_DIMENSIONS.height}`); // set svg dimensions
// DEFINE AXES SCALES
let yScale = scaleLinear()
const yScale = scaleLinear()
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([0, 100])
.nice();
let yAxisScale = scaleLinear()
const yAxisScale = scaleLinear()
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([SVG_DIMENSIONS.height, 0])
.nice();
let xScale = scalePoint() // use scaleTime()?
const xScale = scalePoint() // use scaleTime()?
.domain(dataset.map((d) => d[this.xKey]))
.range([0, SVG_DIMENSIONS.width])
.padding(0.2);
// CUSTOMIZE AND APPEND AXES
let yAxis = axisLeft(yAxisScale)
.ticks(7)
const yAxis = axisLeft(yAxisScale)
.ticks(4)
.tickPadding(10)
.tickSizeInner(-SVG_DIMENSIONS.width) // makes grid lines length of svg
.tickFormat(formatNumbers);
let xAxis = axisBottom(xScale).tickSize(0);
const xAxis = axisBottom(xScale).tickSize(0);
yAxis(chartSvg.append('g'));
xAxis(chartSvg.append('g').attr('transform', `translate(0, ${SVG_DIMENSIONS.height + 10})`));
chartSvg.selectAll('.domain').remove();
// PATH BETWEEN PLOT POINTS
let lineGenerator = line()
const lineGenerator = line()
.x((d) => xScale(d[this.xKey]))
.y((d) => yAxisScale(d[this.yKey]));
Expand Down Expand Up @@ -117,14 +118,14 @@ export default class LineChart extends Component {
.attr('cx', (d) => xScale(d[this.xKey]))
.attr('r', 10);
let hoverCircles = chartSvg.selectAll('.hover-circle');
const hoverCircles = chartSvg.selectAll('.hover-circle');
// MOUSE EVENT FOR TOOLTIP
hoverCircles.on('mouseover', (data) => {
// TODO: how to genericize this?
this.tooltipMonth = data[this.xKey];
this.tooltipTotal = `${data[this.yKey]} total clients`;
this.tooltipNew = `${data.new_clients?.clients} new clients`;
this.tooltipNew = `${data?.new_clients[this.yKey]} new clients`;
let node = hoverCircles.filter((plot) => plot[this.xKey] === data[this.xKey]).node();
this.tooltipTarget = node;
});
Expand Down
38 changes: 22 additions & 16 deletions ui/app/components/clients/vertical-bar-chart.js
Expand Up @@ -25,18 +25,28 @@ import {
* ```
* @param {array} dataset - dataset for the chart, must be an array of flattened objects
* @param {array} chartLegend - array of objects with key names 'key' and 'label' so data can be stacked
* @param {string} xKey - string denoting key for x-axis data (data[xKey]) of dataset
* @param {string} yKey - string denoting key for y-axis data (data[yKey]) of dataset
*/

export default class VerticalBarChart extends Component {
@tracked tooltipTarget = '';
@tracked tooltipTotal = '';
@tracked uniqueEntities = '';
@tracked nonEntityTokens = '';
@tracked entityClients = '';
@tracked nonEntityClients = '';
get chartLegend() {
return this.args.chartLegend;
}
get xKey() {
return this.args.xKey || 'month';
}
get yKey() {
return this.args.yKey || 'total';
}
@action
registerListener(element, args) {
let dataset = args[0];
Expand All @@ -47,12 +57,12 @@ export default class VerticalBarChart extends Component {
// DEFINE DATA BAR SCALES
let yScale = scaleLinear()
.domain([0, max(dataset.map((d) => d.clients))]) // TODO will need to recalculate when you get the data
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([0, 100])
.nice();
let xScale = scaleBand()
.domain(dataset.map((d) => d.month))
.domain(dataset.map((d) => d[this.xKey]))
.range([0, SVG_DIMENSIONS.width]) // set width to fix number of pixels
.paddingInner(0.85);
Expand All @@ -71,17 +81,17 @@ export default class VerticalBarChart extends Component {
.attr('width', '7px')
.attr('class', 'data-bar')
.attr('height', (stackedData) => `${yScale(stackedData[1] - stackedData[0])}%`)
.attr('x', ({ data }) => xScale(data.month)) // uses destructuring because was data.data.month
.attr('x', ({ data }) => xScale(data[this.xKey])) // uses destructuring because was data.data.month
.attr('y', (data) => `${100 - yScale(data[1])}%`); // subtract higher than 100% to give space for x axis ticks
// MAKE AXES //
let yAxisScale = scaleLinear()
.domain([0, max(dataset.map((d) => d.clients))]) // TODO will need to recalculate when you get the data
.domain([0, max(dataset.map((d) => d[this.yKey]))])
.range([`${SVG_DIMENSIONS.height}`, 0])
.nice();
let yAxis = axisLeft(yAxisScale)
.ticks(7)
.ticks(6)
.tickPadding(10)
.tickSizeInner(-SVG_DIMENSIONS.width)
.tickFormat(formatNumbers);
Expand Down Expand Up @@ -111,18 +121,14 @@ export default class VerticalBarChart extends Component {
.attr('height', '100%')
.attr('width', '30px') // three times width
.attr('y', '0') // start at bottom
.attr('x', (data) => xScale(data.month)); // not data.data because this is not stacked data
.attr('x', (data) => xScale(data[this.xKey])); // not data.data because this is not stacked data
// MOUSE EVENT FOR TOOLTIP
tooltipRect.on('mouseover', (data) => {
let hoveredMonth = data.month;
this.tooltipTotal = `${data.clients} ${data.new_clients ? 'total' : 'new'} clients`;
this.uniqueEntities = `${data.entity_clients} unique entities`;
this.nonEntityTokens = `${data.non_entity_clients} non-entity tokens`;
// let node = chartSvg
// .selectAll('rect.tooltip-rect')
// .filter(data => data.month === this.hoveredLabel)
// .node();
let hoveredMonth = data[this.xKey];
this.tooltipTotal = `${data[this.yKey]} ${data.new_clients ? 'total' : 'new'} clients`;
this.entityClients = `${data.entity_clients} entity clients`;
this.nonEntityClients = `${data.non_entity_clients} non-entity clients`;
let node = chartSvg
.selectAll('rect.data-bar')
// filter for the top data bar (so y-coord !== 0) with matching month
Expand Down
16 changes: 3 additions & 13 deletions ui/app/routes/vault/cluster/clients/history.js
Expand Up @@ -2,6 +2,7 @@ import Route from '@ember/routing/route';
import { isSameMonth } from 'date-fns';
import RSVP from 'rsvp';
import getStorage from 'vault/lib/token-storage';
import { parseRFC3339 } from 'core/utils/date-formatters';

const INPUTTED_START_DATE = 'vault:ui-inputted-start-date';
export default class HistoryRoute extends Route {
Expand All @@ -26,17 +27,6 @@ export default class HistoryRoute extends Route {
}
}

parseRFC3339(timestamp) {
// convert '2021-03-21T00:00:00Z' --> ['2021', 2] (e.g. 2021 March, month is zero indexed)
if (Array.isArray(timestamp)) {
// return if already formatted correctly
return timestamp;
}
return timestamp
? [timestamp.split('-')[0], Number(timestamp.split('-')[1].replace(/^0+/, '')) - 1]
: null;
}

async model() {
let parentModel = this.modelFor('vault.cluster.clients');
let licenseStart = await this.getLicenseStartTime();
Expand All @@ -45,8 +35,8 @@ export default class HistoryRoute extends Route {
return RSVP.hash({
config: parentModel.config,
activity,
startTimeFromLicense: this.parseRFC3339(licenseStart),
endTimeFromResponse: this.parseRFC3339(activity?.endTime),
startTimeFromLicense: parseRFC3339(licenseStart),
endTimeFromResponse: parseRFC3339(activity?.endTime),
versionHistory: parentModel.versionHistory,
});
}
Expand Down
23 changes: 9 additions & 14 deletions ui/app/serializers/clients/activity.js
@@ -1,5 +1,6 @@
import ApplicationSerializer from '../application';
import { formatISO } from 'date-fns';
import { parseAPITimestamp, parseRFC3339 } from 'core/utils/date-formatters';
export default class ActivitySerializer extends ApplicationSerializer {
flattenDataset(byNamespaceArray) {
return byNamespaceArray.map((ns) => {
Expand Down Expand Up @@ -34,20 +35,22 @@ export default class ActivitySerializer extends ApplicationSerializer {

// for vault usage - vertical bar chart
flattenByMonths(payload, isNewClients = false) {
const sortedPayload = [...payload];
sortedPayload.reverse();
if (isNewClients) {
return payload.map((m) => {
return sortedPayload?.map((m) => {
return {
month: m.timestamp,
month: parseAPITimestamp(m.timestamp, 'M/yy'),
entity_clients: m.new_clients.counts.entity_clients,
non_entity_clients: m.new_clients.counts.non_entity_clients,
total: m.new_clients.counts.clients,
namespaces: this.flattenDataset(m.new_clients.namespaces),
};
});
} else {
return payload.map((m) => {
return sortedPayload?.map((m) => {
return {
month: m.timestamp,
month: parseAPITimestamp(m.timestamp, 'M/yy'),
entity_clients: m.counts.entity_clients,
non_entity_clients: m.counts.non_entity_clients,
total: m.counts.clients,
Expand Down Expand Up @@ -88,13 +91,6 @@ export default class ActivitySerializer extends ApplicationSerializer {
return object;
}

parseRFC3339(timestamp) {
// convert '2021-03-21T00:00:00Z' --> ['2021', 2] (e.g. 2021 March, month is zero indexed)
return timestamp
? [timestamp.split('-')[0], Number(timestamp.split('-')[1].replace(/^0+/, '')) - 1]
: null;
}

normalizeResponse(store, primaryModelClass, payload, id, requestType) {
if (payload.id === 'no-data') {
return super.normalizeResponse(store, primaryModelClass, payload, id, requestType);
Expand All @@ -107,16 +103,15 @@ export default class ActivitySerializer extends ApplicationSerializer {
by_month_total_clients: this.flattenByMonths(payload.data.months),
by_month_new_clients: this.flattenByMonths(payload.data.months, { isNewClients: true }),
total: this.homogenizeClientNaming(payload.data.total),
formatted_end_time: this.parseRFC3339(payload.data.end_time),
formatted_start_time: this.parseRFC3339(payload.data.start_time),
formatted_end_time: parseRFC3339(payload.data.end_time),
formatted_start_time: parseRFC3339(payload.data.start_time),
};
delete payload.data.by_namespace;
delete payload.data.months;
delete payload.data.total;
return super.normalizeResponse(store, primaryModelClass, transformedPayload, id, requestType);
}
}

/*
SAMPLE PAYLOAD BEFORE/AFTER:
Expand Down
8 changes: 8 additions & 0 deletions ui/app/templates/components/clients/history.hbs
Expand Up @@ -131,6 +131,14 @@
<LayoutLoading />
{{else}}
{{#if this.totalUsageCounts}}
<Clients::RunningTotal
@chartLegend={{this.chartLegend}}
@barChartData={{this.byMonthNewClients}}
@lineChartData={{this.byMonthTotalClients}}
@runningTotals={{this.totalUsageCounts}}
@timestamp={{this.responseTimestamp}}
/>
{{!-- TODO CMB: remove UsageStats component from history tab (and update associated tests) --}}
<Clients::UsageStats @title="Total usage" @totalUsageCounts={{this.totalUsageCounts}} />
{{#if this.hasAttributionData}}
<Clients::Attribution
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/components/clients/line-chart.hbs
Expand Up @@ -3,6 +3,7 @@
class="chart has-grid"
{{on "mouseleave" this.removeTooltip}}
{{did-insert this.renderChart @dataset}}
{{did-update this.renderChart @dataset}}
>
</svg>

Expand Down

0 comments on commit d8b0c4b

Please sign in to comment.