mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 08:29:03 +08:00
feat: drag select timeframe on charts (#2018)
* feat: add drag select functionality to chart * fix: use redux stored values for time frame selection * fix: ignore clicks on chart without dragging * feat: add intersection cursor to chart * refactor: update drag-select chart plugin * fix: respond to drag-select mouseup outside of chart * fix: remove unnecessary chart update * feat: add drag-select to dashboard charts * refactor: add util functions to create custom plugin options * fix: enable custom chart plugins Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Ankit Nayan <ankit@signoz.io>
This commit is contained in:
parent
153e859ac3
commit
1e39131c38
321
frontend/src/components/Graph/Plugin/DragSelect.ts
Normal file
321
frontend/src/components/Graph/Plugin/DragSelect.ts
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
import { Chart, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||||
|
import * as ChartHelpers from 'chart.js/helpers';
|
||||||
|
|
||||||
|
// utils
|
||||||
|
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||||
|
|
||||||
|
export const dragSelectPluginId = 'drag-select-plugin';
|
||||||
|
|
||||||
|
type ChartDragHandlers = {
|
||||||
|
mousedown: ChartEventHandler;
|
||||||
|
mousemove: ChartEventHandler;
|
||||||
|
mouseup: ChartEventHandler;
|
||||||
|
globalMouseup: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DragSelectPluginOptions = {
|
||||||
|
color?: string;
|
||||||
|
onSelect?: (startValueX: number, endValueX: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultDragSelectPluginOptions: Required<DragSelectPluginOptions> = {
|
||||||
|
color: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
onSelect: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createDragSelectPluginOptions(
|
||||||
|
isEnabled: boolean,
|
||||||
|
onSelect?: (start: number, end: number) => void,
|
||||||
|
color?: string,
|
||||||
|
): DragSelectPluginOptions | false {
|
||||||
|
if (!isEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onSelect,
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMousedownHandler(
|
||||||
|
chart: Chart,
|
||||||
|
dragData: DragSelectData,
|
||||||
|
): ChartEventHandler {
|
||||||
|
return (ev): void => {
|
||||||
|
const { left, right } = chart.chartArea;
|
||||||
|
|
||||||
|
let { x: startDragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||||
|
|
||||||
|
if (left > startDragPositionX) {
|
||||||
|
startDragPositionX = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right < startDragPositionX) {
|
||||||
|
startDragPositionX = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startValuePositionX = chart.scales.x.getValueForPixel(
|
||||||
|
startDragPositionX,
|
||||||
|
);
|
||||||
|
|
||||||
|
dragData.onDragStart(startDragPositionX, startValuePositionX);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMousemoveHandler(
|
||||||
|
chart: Chart,
|
||||||
|
dragData: DragSelectData,
|
||||||
|
): ChartEventHandler {
|
||||||
|
return (ev): void => {
|
||||||
|
if (!dragData.isMouseDown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { left, right } = chart.chartArea;
|
||||||
|
|
||||||
|
let { x: dragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||||
|
|
||||||
|
if (left > dragPositionX) {
|
||||||
|
dragPositionX = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right < dragPositionX) {
|
||||||
|
dragPositionX = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valuePositionX = chart.scales.x.getValueForPixel(dragPositionX);
|
||||||
|
|
||||||
|
dragData.onDrag(dragPositionX, valuePositionX);
|
||||||
|
chart.update('none');
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMouseupHandler(
|
||||||
|
chart: Chart,
|
||||||
|
options: DragSelectPluginOptions,
|
||||||
|
dragData: DragSelectData,
|
||||||
|
): ChartEventHandler {
|
||||||
|
return (ev): void => {
|
||||||
|
const { left, right } = chart.chartArea;
|
||||||
|
|
||||||
|
let { x: endRelativePostionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||||
|
|
||||||
|
if (left > endRelativePostionX) {
|
||||||
|
endRelativePostionX = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right < endRelativePostionX) {
|
||||||
|
endRelativePostionX = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
const endValuePositionX = chart.scales.x.getValueForPixel(
|
||||||
|
endRelativePostionX,
|
||||||
|
);
|
||||||
|
|
||||||
|
dragData.onDragEnd(endRelativePostionX, endValuePositionX);
|
||||||
|
|
||||||
|
chart.update('none');
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof options.onSelect === 'function' &&
|
||||||
|
typeof dragData.startValuePositionX === 'number' &&
|
||||||
|
typeof dragData.endValuePositionX === 'number'
|
||||||
|
) {
|
||||||
|
const start = Math.min(
|
||||||
|
dragData.startValuePositionX,
|
||||||
|
dragData.endValuePositionX,
|
||||||
|
);
|
||||||
|
const end = Math.max(
|
||||||
|
dragData.startValuePositionX,
|
||||||
|
dragData.endValuePositionX,
|
||||||
|
);
|
||||||
|
|
||||||
|
options.onSelect(start, end);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createGlobalMouseupHandler(
|
||||||
|
options: DragSelectPluginOptions,
|
||||||
|
dragData: DragSelectData,
|
||||||
|
): () => void {
|
||||||
|
return (): void => {
|
||||||
|
const { isDragging, endRelativePixelPositionX, endValuePositionX } = dragData;
|
||||||
|
|
||||||
|
if (!isDragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragData.onDragEnd(
|
||||||
|
endRelativePixelPositionX as number,
|
||||||
|
endValuePositionX as number,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof options.onSelect === 'function' &&
|
||||||
|
typeof dragData.startValuePositionX === 'number' &&
|
||||||
|
typeof dragData.endValuePositionX === 'number'
|
||||||
|
) {
|
||||||
|
const start = Math.min(
|
||||||
|
dragData.startValuePositionX,
|
||||||
|
dragData.endValuePositionX,
|
||||||
|
);
|
||||||
|
const end = Math.max(
|
||||||
|
dragData.startValuePositionX,
|
||||||
|
dragData.endValuePositionX,
|
||||||
|
);
|
||||||
|
|
||||||
|
options.onSelect(start, end);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class DragSelectData {
|
||||||
|
public isDragging = false;
|
||||||
|
|
||||||
|
public isMouseDown = false;
|
||||||
|
|
||||||
|
public startRelativePixelPositionX: number | null = null;
|
||||||
|
|
||||||
|
public startValuePositionX: number | null | undefined = null;
|
||||||
|
|
||||||
|
public endRelativePixelPositionX: number | null = null;
|
||||||
|
|
||||||
|
public endValuePositionX: number | null | undefined = null;
|
||||||
|
|
||||||
|
public initialize(): void {
|
||||||
|
this.isDragging = false;
|
||||||
|
this.isMouseDown = false;
|
||||||
|
this.startRelativePixelPositionX = null;
|
||||||
|
this.startValuePositionX = null;
|
||||||
|
this.endRelativePixelPositionX = null;
|
||||||
|
this.endValuePositionX = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDragStart(
|
||||||
|
startRelativePixelPositionX: number,
|
||||||
|
startValuePositionX: number | undefined,
|
||||||
|
): void {
|
||||||
|
this.isDragging = false;
|
||||||
|
this.isMouseDown = true;
|
||||||
|
this.startRelativePixelPositionX = startRelativePixelPositionX;
|
||||||
|
this.startValuePositionX = startValuePositionX;
|
||||||
|
this.endRelativePixelPositionX = null;
|
||||||
|
this.endValuePositionX = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDrag(
|
||||||
|
endRelativePixelPositionX: number,
|
||||||
|
endValuePositionX: number | undefined,
|
||||||
|
): void {
|
||||||
|
this.isDragging = true;
|
||||||
|
this.endRelativePixelPositionX = endRelativePixelPositionX;
|
||||||
|
this.endValuePositionX = endValuePositionX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDragEnd(
|
||||||
|
endRelativePixelPositionX: number,
|
||||||
|
endValuePositionX: number | undefined,
|
||||||
|
): void {
|
||||||
|
if (!this.isDragging) {
|
||||||
|
this.initialize();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDragging = false;
|
||||||
|
this.isMouseDown = false;
|
||||||
|
this.endRelativePixelPositionX = endRelativePixelPositionX;
|
||||||
|
this.endValuePositionX = endValuePositionX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDragSelectPlugin = (): Plugin<
|
||||||
|
keyof ChartTypeRegistry,
|
||||||
|
DragSelectPluginOptions
|
||||||
|
> => {
|
||||||
|
const dragData = new DragSelectData();
|
||||||
|
let pluginOptions: Required<DragSelectPluginOptions>;
|
||||||
|
|
||||||
|
const handlers: ChartDragHandlers = {
|
||||||
|
mousedown: () => {},
|
||||||
|
mousemove: () => {},
|
||||||
|
mouseup: () => {},
|
||||||
|
globalMouseup: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dragSelectPlugin: Plugin<
|
||||||
|
keyof ChartTypeRegistry,
|
||||||
|
DragSelectPluginOptions
|
||||||
|
> = {
|
||||||
|
id: dragSelectPluginId,
|
||||||
|
start: (chart: Chart, _, passedOptions) => {
|
||||||
|
pluginOptions = mergeDefaultOptions(
|
||||||
|
passedOptions,
|
||||||
|
defaultDragSelectPluginOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { canvas } = chart;
|
||||||
|
|
||||||
|
dragData.initialize();
|
||||||
|
|
||||||
|
const mousedownHandler = createMousedownHandler(chart, dragData);
|
||||||
|
const mousemoveHandler = createMousemoveHandler(chart, dragData);
|
||||||
|
const mouseupHandler = createMouseupHandler(chart, pluginOptions, dragData);
|
||||||
|
const globalMouseupHandler = createGlobalMouseupHandler(
|
||||||
|
pluginOptions,
|
||||||
|
dragData,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', mousedownHandler, { passive: true });
|
||||||
|
canvas.addEventListener('mousemove', mousemoveHandler, { passive: true });
|
||||||
|
canvas.addEventListener('mouseup', mouseupHandler, { passive: true });
|
||||||
|
document.addEventListener('mouseup', globalMouseupHandler, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
handlers.mousedown = mousedownHandler;
|
||||||
|
handlers.mousemove = mousemoveHandler;
|
||||||
|
handlers.mouseup = mouseupHandler;
|
||||||
|
handlers.globalMouseup = globalMouseupHandler;
|
||||||
|
},
|
||||||
|
beforeDestroy: (chart: Chart) => {
|
||||||
|
const { canvas } = chart;
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.removeEventListener('mousedown', handlers.mousedown);
|
||||||
|
canvas.removeEventListener('mousemove', handlers.mousemove);
|
||||||
|
canvas.removeEventListener('mouseup', handlers.mouseup);
|
||||||
|
document.removeEventListener('mouseup', handlers.globalMouseup);
|
||||||
|
},
|
||||||
|
afterDatasetsDraw: (chart: Chart) => {
|
||||||
|
const {
|
||||||
|
startRelativePixelPositionX,
|
||||||
|
endRelativePixelPositionX,
|
||||||
|
isDragging,
|
||||||
|
} = dragData;
|
||||||
|
|
||||||
|
if (startRelativePixelPositionX && endRelativePixelPositionX && isDragging) {
|
||||||
|
const left = Math.min(
|
||||||
|
startRelativePixelPositionX,
|
||||||
|
endRelativePixelPositionX,
|
||||||
|
);
|
||||||
|
const right = Math.max(
|
||||||
|
startRelativePixelPositionX,
|
||||||
|
endRelativePixelPositionX,
|
||||||
|
);
|
||||||
|
const top = chart.chartArea.top - 5;
|
||||||
|
const bottom = chart.chartArea.bottom + 5;
|
||||||
|
|
||||||
|
/* eslint-disable-next-line no-param-reassign */
|
||||||
|
chart.ctx.fillStyle = pluginOptions.color;
|
||||||
|
chart.ctx.fillRect(left, top, right - left, bottom - top);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return dragSelectPlugin;
|
||||||
|
};
|
164
frontend/src/components/Graph/Plugin/IntersectionCursor.ts
Normal file
164
frontend/src/components/Graph/Plugin/IntersectionCursor.ts
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import { Chart, ChartEvent, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||||
|
import * as ChartHelpers from 'chart.js/helpers';
|
||||||
|
|
||||||
|
// utils
|
||||||
|
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||||
|
|
||||||
|
export const intersectionCursorPluginId = 'intersection-cursor-plugin';
|
||||||
|
|
||||||
|
export type IntersectionCursorPluginOptions = {
|
||||||
|
color?: string;
|
||||||
|
dashSize?: number;
|
||||||
|
gapSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultIntersectionCursorPluginOptions: Required<IntersectionCursorPluginOptions> = {
|
||||||
|
color: 'white',
|
||||||
|
dashSize: 3,
|
||||||
|
gapSize: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createIntersectionCursorPluginOptions(
|
||||||
|
isEnabled: boolean,
|
||||||
|
color?: string,
|
||||||
|
dashSize?: number,
|
||||||
|
gapSize?: number,
|
||||||
|
): IntersectionCursorPluginOptions | false {
|
||||||
|
if (!isEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
dashSize,
|
||||||
|
gapSize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMousemoveHandler(
|
||||||
|
chart: Chart,
|
||||||
|
cursorData: IntersectionCursorData,
|
||||||
|
): ChartEventHandler {
|
||||||
|
return (ev: ChartEvent | MouseEvent): void => {
|
||||||
|
const { left, right, top, bottom } = chart.chartArea;
|
||||||
|
|
||||||
|
let { x, y } = ChartHelpers.getRelativePosition(ev, chart);
|
||||||
|
|
||||||
|
if (left > x) {
|
||||||
|
x = left;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right < x) {
|
||||||
|
x = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < top) {
|
||||||
|
y = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y > bottom) {
|
||||||
|
y = bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorData.onMouseMove(x, y);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMouseoutHandler(
|
||||||
|
cursorData: IntersectionCursorData,
|
||||||
|
): ChartEventHandler {
|
||||||
|
return (): void => {
|
||||||
|
cursorData.onMouseOut();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntersectionCursorData {
|
||||||
|
public positionX: number | null | undefined;
|
||||||
|
|
||||||
|
public positionY: number | null | undefined;
|
||||||
|
|
||||||
|
public initialize(): void {
|
||||||
|
this.positionX = null;
|
||||||
|
this.positionY = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMouseMove(x: number | undefined, y: number | undefined): void {
|
||||||
|
this.positionX = x;
|
||||||
|
this.positionY = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onMouseOut(): void {
|
||||||
|
this.positionX = null;
|
||||||
|
this.positionY = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createIntersectionCursorPlugin = (): Plugin<
|
||||||
|
keyof ChartTypeRegistry,
|
||||||
|
IntersectionCursorPluginOptions
|
||||||
|
> => {
|
||||||
|
const cursorData = new IntersectionCursorData();
|
||||||
|
let pluginOptions: Required<IntersectionCursorPluginOptions>;
|
||||||
|
|
||||||
|
let mousemoveHandler: (ev: ChartEvent | MouseEvent) => void;
|
||||||
|
let mouseoutHandler: (ev: ChartEvent | MouseEvent) => void;
|
||||||
|
|
||||||
|
const intersectionCursorPlugin: Plugin<
|
||||||
|
keyof ChartTypeRegistry,
|
||||||
|
IntersectionCursorPluginOptions
|
||||||
|
> = {
|
||||||
|
id: intersectionCursorPluginId,
|
||||||
|
start: (chart: Chart, _, passedOptions) => {
|
||||||
|
const { canvas } = chart;
|
||||||
|
|
||||||
|
cursorData.initialize();
|
||||||
|
pluginOptions = mergeDefaultOptions(
|
||||||
|
passedOptions,
|
||||||
|
defaultIntersectionCursorPluginOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
mousemoveHandler = createMousemoveHandler(chart, cursorData);
|
||||||
|
mouseoutHandler = createMouseoutHandler(cursorData);
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', mousemoveHandler, { passive: true });
|
||||||
|
canvas.addEventListener('mouseout', mouseoutHandler, { passive: true });
|
||||||
|
},
|
||||||
|
beforeDestroy: (chart: Chart) => {
|
||||||
|
const { canvas } = chart;
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.removeEventListener('mousemove', mousemoveHandler);
|
||||||
|
canvas.removeEventListener('mouseout', mouseoutHandler);
|
||||||
|
},
|
||||||
|
afterDatasetsDraw: (chart: Chart) => {
|
||||||
|
const { positionX, positionY } = cursorData;
|
||||||
|
|
||||||
|
const lineDashData = [pluginOptions.dashSize, pluginOptions.gapSize];
|
||||||
|
|
||||||
|
if (typeof positionX === 'number' && typeof positionY === 'number') {
|
||||||
|
const { top, bottom, left, right } = chart.chartArea;
|
||||||
|
|
||||||
|
chart.ctx.beginPath();
|
||||||
|
/* eslint-disable-next-line no-param-reassign */
|
||||||
|
chart.ctx.strokeStyle = pluginOptions.color;
|
||||||
|
chart.ctx.setLineDash(lineDashData);
|
||||||
|
chart.ctx.moveTo(left, positionY);
|
||||||
|
chart.ctx.lineTo(right, positionY);
|
||||||
|
chart.ctx.stroke();
|
||||||
|
|
||||||
|
chart.ctx.beginPath();
|
||||||
|
chart.ctx.setLineDash(lineDashData);
|
||||||
|
/* eslint-disable-next-line no-param-reassign */
|
||||||
|
chart.ctx.strokeStyle = pluginOptions.color;
|
||||||
|
chart.ctx.moveTo(positionX, top);
|
||||||
|
chart.ctx.lineTo(positionX, bottom);
|
||||||
|
chart.ctx.stroke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return intersectionCursorPlugin;
|
||||||
|
};
|
20
frontend/src/components/Graph/Plugin/utils.ts
Normal file
20
frontend/src/components/Graph/Plugin/utils.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ChartEvent } from 'chart.js';
|
||||||
|
|
||||||
|
export type ChartEventHandler = (ev: ChartEvent | MouseEvent) => void;
|
||||||
|
|
||||||
|
export function mergeDefaultOptions<T extends Record<string, unknown>>(
|
||||||
|
options: T,
|
||||||
|
defaultOptions: Required<T>,
|
||||||
|
): Required<T> {
|
||||||
|
const sanitizedOptions = { ...options };
|
||||||
|
Object.keys(options).forEach((key) => {
|
||||||
|
if (sanitizedOptions[key as keyof T] === undefined) {
|
||||||
|
delete sanitizedOptions[key as keyof T];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...defaultOptions,
|
||||||
|
...sanitizedOptions,
|
||||||
|
};
|
||||||
|
}
|
@ -28,7 +28,19 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
|||||||
|
|
||||||
import { hasData } from './hasData';
|
import { hasData } from './hasData';
|
||||||
import { legend } from './Plugin';
|
import { legend } from './Plugin';
|
||||||
|
import {
|
||||||
|
createDragSelectPlugin,
|
||||||
|
createDragSelectPluginOptions,
|
||||||
|
dragSelectPluginId,
|
||||||
|
DragSelectPluginOptions,
|
||||||
|
} from './Plugin/DragSelect';
|
||||||
import { emptyGraph } from './Plugin/EmptyGraph';
|
import { emptyGraph } from './Plugin/EmptyGraph';
|
||||||
|
import {
|
||||||
|
createIntersectionCursorPlugin,
|
||||||
|
createIntersectionCursorPluginOptions,
|
||||||
|
intersectionCursorPluginId,
|
||||||
|
IntersectionCursorPluginOptions,
|
||||||
|
} from './Plugin/IntersectionCursor';
|
||||||
import { LegendsContainer } from './styles';
|
import { LegendsContainer } from './styles';
|
||||||
import { useXAxisTimeUnit } from './xAxisConfig';
|
import { useXAxisTimeUnit } from './xAxisConfig';
|
||||||
import { getToolTipValue, getYAxisFormattedValue } from './yAxisConfig';
|
import { getToolTipValue, getYAxisFormattedValue } from './yAxisConfig';
|
||||||
@ -64,6 +76,8 @@ function Graph({
|
|||||||
forceReRender,
|
forceReRender,
|
||||||
staticLine,
|
staticLine,
|
||||||
containerHeight,
|
containerHeight,
|
||||||
|
onDragSelect,
|
||||||
|
dragSelectColor,
|
||||||
}: GraphProps): JSX.Element {
|
}: GraphProps): JSX.Element {
|
||||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
@ -91,7 +105,7 @@ function Graph({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chartRef.current !== null) {
|
if (chartRef.current !== null) {
|
||||||
const options: ChartOptions = {
|
const options: CustomChartOptions = {
|
||||||
animation: {
|
animation: {
|
||||||
duration: animate ? 200 : 0,
|
duration: animate ? 200 : 0,
|
||||||
},
|
},
|
||||||
@ -148,6 +162,15 @@ function Graph({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[dragSelectPluginId]: createDragSelectPluginOptions(
|
||||||
|
!!onDragSelect,
|
||||||
|
onDragSelect,
|
||||||
|
dragSelectColor,
|
||||||
|
),
|
||||||
|
[intersectionCursorPluginId]: createIntersectionCursorPluginOptions(
|
||||||
|
!!onDragSelect,
|
||||||
|
currentTheme === 'dark' ? 'white' : 'black',
|
||||||
|
),
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
@ -211,7 +234,13 @@ function Graph({
|
|||||||
const chartHasData = hasData(data);
|
const chartHasData = hasData(data);
|
||||||
const chartPlugins = [];
|
const chartPlugins = [];
|
||||||
|
|
||||||
if (!chartHasData) chartPlugins.push(emptyGraph);
|
if (chartHasData) {
|
||||||
|
chartPlugins.push(createIntersectionCursorPlugin());
|
||||||
|
chartPlugins.push(createDragSelectPlugin());
|
||||||
|
} else {
|
||||||
|
chartPlugins.push(emptyGraph);
|
||||||
|
}
|
||||||
|
|
||||||
chartPlugins.push(legend(name, data.datasets.length > 3));
|
chartPlugins.push(legend(name, data.datasets.length > 3));
|
||||||
|
|
||||||
lineChartRef.current = new Chart(chartRef.current, {
|
lineChartRef.current = new Chart(chartRef.current, {
|
||||||
@ -234,6 +263,9 @@ function Graph({
|
|||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
staticLine,
|
staticLine,
|
||||||
|
onDragSelect,
|
||||||
|
dragSelectColor,
|
||||||
|
currentTheme,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -248,6 +280,13 @@ function Graph({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomChartOptions = ChartOptions & {
|
||||||
|
plugins: {
|
||||||
|
[dragSelectPluginId]: DragSelectPluginOptions | false;
|
||||||
|
[intersectionCursorPluginId]: IntersectionCursorPluginOptions | false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
interface GraphProps {
|
interface GraphProps {
|
||||||
animate?: boolean;
|
animate?: boolean;
|
||||||
type: ChartType;
|
type: ChartType;
|
||||||
@ -260,6 +299,8 @@ interface GraphProps {
|
|||||||
forceReRender?: boolean | null | number;
|
forceReRender?: boolean | null | number;
|
||||||
staticLine?: StaticLineProps | undefined;
|
staticLine?: StaticLineProps | undefined;
|
||||||
containerHeight?: string | number;
|
containerHeight?: string | number;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
|
dragSelectColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StaticLineProps {
|
export interface StaticLineProps {
|
||||||
@ -287,5 +328,7 @@ Graph.defaultProps = {
|
|||||||
forceReRender: undefined,
|
forceReRender: undefined,
|
||||||
staticLine: undefined,
|
staticLine: undefined,
|
||||||
containerHeight: '85%',
|
containerHeight: '85%',
|
||||||
|
onDragSelect: undefined,
|
||||||
|
dragSelectColor: undefined,
|
||||||
};
|
};
|
||||||
export default Graph;
|
export default Graph;
|
||||||
|
@ -19,6 +19,7 @@ function GridGraphComponent({
|
|||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
staticLine,
|
staticLine,
|
||||||
|
onDragSelect,
|
||||||
}: GridGraphComponentProps): JSX.Element | null {
|
}: GridGraphComponentProps): JSX.Element | null {
|
||||||
const location = history.location.pathname;
|
const location = history.location.pathname;
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ function GridGraphComponent({
|
|||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
staticLine,
|
staticLine,
|
||||||
|
onDragSelect,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -85,6 +87,7 @@ export interface GridGraphComponentProps {
|
|||||||
name: string;
|
name: string;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
staticLine?: StaticLineProps;
|
staticLine?: StaticLineProps;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
GridGraphComponent.defaultProps = {
|
GridGraphComponent.defaultProps = {
|
||||||
@ -94,6 +97,7 @@ GridGraphComponent.defaultProps = {
|
|||||||
onClickHandler: undefined,
|
onClickHandler: undefined,
|
||||||
yAxisUnit: undefined,
|
yAxisUnit: undefined,
|
||||||
staticLine: undefined,
|
staticLine: undefined,
|
||||||
|
onDragSelect: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GridGraphComponent;
|
export default GridGraphComponent;
|
||||||
|
@ -27,6 +27,7 @@ function FullView({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
onDragSelect,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { selectedTime: globalSelectedTime } = useSelector<
|
const { selectedTime: globalSelectedTime } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
@ -102,6 +103,7 @@ function FullView({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
onDragSelect,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -114,12 +116,14 @@ interface FullViewProps {
|
|||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
name: string;
|
name: string;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullView.defaultProps = {
|
FullView.defaultProps = {
|
||||||
fullViewOptions: undefined,
|
fullViewOptions: undefined,
|
||||||
onClickHandler: undefined,
|
onClickHandler: undefined,
|
||||||
yAxisUnit: undefined,
|
yAxisUnit: undefined,
|
||||||
|
onDragSelect: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FullView;
|
export default FullView;
|
||||||
|
@ -30,6 +30,7 @@ function FullView({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
onDragSelect,
|
||||||
}: FullViewProps): JSX.Element {
|
}: FullViewProps): JSX.Element {
|
||||||
const { minTime, maxTime, selectedTime: globalSelectedTime } = useSelector<
|
const { minTime, maxTime, selectedTime: globalSelectedTime } = useSelector<
|
||||||
AppState,
|
AppState,
|
||||||
@ -166,6 +167,7 @@ function FullView({
|
|||||||
onClickHandler,
|
onClickHandler,
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
onDragSelect,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -178,12 +180,14 @@ interface FullViewProps {
|
|||||||
onClickHandler?: GraphOnClickHandler;
|
onClickHandler?: GraphOnClickHandler;
|
||||||
name: string;
|
name: string;
|
||||||
yAxisUnit?: string;
|
yAxisUnit?: string;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullView.defaultProps = {
|
FullView.defaultProps = {
|
||||||
fullViewOptions: undefined,
|
fullViewOptions: undefined,
|
||||||
onClickHandler: undefined,
|
onClickHandler: undefined,
|
||||||
yAxisUnit: undefined,
|
yAxisUnit: undefined,
|
||||||
|
onDragSelect: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FullView;
|
export default FullView;
|
||||||
|
@ -35,6 +35,7 @@ function GridCardGraph({
|
|||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
layout = [],
|
layout = [],
|
||||||
setLayout,
|
setLayout,
|
||||||
|
onDragSelect,
|
||||||
}: GridCardGraphProps): JSX.Element {
|
}: GridCardGraphProps): JSX.Element {
|
||||||
const [state, setState] = useState<GridCardGraphState>({
|
const [state, setState] = useState<GridCardGraphState>({
|
||||||
loading: true,
|
loading: true,
|
||||||
@ -299,6 +300,7 @@ function GridCardGraph({
|
|||||||
title: ' ', // empty title to accommodate absolutely positioned widget header
|
title: ' ', // empty title to accommodate absolutely positioned widget header
|
||||||
name,
|
name,
|
||||||
yAxisUnit,
|
yAxisUnit,
|
||||||
|
onDragSelect,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -329,8 +331,13 @@ interface GridCardGraphProps extends DispatchProps {
|
|||||||
layout?: Layout[];
|
layout?: Layout[];
|
||||||
// eslint-disable-next-line react/require-default-props
|
// eslint-disable-next-line react/require-default-props
|
||||||
setLayout?: React.Dispatch<React.SetStateAction<LayoutProps[]>>;
|
setLayout?: React.Dispatch<React.SetStateAction<LayoutProps[]>>;
|
||||||
|
onDragSelect?: (start: number, end: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GridCardGraph.defaultProps = {
|
||||||
|
onDragSelect: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (
|
const mapDispatchToProps = (
|
||||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||||
): DispatchProps => ({
|
): DispatchProps => ({
|
||||||
|
@ -8,6 +8,8 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
import { ThunkDispatch } from 'redux-thunk';
|
import { ThunkDispatch } from 'redux-thunk';
|
||||||
|
import { AppDispatch } from 'store';
|
||||||
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import {
|
import {
|
||||||
ToggleAddWidget,
|
ToggleAddWidget,
|
||||||
ToggleAddWidgetProps,
|
ToggleAddWidgetProps,
|
||||||
@ -63,12 +65,22 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
const [selectedDashboard] = dashboards;
|
const [selectedDashboard] = dashboards;
|
||||||
const { data } = selectedDashboard;
|
const { data } = selectedDashboard;
|
||||||
const { widgets } = data;
|
const { widgets } = data;
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch: AppDispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const [layouts, setLayout] = useState<LayoutProps[]>(
|
const [layouts, setLayout] = useState<LayoutProps[]>(
|
||||||
getPreLayouts(widgets, selectedDashboard.data.layout || []),
|
getPreLayouts(widgets, selectedDashboard.data.layout || []),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
if (!isAddWidget) {
|
if (!isAddWidget) {
|
||||||
@ -182,13 +194,14 @@ function GridGraph(props: Props): JSX.Element {
|
|||||||
yAxisUnit={currentWidget?.yAxisUnit}
|
yAxisUnit={currentWidget?.yAxisUnit}
|
||||||
layout={layout}
|
layout={layout}
|
||||||
setLayout={setLayout}
|
setLayout={setLayout}
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[widgets],
|
[widgets, onDragSelect],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEmptyWidgetHandler = useCallback(async () => {
|
const onEmptyWidgetHandler = useCallback(async () => {
|
||||||
|
@ -8,9 +8,10 @@ import { colors } from 'lib/getRandomColor';
|
|||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes';
|
||||||
import { escapeRegExp } from 'lodash-es';
|
import { escapeRegExp } from 'lodash-es';
|
||||||
import React, { useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { UpdateTimeInterval } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { PromQLWidgets } from 'types/api/dashboard/getAll';
|
import { PromQLWidgets } from 'types/api/dashboard/getAll';
|
||||||
import MetricReducer from 'types/reducer/metrics';
|
import MetricReducer from 'types/reducer/metrics';
|
||||||
@ -22,6 +23,7 @@ import { Button } from './styles';
|
|||||||
function Application({ getWidget }: DashboardProps): JSX.Element {
|
function Application({ getWidget }: DashboardProps): JSX.Element {
|
||||||
const { servicename } = useParams<{ servicename?: string }>();
|
const { servicename } = useParams<{ servicename?: string }>();
|
||||||
const selectedTimeStamp = useRef(0);
|
const selectedTimeStamp = useRef(0);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
topOperations,
|
topOperations,
|
||||||
@ -92,6 +94,16 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragSelect = useCallback(
|
||||||
|
(start: number, end: number) => {
|
||||||
|
const startTimestamp = Math.trunc(start);
|
||||||
|
const endTimestamp = Math.trunc(end);
|
||||||
|
|
||||||
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
const onErrorTrackHandler = (timestamp: number): void => {
|
const onErrorTrackHandler = (timestamp: number): void => {
|
||||||
const currentTime = timestamp;
|
const currentTime = timestamp;
|
||||||
const tPlusOne = timestamp + 1 * 60 * 1000;
|
const tPlusOne = timestamp + 1 * 60 * 1000;
|
||||||
@ -173,6 +185,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
|||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
yAxisUnit="ms"
|
yAxisUnit="ms"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
@ -205,6 +218,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
yAxisUnit="ops"
|
yAxisUnit="ops"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
@ -239,6 +253,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
|
|||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
yAxisUnit="%"
|
yAxisUnit="%"
|
||||||
|
onDragSelect={onDragSelect}
|
||||||
/>
|
/>
|
||||||
</GraphContainer>
|
</GraphContainer>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -60,9 +60,6 @@ function DateTimeSelection({
|
|||||||
searchStartTime,
|
searchStartTime,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const [startTime, setStartTime] = useState<Dayjs>();
|
|
||||||
const [endTime, setEndTime] = useState<Dayjs>();
|
|
||||||
|
|
||||||
const [options, setOptions] = useState(getOptions(location.pathname));
|
const [options, setOptions] = useState(getOptions(location.pathname));
|
||||||
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
const [refreshButtonHidden, setRefreshButtonHidden] = useState<boolean>(false);
|
||||||
const [customDateTimeVisible, setCustomDTPickerVisible] = useState<boolean>(
|
const [customDateTimeVisible, setCustomDTPickerVisible] = useState<boolean>(
|
||||||
@ -108,10 +105,6 @@ function DateTimeSelection({
|
|||||||
return defaultSelectedOption;
|
return defaultSelectedOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [selectedTimeInterval, setSelectedTimeInterval] = useState<Time>(
|
|
||||||
getDefaultTime(location.pathname),
|
|
||||||
);
|
|
||||||
|
|
||||||
const updateLocalStorageForRoutes = (value: Time): void => {
|
const updateLocalStorageForRoutes = (value: Time): void => {
|
||||||
const preRoutes = getLocalStorageKey(LOCALSTORAGE.METRICS_TIME_IN_DURATION);
|
const preRoutes = getLocalStorageKey(LOCALSTORAGE.METRICS_TIME_IN_DURATION);
|
||||||
if (preRoutes !== null) {
|
if (preRoutes !== null) {
|
||||||
@ -133,7 +126,7 @@ function DateTimeSelection({
|
|||||||
const currentTime = dayjs();
|
const currentTime = dayjs();
|
||||||
|
|
||||||
const lastRefresh = dayjs(
|
const lastRefresh = dayjs(
|
||||||
selectedTimeInterval === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
selectedTime === 'custom' ? minTime / 1000000 : maxTime / 1000000,
|
||||||
);
|
);
|
||||||
|
|
||||||
const secondsDiff = currentTime.diff(lastRefresh, 'seconds');
|
const secondsDiff = currentTime.diff(lastRefresh, 'seconds');
|
||||||
@ -160,13 +153,11 @@ function DateTimeSelection({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return `Last refresh - ${secondsDiff} sec ago`;
|
return `Last refresh - ${secondsDiff} sec ago`;
|
||||||
}, [maxTime, minTime, selectedTimeInterval]);
|
}, [maxTime, minTime, selectedTime]);
|
||||||
|
|
||||||
const onSelectHandler = (value: Time): void => {
|
const onSelectHandler = (value: Time): void => {
|
||||||
if (value !== 'custom') {
|
if (value !== 'custom') {
|
||||||
updateTimeInterval(value);
|
updateTimeInterval(value);
|
||||||
const selectedLabel = getInputLabel(undefined, undefined, value);
|
|
||||||
setSelectedTimeInterval(selectedLabel as Time);
|
|
||||||
updateLocalStorageForRoutes(value);
|
updateLocalStorageForRoutes(value);
|
||||||
if (refreshButtonHidden) {
|
if (refreshButtonHidden) {
|
||||||
setRefreshButtonHidden(false);
|
setRefreshButtonHidden(false);
|
||||||
@ -178,7 +169,7 @@ function DateTimeSelection({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onRefreshHandler = (): void => {
|
const onRefreshHandler = (): void => {
|
||||||
onSelectHandler(selectedTimeInterval);
|
onSelectHandler(selectedTime);
|
||||||
onLastRefreshHandler();
|
onLastRefreshHandler();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -186,9 +177,6 @@ function DateTimeSelection({
|
|||||||
if (dateTimeRange !== null) {
|
if (dateTimeRange !== null) {
|
||||||
const [startTimeMoment, endTimeMoment] = dateTimeRange;
|
const [startTimeMoment, endTimeMoment] = dateTimeRange;
|
||||||
if (startTimeMoment && endTimeMoment) {
|
if (startTimeMoment && endTimeMoment) {
|
||||||
setSelectedTimeInterval('custom');
|
|
||||||
setStartTime(startTimeMoment);
|
|
||||||
setEndTime(endTimeMoment);
|
|
||||||
setCustomDTPickerVisible(false);
|
setCustomDTPickerVisible(false);
|
||||||
updateTimeInterval('custom', [
|
updateTimeInterval('custom', [
|
||||||
startTimeMoment?.toDate().getTime() || 0,
|
startTimeMoment?.toDate().getTime() || 0,
|
||||||
@ -239,9 +227,6 @@ function DateTimeSelection({
|
|||||||
|
|
||||||
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
const [preStartTime = 0, preEndTime = 0] = getTime() || [];
|
||||||
|
|
||||||
setStartTime(dayjs(preStartTime));
|
|
||||||
setEndTime(dayjs(preEndTime));
|
|
||||||
|
|
||||||
setRefreshButtonHidden(updatedTime === 'custom');
|
setRefreshButtonHidden(updatedTime === 'custom');
|
||||||
|
|
||||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||||
@ -266,7 +251,11 @@ function DateTimeSelection({
|
|||||||
<FormContainer>
|
<FormContainer>
|
||||||
<DefaultSelect
|
<DefaultSelect
|
||||||
onSelect={(value: unknown): void => onSelectHandler(value as Time)}
|
onSelect={(value: unknown): void => onSelectHandler(value as Time)}
|
||||||
value={getInputLabel(startTime, endTime, selectedTime)}
|
value={getInputLabel(
|
||||||
|
dayjs(minTime / 1000000),
|
||||||
|
dayjs(maxTime / 1000000),
|
||||||
|
selectedTime,
|
||||||
|
)}
|
||||||
data-testid="dropDown"
|
data-testid="dropDown"
|
||||||
>
|
>
|
||||||
{options.map(({ value, label }) => (
|
{options.map(({ value, label }) => (
|
||||||
|
@ -18,6 +18,8 @@ const store = createStore(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
if (window !== undefined) {
|
if (window !== undefined) {
|
||||||
window.store = store;
|
window.store = store;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user