* feat: time picker hint and timezone picker UI with basic functionality + helper to get timezones * feat: add support for esc keypress to close the timezone picker * chore: add the selected timezone as url param and close timezone picker on select * fix: overall improvement + add searchIndex to timezone * feat: timezone preferences UI * chore: improve timezone utils * chore: change timezone item from div to button * feat: display timezone in timepicker input * chore: fix the typo * fix: don't focus on time picker when timezone is clicked * fix: fix the issue of timezone breaking for browser and utc timezones * fix: display the timezone in timepicker hint 'You are at' * feat: timezone basic functionality (#6492) * chore: change div to fragment + change type to any as the ESLint complains otherwise * chore: manage etc timezone filtering with an arg * chore: update timezone wrapper class name * fix: add timezone support to downloaded logs * feat: add current timezone to dashboard list and configure metadata modal * fix: add pencil icon next to timezone hint + change the copy to Current timezone * fix: properly handle the escape button behavior for timezone picker * chore: replace @vvo/tzdb with native Intl API for timezones * feat: lightmode for timezone picker and timezone adaptation components * fix: use normald tz in browser timezone * fix: timezone picker lightmode fixes * feat: display selected time range in 12 hour format * chore: remove unnecessary optional chaining * fix: fix the typo in css variable * chore: add em dash and change icon for timezone hint in date/time picker * chore: move pen line icon to the right of timezone offset * fix: fix the failing tests * feat: handle switching off the timezone adaptation
225 lines
5.3 KiB
TypeScript
225 lines
5.3 KiB
TypeScript
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
|
|
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
|
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
|
import dayjs from 'dayjs';
|
|
import { MutableRefObject } from 'react';
|
|
|
|
import { getAxisLabelColor } from './helpers';
|
|
import {
|
|
createDragSelectPluginOptions,
|
|
dragSelectPluginId,
|
|
} from './Plugin/DragSelect';
|
|
import {
|
|
createIntersectionCursorPluginOptions,
|
|
intersectionCursorPluginId,
|
|
} from './Plugin/IntersectionCursor';
|
|
import {
|
|
CustomChartOptions,
|
|
GraphOnClickHandler,
|
|
IAxisTimeConfig,
|
|
StaticLineProps,
|
|
} from './types';
|
|
import { getToolTipValue, getYAxisFormattedValue } from './yAxisConfig';
|
|
|
|
export const toggleGraph = (
|
|
graphIndex: number,
|
|
isVisible: boolean,
|
|
lineChartRef: MutableRefObject<Chart | undefined>,
|
|
): void => {
|
|
if (lineChartRef && lineChartRef.current) {
|
|
const { type } = lineChartRef.current?.config as ChartConfiguration;
|
|
if (type === 'pie' || type === 'doughnut') {
|
|
lineChartRef.current?.toggleDataVisibility(graphIndex);
|
|
} else {
|
|
lineChartRef.current?.setDatasetVisibility(graphIndex, isVisible);
|
|
}
|
|
lineChartRef.current?.update();
|
|
}
|
|
};
|
|
|
|
export const getGraphOptions = (
|
|
animate: boolean,
|
|
staticLine: StaticLineProps | undefined,
|
|
title: string | undefined,
|
|
nearestDatasetIndex: MutableRefObject<number | null>,
|
|
yAxisUnit: string,
|
|
onDragSelect: ((start: number, end: number) => void) | undefined,
|
|
dragSelectColor: string | undefined,
|
|
currentTheme: 'dark' | 'light',
|
|
getGridColor: () => 'rgba(231,233,237,0.1)' | 'rgba(231,233,237,0.8)',
|
|
xAxisTimeUnit: IAxisTimeConfig,
|
|
isStacked: boolean | undefined,
|
|
onClickHandler: GraphOnClickHandler | undefined,
|
|
data: ChartData,
|
|
timezone: Timezone,
|
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
): CustomChartOptions => ({
|
|
animation: {
|
|
duration: animate ? 200 : 0,
|
|
},
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
},
|
|
plugins: {
|
|
annotation: staticLine
|
|
? {
|
|
annotations: [
|
|
{
|
|
type: 'line',
|
|
yMin: staticLine.yMin,
|
|
yMax: staticLine.yMax,
|
|
borderColor: staticLine.borderColor,
|
|
borderWidth: staticLine.borderWidth,
|
|
label: {
|
|
content: staticLine.lineText,
|
|
enabled: true,
|
|
font: {
|
|
size: 10,
|
|
},
|
|
borderWidth: 0,
|
|
position: 'start',
|
|
backgroundColor: 'transparent',
|
|
color: staticLine.textColor,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
: undefined,
|
|
title: {
|
|
display: title !== undefined,
|
|
text: title,
|
|
},
|
|
legend: {
|
|
display: false,
|
|
},
|
|
tooltip: {
|
|
callbacks: {
|
|
title(context): string | string[] {
|
|
const date = dayjs(context[0].parsed.x);
|
|
return date.tz(timezone.value).format('MMM DD, YYYY, HH:mm:ss');
|
|
},
|
|
label(context): string | string[] {
|
|
let label = context.dataset.label || '';
|
|
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if (context.parsed.y !== null) {
|
|
label += getToolTipValue(context.parsed.y.toString(), yAxisUnit);
|
|
}
|
|
|
|
return label;
|
|
},
|
|
labelTextColor(labelData): Color {
|
|
if (labelData.datasetIndex === nearestDatasetIndex.current) {
|
|
return 'rgba(255, 255, 255, 1)';
|
|
}
|
|
|
|
return 'rgba(255, 255, 255, 0.75)';
|
|
},
|
|
},
|
|
position: 'custom',
|
|
itemSort(item1, item2): number {
|
|
return item2.parsed.y - item1.parsed.y;
|
|
},
|
|
},
|
|
[dragSelectPluginId]: createDragSelectPluginOptions(
|
|
!!onDragSelect,
|
|
onDragSelect,
|
|
dragSelectColor,
|
|
),
|
|
[intersectionCursorPluginId]: createIntersectionCursorPluginOptions(
|
|
!!onDragSelect,
|
|
currentTheme === 'dark' ? 'white' : 'black',
|
|
),
|
|
},
|
|
layout: {
|
|
padding: 0,
|
|
},
|
|
scales: {
|
|
x: {
|
|
stacked: isStacked,
|
|
grid: {
|
|
display: true,
|
|
color: getGridColor(),
|
|
drawTicks: true,
|
|
},
|
|
adapters: {
|
|
date: chartjsAdapter,
|
|
},
|
|
time: {
|
|
unit: xAxisTimeUnit?.unitName || 'minute',
|
|
stepSize: xAxisTimeUnit?.stepSize || 1,
|
|
displayFormats: {
|
|
millisecond: 'HH:mm:ss',
|
|
second: 'HH:mm:ss',
|
|
minute: 'HH:mm',
|
|
hour: 'MM/dd HH:mm',
|
|
day: 'MM/dd',
|
|
week: 'MM/dd',
|
|
month: 'yy-MM',
|
|
year: 'yy',
|
|
},
|
|
},
|
|
type: 'time',
|
|
ticks: { color: getAxisLabelColor(currentTheme) },
|
|
},
|
|
y: {
|
|
stacked: isStacked,
|
|
display: true,
|
|
grid: {
|
|
display: true,
|
|
color: getGridColor(),
|
|
},
|
|
ticks: {
|
|
color: getAxisLabelColor(currentTheme),
|
|
// Include a dollar sign in the ticks
|
|
callback(value): string {
|
|
return getYAxisFormattedValue(value.toString(), yAxisUnit);
|
|
},
|
|
},
|
|
},
|
|
},
|
|
elements: {
|
|
line: {
|
|
tension: 0,
|
|
cubicInterpolationMode: 'monotone',
|
|
},
|
|
point: {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
hoverBackgroundColor: (ctx: any): string => {
|
|
if (ctx?.element?.options?.borderColor) {
|
|
return ctx.element.options.borderColor;
|
|
}
|
|
return 'rgba(0,0,0,0.1)';
|
|
},
|
|
hoverRadius: 5,
|
|
},
|
|
},
|
|
onClick: (event, element, chart): void => {
|
|
if (onClickHandler) {
|
|
onClickHandler(event, element, chart, data);
|
|
}
|
|
},
|
|
onHover: (event, _, chart): void => {
|
|
if (event.native) {
|
|
const interactions = chart.getElementsAtEventForMode(
|
|
event.native,
|
|
'nearest',
|
|
{
|
|
intersect: false,
|
|
},
|
|
true,
|
|
);
|
|
|
|
if (interactions[0]) {
|
|
// eslint-disable-next-line no-param-reassign
|
|
nearestDatasetIndex.current = interactions[0].datasetIndex;
|
|
}
|
|
}
|
|
},
|
|
});
|