/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { SVG_DIMENSIONS, formatNumbers } from 'vault/utils/chart-helpers';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import { format, isValid } from 'date-fns';
import { debug } from '@ember/debug';
import type { Count, MonthlyChartData, Timestamp } from 'vault/vault/charts/client-counts';
import type ClientsVersionHistoryModel from 'vault/models/clients/version-history';
interface Args {
dataset: MonthlyChartData[];
upgradeData: ClientsVersionHistoryModel[];
xKey?: string;
yKey?: string;
chartHeight?: number;
}
interface ChartData {
x: Date;
y: number | null;
new: number;
tooltipUpgrade: string | null;
month: string; // used for test selectors and to match key on upgradeData
}
interface UpgradeByMonth {
[key: string]: ClientsVersionHistoryModel;
}
/**
* @module LineChart
* LineChart components are used to display time-based data in a line plot with accompanying tooltip
*
* @example
* ```js
*
* ```
* @param {array} dataset - array of objects containing data to be plotted
* @param {string} [xKey=clients] - string denoting key for x-axis data of dataset. Should reference a timestamp string.
* @param {string} [yKey=timestamp] - string denoting key for y-axis data of dataset. Should reference a number or null.
* @param {array} [upgradeData=null] - array of objects containing version history from the /version-history endpoint
* @param {number} [chartHeight=190] - height of chart in pixels
*/
export default class LineChart extends Component {
// Chart settings
get yKey() {
return this.args.yKey || 'clients';
}
get xKey() {
return this.args.xKey || 'timestamp';
}
get chartHeight() {
return this.args.chartHeight || SVG_DIMENSIONS.height;
}
// Plot points
get data(): ChartData[] {
try {
return this.args.dataset?.map((datum) => {
const timestamp = parseAPITimestamp(datum[this.xKey as keyof Timestamp]) as Date;
if (isValid(timestamp) === false)
throw new Error(`Unable to parse value "${datum[this.xKey as keyof Timestamp]}" as date`);
const upgradeMessage = this.getUpgradeMessage(datum);
return {
x: timestamp,
y: (datum[this.yKey as keyof Count] as number) ?? null,
new: this.getNewClients(datum),
tooltipUpgrade: upgradeMessage,
month: datum.month,
};
});
} catch (e) {
debug(e as string);
return [];
}
}
get upgradedMonths() {
return this.data.filter((datum) => datum.tooltipUpgrade);
}
// Domains
get yDomain() {
const counts: number[] = this.data
.map((d) => d.y)
.flatMap((num) => (typeof num === 'number' ? [num] : []));
const max = Math.max(...counts);
// if max is <=4, hardcode 4 which is the y-axis tickCount so y-axes are not decimals
return [0, max <= 4 ? 4 : max];
}
get timeDomain() {
// assume data is sorted by time
const firstTime = this.data[0]?.x;
const lastTime = this.data[this.data.length - 1]?.x;
return [firstTime, lastTime];
}
get upgradeByMonthYear(): UpgradeByMonth {
const empty: UpgradeByMonth = {};
if (!Array.isArray(this.args.upgradeData)) return empty;
return (
this.args.upgradeData?.reduce((acc, upgrade) => {
if (upgrade.timestampInstalled) {
const key = parseAPITimestamp(upgrade.timestampInstalled, 'M/yy');
acc[key as string] = upgrade;
}
return acc;
}, empty) || empty
);
}
getUpgradeMessage(datum: MonthlyChartData) {
const upgradeInfo = this.upgradeByMonthYear[datum.month as string];
if (upgradeInfo) {
const { version, previousVersion } = upgradeInfo;
return `Vault was upgraded
${previousVersion ? 'from ' + previousVersion : ''} to ${version}`;
}
return null;
}
getNewClients(datum: MonthlyChartData) {
if (!datum?.new_clients) return 0;
return (datum?.new_clients[this.yKey as keyof Count] as number) || 0;
}
hasValue = (count: number | null) => {
return typeof count === 'number' ? true : false;
};
// These functions are used by the tooltip
formatCount = (count: number) => {
return formatNumbers([count]);
};
formatMonth = (date: Date) => {
return format(date, 'M/yy');
};
tooltipX = (original: number) => {
return original.toString();
};
tooltipY = (original: number) => {
return `${this.chartHeight - original + 15}`;
};
}