Skip to content

Commit

Permalink
feat: add threshold line to timeseries charts [MA-3442] (#1825)
Browse files Browse the repository at this point in the history
  • Loading branch information
mihai-peteu authored Dec 4, 2024
1 parent 70e6741 commit 054b547
Show file tree
Hide file tree
Showing 14 changed files with 140 additions and 7 deletions.
2 changes: 2 additions & 0 deletions packages/analytics/analytics-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ yarn add @kong-ui-public/analytics-chart
- `stacked` option applies to timeseries charts as well as vertical/horizontal bar charts.
- `fill` only applies to time series line chart
- `chartTypes` defined [here](https://github.com/Kong/public-ui-components/blob/main/packages/analytics/analytics-utilities/src/types/chart-types.ts)
- `threshold` is optional
- A key / value pair of type `Record<ExploreAggregations: number>` will draw a dotted threshold line on a timeseries chart at the provided Y-axis value.
- `chartDatasetColors` are optional
- If no colors are provided, the default color palette will be used
- If custom colors are needed you may provide a custom color palette in the form of:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
:legend-position="legendPosition"
:show-annotations="showAnnotationsToggle"
:show-legend-values="showLegendValuesToggle"
:threshold="threshold"
:timeseries-zoom="timeSeriesZoomToggle"
tooltip-title="tooltip title"
@zoom-time-range="eventLog += 'Zoomed to ' + JSON.stringify($event) + '\n'"
Expand Down Expand Up @@ -257,7 +258,7 @@ import {
CsvExportModal,
CsvExportButton,
} from '../../src'
import type { AnalyticsExploreRecord, ExploreResultV4, QueryResponseMeta } from '@kong-ui-public/analytics-utilities'
import type { AnalyticsExploreRecord, ExploreAggregations, ExploreResultV4, QueryResponseMeta } from '@kong-ui-public/analytics-utilities'
import type { AnalyticsChartColors, AnalyticsChartOptions, ChartType } from '../../src/types'
import { isValidJson, rand } from '../utils/utils'
import { lookupDatavisColor } from '../../src/utils'
Expand All @@ -266,7 +267,6 @@ import type { SandboxNavigationItem } from '@kong-ui-public/sandbox-layout'
import { generateMultipleMetricTimeSeriesData, generateSingleMetricTimeSeriesData } from '@kong-ui-public/analytics-utilities'
import CodeText from '../CodeText.vue'
import { INJECT_QUERY_PROVIDER } from '../../src/constants'
import useEvaluateFeatureFlag from '../../src/composables/useEvauluateFeatureFlag'
enum Metrics {
TotalRequests = 'TotalRequests',
Expand Down Expand Up @@ -324,6 +324,10 @@ const serviceDimensionValues = ref(new Set([
'service1', 'service2', 'service3', 'service4', 'service5',
]))
const threshold = {
'request_count': 1250,
} as Record<ExploreAggregations, number>
const exportModalVisible = ref(false)
const setModalVisibility = (val: boolean) => {
exportModalVisible.value = val
Expand Down Expand Up @@ -379,6 +383,7 @@ const analyticsChartOptions = computed<AnalyticsChartOptions>(() => {
return {
type: chartType.value,
stacked: stackToggle.value,
threshold,
// chartDatasetColors: colorPalette.value,
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
:metric-unit="computedMetricUnit"
:stacked="chartOptions.stacked"
:synthetics-data-key="syntheticsDataKey"
:threshold="threshold"
:time-range-ms="timeRangeMs"
:tooltip-title="tooltipTitle"
:type="(chartOptions.type as ('timeseries_line' | 'timeseries_bar'))"
Expand Down Expand Up @@ -183,6 +184,11 @@ const props = defineProps({
required: false,
default: true,
},
threshold: {
type: Object as PropType<Record<ExploreAggregations, number>>,
required: false,
default: undefined,
},
timeseriesZoom: {
type: Boolean,
required: false,
Expand All @@ -207,6 +213,7 @@ const computedChartData = computed(() => {
{
fill: props.chartOptions.stacked,
colorPalette: props.chartOptions.chartDatasetColors || defaultStatusCodeColors,
threshold: props.chartOptions.threshold || undefined,
},
toRef(props, 'chartData'),
).value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ const htmlLegendPlugin = {
// @ts-ignore - ChartJS types are incomplete
legendItems.value = chart.options.plugins.legend.labels.generateLabels(chart)
.map(e => ({ ...e, value: props.legendValues && props.legendValues[e.text] }))
.filter(e => !e.value.isThreshold)
.sort(props.chartLegendSortFn)
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ export default function useChartLegendValues(chartData: Ref<KChartData>, chartTy
})) || `${approxNum(raw, { capital: true, ...(metricUnit.value === 'usd' && { prefix: '$' }) })} ${metricUnit.value}`
}

return { ...a, [v.label as string]: { raw, formatted } }
return {
...a,
[v.label as string]: {
raw,
formatted,
isThreshold: v.isThreshold,
},
}
}, {})
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { ExploreResultV4, AnalyticsExploreRecord } from '@kong-ui-public/analytics-utilities'
import type { ExploreAggregations, ExploreResultV4, AnalyticsExploreRecord } from '@kong-ui-public/analytics-utilities'
import { defaultLineOptions, lookupDatavisColor, datavisPalette, BORDER_WIDTH, NO_BORDER } from '../utils'
import type { Ref } from 'vue'
import { computed } from 'vue'
import type { Dataset, KChartData, ExploreToDatasetDeps, DatasetLabel } from '../types'
import { parseISO } from 'date-fns'
import { isNullOrUndef } from 'chart.js/helpers'
import composables from '../composables'
import { KUI_COLOR_BACKGROUND_NEUTRAL } from '@kong/design-tokens'

type MetricThreshold = Record<ExploreAggregations, number>

const range = (start: number, stop: number, step: number = 1): number[] =>
Array(Math.ceil((stop - start) / step)).fill(start).map((x, y) => x + y * step)
Expand Down Expand Up @@ -180,6 +183,30 @@ export default function useExploreResultToTimeDataset(
// sort by total, descending
datasets.sort((a, b) => (Number(a.total) < Number(b.total) ? -1 : 1))

// Draw threshold lines, if any
if (deps.threshold) {
for (const key of Object.keys(deps.threshold)) {
const thresholdValue = deps.threshold[key as keyof MetricThreshold]

if (thresholdValue) {
datasets.push({
type: 'line',
rawMetric: key,
isThreshold: true,
label: i18n.t('chartLabels.threshold'),
borderColor: KUI_COLOR_BACKGROUND_NEUTRAL,
borderWidth: 1.25,
borderDash: [12, 8],
fill: false,
order: -1, // Display above all other datasets
stack: 'custom', // Never stack this dataset
data: zeroFilledTimeSeries.map(ts => {
return { x: ts, y: thresholdValue }
}),
} as Dataset)
}
}
}
return {
datasets,
colorMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -726,4 +726,56 @@ describe('useVitalsExploreDatasets', () => {
expect(result.value.datasets[3].backgroundColor).toEqual('#ffd5b1')
expect(result.value.datasets[4].backgroundColor).toEqual('#ffb6b6')
})

it('displays a static threshold line on timeseries charts', () => {
const exploreResult: ComputedRef<ExploreResultV4> = computed(() => ({
data: [
{
timestamp: START_FOR_DAILY_QUERY.toISOString(),
event: {
metric1: 2,
metric2: 1,
},
} as GroupByResult,
{
timestamp: END_FOR_DAILY_QUERY.toISOString(),
event: {
metric1: 2,
metric2: 1,
},
} as GroupByResult,
],
meta: {
start_ms: Math.trunc(START_FOR_DAILY_QUERY.getTime()),
end_ms: Math.trunc(END_FOR_DAILY_QUERY.getTime()),
granularity_ms: 86400000,
metric_names: ['metric1', 'metric2'] as any as ExploreAggregations[],
display: {},
query_id: '',
metric_units: { metric1: 'units' } as MetricUnit,
},
}))

const result = useExploreResultToTimeDataset(
{
fill: false,
threshold: { 'request_count': 320 } as Record<ExploreAggregations, number>,
},
exploreResult,
)

expect(result.value.datasets[2].label).toEqual('Alert threshold')
expect(result.value.datasets[2].data).toEqual(
[
{
x: START_FOR_DAILY_QUERY.getTime(),
y: 320,
},
{
x: END_FOR_DAILY_QUERY.getTime(),
y: 320,
},
],
)
})
})
3 changes: 2 additions & 1 deletion packages/analytics/analytics-chart/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@
"cost": "Costs",
"llm_cache_embeddings_latency_average": "Embeddings Latency (avg)",
"llm_cache_fetch_latency_average": "Fetch Latency (avg)",
"llm_latency_average": "LLM Latency (avg)"
"llm_latency_average": "LLM Latency (avg)",
"threshold": "Alert threshold"
},
"metricAxisTitles": {
"request_count": "Request Count",
Expand Down
16 changes: 14 additions & 2 deletions packages/analytics/analytics-chart/src/types/chart-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import type { ChartData, ChartDataset, LegendItem } from 'chart.js'
import type { ChartMetricDisplay } from '../enums'
import type { ChartTooltipSortFn } from './chartjs-options'
import type { ChartType, SimpleChartType } from './chart-types'
import type { ExploreAggregations } from '@kong-ui-public/analytics-utilities'

// Chart.js extendend interfaces
export type Dataset = ChartDataset & { rawDimension: string, rawMetric?: string, total?: number, lineTension?: number, fill?: boolean }
export type Dataset = ChartDataset & { rawDimension: string,
rawMetric?: string,
total?: number,
lineTension?: number,
fill?: boolean,
isThreshold?: boolean
}

export interface KChartData extends ChartData {
datasets: Dataset[]
Expand All @@ -31,7 +38,8 @@ export interface AnalyticsChartColors {

export interface LegendValueEntry {
raw: number,
formatted: string
formatted: string,
isThreshold?: boolean,
}

/**
Expand Down Expand Up @@ -83,6 +91,10 @@ export interface AnalyticsChartOptions {
* Sort tooltip entries
*/
chartTooltipSortFn?: ChartTooltipSortFn,
/**
* A static or dynamic metric threshold to be displayed on a timeseries chart
*/
threshold?: Record<ExploreAggregations, number>,
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AnalyticsChartColors } from './chart-data'
import type { ExploreAggregations } from '@kong-ui-public/analytics-utilities'

/**
* Interace representing the various options
Expand All @@ -13,4 +14,5 @@ import type { AnalyticsChartColors } from './chart-data'
export interface ExploreToDatasetDeps {
colorPalette?: AnalyticsChartColors | string[]
fill?: boolean
threshold?: Record<ExploreAggregations, number>
}
6 changes: 6 additions & 0 deletions packages/analytics/analytics-utilities/src/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export function formatTime(ts: number, options: TimeFormatOptions = {}) {
}
}

/**
* Formatted display for a start and end time range
* @param start Date from
* @param end Date to
* @returns Human-readable date range string
*/
export function formatTimeRange(start: Date, end: Date) {
return `${formatTime(start.getTime())} - ${formatTime(end.getTime(), { includeTZ: true })}`
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import type { DashboardConfig, DashboardRendererContext, TileConfig, TileDefinit
import { DashboardRenderer } from '../../src'
import { inject, ref } from 'vue'
import { ChartMetricDisplay } from '@kong-ui-public/analytics-chart'
import type { ExploreAggregations } from '@kong-ui-public/analytics-utilities'
import type { SandboxNavigationItem } from '@kong-ui-public/sandbox-layout'
import { SandboxLayout } from '@kong-ui-public/sandbox-layout'
import '@kong-ui-public/sandbox-layout/dist/style.css'
Expand Down Expand Up @@ -164,6 +165,9 @@ const dashboardConfig: DashboardConfig = {
chart: {
type: 'timeseries_line',
chartTitle: 'Timeseries line chart of mock data',
threshold: {
'request_count': 3200,
} as Record<ExploreAggregations, number>,
},
query: {
datasource: 'basic',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const options = computed((): AnalyticsChartOptions => ({
type: props.chartOptions.type,
stacked: props.chartOptions.stacked ?? false,
chartDatasetColors: props.chartOptions.chartDatasetColors,
threshold: props.chartOptions.threshold,
}))
const exploreLink = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ export const timeseriesChartSchema = {
stacked: {
type: 'boolean',
},
threshold: {
type: 'object',
additionalProperties: {
type: 'number',
},
},
chartDatasetColors: chartDatasetColorsSchema,
syntheticsDataKey,
chartTitle,
Expand Down

0 comments on commit 054b547

Please sign in to comment.