Uplot time range (#4144)

* feat: show range bound chart based on the selected time range

* feat: handle no data

* feat: show bigger point if only data point exists

* feat: show bigger point if only data point exists

* feat: widget ui fixes

* feat: no data - full view fix

* fix: show closed point on hover

* feat: handle widget time preference in case of dashboard, edit view, full view and chart preview
This commit is contained in:
Yunus M 2023-12-14 22:56:25 +05:30 committed by GitHub
parent 7efe907757
commit 418ab67d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 260 additions and 51 deletions

View File

@ -86,6 +86,7 @@ module.exports = {
}, },
], ],
'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
'no-plusplus': 'off',
'jsx-a11y/label-has-associated-control': [ 'jsx-a11y/label-has-associated-control': [
'error', 'error',
{ {
@ -109,7 +110,6 @@ module.exports = {
// eslint rules need to remove // eslint rules need to remove
'@typescript-eslint/no-shadow': 'off', '@typescript-eslint/no-shadow': 'off',
'import/no-cycle': 'off', 'import/no-cycle': 'off',
'prettier/prettier': [ 'prettier/prettier': [
'error', 'error',
{}, {},

View File

@ -13,3 +13,11 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.uplot-no-data {
position: relative;
display: flex;
width: 100%;
flex-direction: column;
gap: 8px;
}

View File

@ -1,8 +1,9 @@
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
import './uplot.scss'; import './Uplot.styles.scss';
import { Typography } from 'antd'; import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types'; import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { import {
forwardRef, forwardRef,
@ -127,6 +128,16 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
} }
}, [data, resetScales, create]); }, [data, resetScales, create]);
if (data && data[0] && data[0]?.length === 0) {
return (
<div className="uplot-no-data not-found">
<LineChart size={48} strokeWidth={0.5} />
<Typography>No Data</Typography>
</div>
);
}
return ( return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}> <ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<div className="uplot-graph-container" ref={targetRef}> <div className="uplot-graph-container" ref={targetRef}>

View File

@ -10,7 +10,7 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions'; import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -18,6 +18,7 @@ import { AlertDef } from 'types/api/alerts/def';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import { ChartContainer, FailedMessageContainer } from './styles'; import { ChartContainer, FailedMessageContainer } from './styles';
import { getThresholdLabel } from './utils'; import { getThresholdLabel } from './utils';
@ -49,9 +50,13 @@ function ChartPreview({
}: ChartPreviewProps): JSX.Element | null { }: ChartPreviewProps): JSX.Element | null {
const { t } = useTranslation('alerts'); const { t } = useTranslation('alerts');
const threshold = alertDef?.condition.target || 0; const threshold = alertDef?.condition.target || 0;
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>( const [minTimeScale, setMinTimeScale] = useState<number>();
(state) => state.globalTime, const [maxTimeScale, setMaxTimeScale] = useState<number>();
);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const canQuery = useMemo((): boolean => { const canQuery = useMemo((): boolean => {
if (!query || query == null) { if (!query || query == null) {
@ -101,6 +106,13 @@ function ChartPreview({
const graphRef = useRef<HTMLDivElement>(null); const graphRef = useRef<HTMLDivElement>(null);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
const chartData = getUPlotChartData(queryResponse?.data?.payload); const chartData = getUPlotChartData(queryResponse?.data?.payload);
const containerDimensions = useResizeObserver(graphRef); const containerDimensions = useResizeObserver(graphRef);
@ -117,6 +129,8 @@ function ChartPreview({
yAxisUnit, yAxisUnit,
apiResponse: queryResponse?.data?.payload, apiResponse: queryResponse?.data?.payload,
dimensions: containerDimensions, dimensions: containerDimensions,
minTimeScale,
maxTimeScale,
isDarkMode, isDarkMode,
thresholds: [ thresholds: [
{ {
@ -141,6 +155,8 @@ function ChartPreview({
yAxisUnit, yAxisUnit,
queryResponse?.data?.payload, queryResponse?.data?.payload,
containerDimensions, containerDimensions,
minTimeScale,
maxTimeScale,
isDarkMode, isDarkMode,
threshold, threshold,
t, t,

View File

@ -23,6 +23,7 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot'; import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange';
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants'; import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
import GraphManager from './GraphManager'; import GraphManager from './GraphManager';
@ -92,6 +93,21 @@ function FullView({
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(response);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, response]);
useEffect(() => { useEffect(() => {
if (!response.isFetching && fullViewRef.current) { if (!response.isFetching && fullViewRef.current) {
const width = fullViewRef.current?.clientWidth const width = fullViewRef.current?.clientWidth
@ -114,6 +130,8 @@ function FullView({
graphsVisibilityStates, graphsVisibilityStates,
setGraphsVisibilityStates, setGraphsVisibilityStates,
thresholds: widget.thresholds, thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
}); });
setChartOptions(newChartOptions); setChartOptions(newChartOptions);

View File

@ -1,4 +1,5 @@
import { Skeleton, Typography } from 'antd'; import { Skeleton, Typography } from 'antd';
import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types'; import { ToggleGraphProps } from 'components/Graph/types';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
@ -298,7 +299,10 @@ function WidgetGraphComponent({
</div> </div>
{queryResponse.isLoading && <Skeleton />} {queryResponse.isLoading && <Skeleton />}
{queryResponse.isSuccess && ( {queryResponse.isSuccess && (
<div style={{ height: '90%' }} ref={graphRef}> <div
className={cx('widget-graph-container', widget.panelTypes)}
ref={graphRef}
>
<GridPanelSwitch <GridPanelSwitch
panelType={widget.panelTypes} panelType={widget.panelTypes}
data={data} data={data}

View File

@ -15,6 +15,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import EmptyWidget from '../EmptyWidget'; import EmptyWidget from '../EmptyWidget';
import { MenuItemKeys } from '../WidgetHeader/contants'; import { MenuItemKeys } from '../WidgetHeader/contants';
@ -34,6 +35,8 @@ function GridCardGraph({
const dispatch = useDispatch(); const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard(); const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const onDragSelect = useCallback( const onDragSelect = useCallback(
(start: number, end: number): void => { (start: number, end: number): void => {
@ -62,16 +65,16 @@ function GridCardGraph({
} }
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]); }, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const updatedQuery = useStepInterval(widget?.query); const updatedQuery = useStepInterval(widget?.query);
const isEmptyWidget = const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget); widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const queryResponse = useGetQueryRange( const queryResponse = useGetQueryRange(
{ {
selectedTime: widget?.timePreferance, selectedTime: widget?.timePreferance,
@ -103,6 +106,13 @@ function GridCardGraph({
const containerDimensions = useResizeObserver(graphRef); const containerDimensions = useResizeObserver(graphRef);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans); const chartData = getUPlotChartData(queryResponse?.data?.payload, fillSpans);
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
@ -123,6 +133,8 @@ function GridCardGraph({
yAxisUnit: widget?.yAxisUnit, yAxisUnit: widget?.yAxisUnit,
onClickHandler, onClickHandler,
thresholds: widget.thresholds, thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
}), }),
[ [
widget?.id, widget?.id,
@ -133,6 +145,8 @@ function GridCardGraph({
isDarkMode, isDarkMode,
onDragSelect, onDragSelect,
onClickHandler, onClickHandler,
minTimeScale,
maxTimeScale,
], ],
); );

View File

@ -5,3 +5,11 @@
border: none !important; border: none !important;
} }
} }
.widget-graph-container {
height: 100%;
&.graph {
height: calc(100% - 30px);
}
}

View File

@ -2,9 +2,12 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
height: 30px; height: 40px;
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.5rem;
box-sizing: border-box;
font-size: 14px;
font-weight: 600;
} }
.widget-header-title { .widget-header-title {
@ -19,6 +22,10 @@
visibility: hidden; visibility: hidden;
border: none; border: none;
box-shadow: none; box-shadow: none;
cursor: pointer;
font: 14px;
font-weight: 600;
padding: 8px;
} }
.widget-header-hover { .widget-header-hover {

View File

@ -10,7 +10,7 @@ import {
MoreOutlined, MoreOutlined,
WarningOutlined, WarningOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Tooltip, Typography } from 'antd'; import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
@ -199,9 +199,7 @@ function WidgetHeader({
</Tooltip> </Tooltip>
)} )}
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight"> <Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
<Button <MoreOutlined
type="default"
icon={<MoreOutlined />}
className={`widget-header-more-options ${ className={`widget-header-more-options ${
parentHover ? 'widget-header-hover' : '' parentHover ? 'widget-header-hover' : ''
}`} }`}

View File

@ -2,7 +2,7 @@ import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { StyledCSS } from 'container/GantChart/Trace/styles'; import { StyledCSS } from 'container/GantChart/Trace/styles';
import RGL, { WidthProvider } from 'react-grid-layout'; import RGL, { WidthProvider } from 'react-grid-layout';
import styled, { css, FlattenSimpleInterpolation } from 'styled-components'; import styled, { css } from 'styled-components';
const ReactGridLayoutComponent = WidthProvider(RGL); const ReactGridLayoutComponent = WidthProvider(RGL);
@ -17,14 +17,8 @@ export const Card = styled(CardComponent)<CardProps>`
} }
.ant-card-body { .ant-card-body {
height: 90%; height: calc(100% - 40px);
padding: 0; padding: 0;
${({ $panelType }): FlattenSimpleInterpolation =>
$panelType === PANEL_TYPES.TABLE
? css`
padding-top: 1.8rem;
`
: css``}
} }
`; `;

View File

@ -6,13 +6,16 @@ import { useResizeObserver } from 'hooks/useDimensions';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useMemo, useRef } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { UseQueryResult } from 'react-query'; import { UseQueryResult } from 'react-query';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
function WidgetGraph({ function WidgetGraph({
getWidgetQueryRange, getWidgetQueryRange,
@ -23,6 +26,21 @@ function WidgetGraph({
}: WidgetGraphProps): JSX.Element { }: WidgetGraphProps): JSX.Element {
const { stagedQuery } = useQueryBuilder(); const { stagedQuery } = useQueryBuilder();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
useEffect((): void => {
const { startTime, endTime } = getTimeRange(getWidgetQueryRange);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [getWidgetQueryRange, maxTime, minTime, globalSelectedInterval]);
const graphRef = useRef<HTMLDivElement>(null); const graphRef = useRef<HTMLDivElement>(null);
const containerDimensions = useResizeObserver(graphRef); const containerDimensions = useResizeObserver(graphRef);
@ -63,6 +81,8 @@ function WidgetGraph({
onDragSelect, onDragSelect,
thresholds, thresholds,
fillSpans, fillSpans,
minTimeScale,
maxTimeScale,
}), }),
[ [
widgetId, widgetId,
@ -73,6 +93,8 @@ function WidgetGraph({
onDragSelect, onDragSelect,
thresholds, thresholds,
fillSpans, fillSpans,
minTimeScale,
maxTimeScale,
], ],
); );

View File

@ -12,8 +12,7 @@ export const Container = styled(Card)<Props>`
} }
.ant-card-body { .ant-card-body {
padding: ${({ $panelType }): string => padding: 8px;
$panelType === PANEL_TYPES.TABLE ? '0 0' : '1.5rem 0'};
height: 57vh; height: 57vh;
overflow: auto; overflow: auto;
display: flex; display: flex;

View File

@ -3,9 +3,13 @@ import Uplot from 'components/Uplot';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { getTimeRange } from 'utils/getTimeRange';
import { Container, ErrorText } from './styles'; import { Container, ErrorText } from './styles';
@ -31,6 +35,21 @@ function TimeSeriesView({
? graphRef.current.clientHeight ? graphRef.current.clientHeight
: 300; : 300;
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
useEffect((): void => {
const { startTime, endTime } = getTimeRange();
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, data]);
const chartOptions = getUPlotChartOptions({ const chartOptions = getUPlotChartOptions({
yAxisUnit: yAxisUnit || '', yAxisUnit: yAxisUnit || '',
apiResponse: data?.payload, apiResponse: data?.payload,
@ -39,6 +58,8 @@ function TimeSeriesView({
height, height,
}, },
isDarkMode, isDarkMode,
minTimeScale,
maxTimeScale,
}); });
return ( return (

View File

@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck // @ts-nocheck
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
@ -15,6 +16,7 @@ import onClickPlugin, { OnClickPluginOpts } from './plugins/onClickPlugin';
import tooltipPlugin from './plugins/tooltipPlugin'; import tooltipPlugin from './plugins/tooltipPlugin';
import getAxes from './utils/getAxes'; import getAxes from './utils/getAxes';
import getSeries from './utils/getSeriesData'; import getSeries from './utils/getSeriesData';
import { getXAxisScale } from './utils/getXAxisScale';
import { getYAxisScale } from './utils/getYAxisScale'; import { getYAxisScale } from './utils/getYAxisScale';
interface GetUPlotChartOptions { interface GetUPlotChartOptions {
@ -31,6 +33,8 @@ interface GetUPlotChartOptions {
thresholdValue?: number; thresholdValue?: number;
thresholdText?: string; thresholdText?: string;
fillSpans?: boolean; fillSpans?: boolean;
minTimeScale?: number;
maxTimeScale?: number;
} }
export const getUPlotChartOptions = ({ export const getUPlotChartOptions = ({
@ -40,18 +44,20 @@ export const getUPlotChartOptions = ({
apiResponse, apiResponse,
onDragSelect, onDragSelect,
yAxisUnit, yAxisUnit,
minTimeScale,
maxTimeScale,
onClickHandler = _noop, onClickHandler = _noop,
graphsVisibilityStates, graphsVisibilityStates,
setGraphsVisibilityStates, setGraphsVisibilityStates,
thresholds, thresholds,
fillSpans, fillSpans,
}: GetUPlotChartOptions): uPlot.Options => { }: GetUPlotChartOptions): uPlot.Options => {
// eslint-disable-next-line sonarjs/prefer-immediate-return const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
const chartOptions = {
return {
id, id,
width: dimensions.width, width: dimensions.width,
height: dimensions.height - 45, height: dimensions.height - 30,
// tzDate: (ts) => uPlot.tzDate(new Date(ts * 1e3), ''), // Pass timezone for 2nd param
legend: { legend: {
show: true, show: true,
live: false, live: false,
@ -67,18 +73,18 @@ export const getUPlotChartOptions = ({
bias: 1, bias: 1,
}, },
points: { points: {
size: (u, seriesIdx): number => u.series[seriesIdx].points.size * 2.5, size: (u, seriesIdx): number => u.series[seriesIdx].points.size * 3,
width: (u, seriesIdx, size): number => size / 4, width: (u, seriesIdx, size): number => size / 4,
stroke: (u, seriesIdx): string => stroke: (u, seriesIdx): string =>
`${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`, `${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`,
fill: (): string => '#fff', fill: (): string => '#fff',
}, },
}, },
padding: [16, 16, 16, 16], padding: [16, 16, 8, 8],
scales: { scales: {
x: { x: {
time: true, spanGaps: true,
auto: true, // Automatically adjust scale range ...timeScaleProps,
}, },
y: { y: {
...getYAxisScale( ...getYAxisScale(
@ -194,6 +200,4 @@ export const getUPlotChartOptions = ({
), ),
axes: getAxes(isDarkMode, yAxisUnit), axes: getAxes(isDarkMode, yAxisUnit),
}; };
return chartOptions;
}; };

View File

@ -54,7 +54,7 @@ const generateTooltipContent = (
const value = data[index][idx]; const value = data[index][idx];
const label = getLabelName(metric, queryName || '', legend || ''); const label = getLabelName(metric, queryName || '', legend || '');
if (value) { if (Number.isFinite(value)) {
const tooltipValue = getToolTipValue(value, yAxisUnit); const tooltipValue = getToolTipValue(value, yAxisUnit);
const dataObj = { const dataObj = {
@ -191,7 +191,8 @@ const tooltipPlugin = (
if (overlay) { if (overlay) {
overlay.textContent = ''; overlay.textContent = '';
const { left, top, idx } = u.cursor; const { left, top, idx } = u.cursor;
if (idx) {
if (Number.isInteger(idx)) {
const anchor = { left: left + bLeft, top: top + bTop }; const anchor = { left: left + bLeft, top: top + bTop };
const content = generateTooltipContent( const content = generateTooltipContent(
apiResult, apiResult,

View File

@ -9,8 +9,7 @@ const getAxes = (isDarkMode: boolean, yAxisUnit?: string): any => [
stroke: isDarkMode ? 'white' : 'black', // Color of the axis line stroke: isDarkMode ? 'white' : 'black', // Color of the axis line
grid: { grid: {
stroke: getGridColor(isDarkMode), // Color of the grid lines stroke: getGridColor(isDarkMode), // Color of the grid lines
dash: [10, 10], // Dash pattern for grid lines, width: 0.2, // Width of the grid lines,
width: 0.5, // Width of the grid lines,
show: true, show: true,
}, },
ticks: { ticks: {
@ -24,8 +23,7 @@ const getAxes = (isDarkMode: boolean, yAxisUnit?: string): any => [
stroke: isDarkMode ? 'white' : 'black', // Color of the axis line stroke: isDarkMode ? 'white' : 'black', // Color of the axis line
grid: { grid: {
stroke: getGridColor(isDarkMode), // Color of the grid lines stroke: getGridColor(isDarkMode), // Color of the grid lines
dash: [10, 10], // Dash pattern for grid lines, width: 0.2, // Width of the grid lines
width: 0.3, // Width of the grid lines
}, },
ticks: { ticks: {
// stroke: isDarkMode ? 'white' : 'black', // Color of the tick lines // stroke: isDarkMode ? 'white' : 'black', // Color of the tick lines

View File

@ -1,8 +1,8 @@
const getGridColor = (isDarkMode: boolean): string => { const getGridColor = (isDarkMode: boolean): string => {
if (isDarkMode) { if (isDarkMode) {
return 'rgba(231,233,237,0.2)'; return 'rgba(231,233,237,0.3)';
} }
return 'rgba(231,233,237,0.8)'; return 'rgba(0,0,0,0.5)';
}; };
export default getGridColor; export default getGridColor;

View File

@ -46,17 +46,22 @@ const getSeries = (
legend || '', legend || '',
); );
const pointSize = seriesList[i].values.length > 1 ? 5 : 10;
const showPoints = !(seriesList[i].values.length > 1);
const seriesObj: any = { const seriesObj: any = {
width: 1.4,
paths, paths,
drawStyle: drawStyles.line, drawStyle: drawStyles.line,
lineInterpolation: lineInterpolations.spline, lineInterpolation: lineInterpolations.spline,
show: newGraphVisibilityStates ? newGraphVisibilityStates[i] : true, show: newGraphVisibilityStates ? newGraphVisibilityStates[i] : true,
label, label,
stroke: color, stroke: color,
width: 2,
spanGaps: true, spanGaps: true,
points: { points: {
show: false, size: pointSize,
show: showPoints,
stroke: color,
}, },
}; };

View File

@ -0,0 +1,40 @@
function getFallbackMinMaxTimeStamp(): {
fallbackMin: number;
fallbackMax: number;
} {
const currentDate = new Date();
// Get the Unix timestamp (milliseconds since January 1, 1970)
const currentTime = currentDate.getTime();
const currentUnixTimestamp = Math.floor(currentTime / 1000);
// Calculate the date and time one day ago
const oneDayAgoUnixTimestamp = Math.floor(
(currentDate.getTime() - 86400000) / 1000,
); // 86400000 milliseconds in a day
return {
fallbackMin: oneDayAgoUnixTimestamp,
fallbackMax: currentUnixTimestamp,
};
}
export const getXAxisScale = (
minTimeScale: number,
maxTimeScale: number,
): {
time: boolean;
auto: boolean;
range?: [number, number];
} => {
let minTime = minTimeScale;
let maxTime = maxTimeScale;
if (!minTimeScale || !maxTimeScale) {
const { fallbackMin, fallbackMax } = getFallbackMinMaxTimeStamp();
minTime = fallbackMin;
maxTime = fallbackMax;
}
return { time: true, auto: false, range: [minTime, maxTime] };
};

View File

@ -54,7 +54,12 @@ function getRange(
const [minSeriesValue, maxSeriesValue] = findMinMaxValues(series); const [minSeriesValue, maxSeriesValue] = findMinMaxValues(series);
const min = Math.min(minThresholdValue, minSeriesValue); const min = Math.min(minThresholdValue, minSeriesValue);
const max = Math.max(maxThresholdValue, maxSeriesValue); let max = Math.max(maxThresholdValue, maxSeriesValue);
// this is a temp change, we need to figure out a generic way to better handle ranges based on yAxisUnit
if (yAxisUnit === 'percentunit' && max < 1) {
max = 1;
}
return [min, max]; return [min, max];
} }

View File

@ -0,0 +1,36 @@
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { UseQueryResult } from 'react-query';
import store from 'store';
import { SuccessResponse } from 'types/api';
import {
MetricRangePayloadProps,
QueryRangePayload,
} from 'types/api/metrics/getQueryRange';
export const getTimeRange = (
widgetQueryRange?: UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>,
): Record<string, number> => {
const widgetParams =
(widgetQueryRange?.data?.params as QueryRangePayload) || null;
if (widgetParams && widgetParams?.start && widgetParams?.end) {
return {
startTime: widgetParams.start / 1000,
endTime: widgetParams.end / 1000,
};
}
const { globalTime } = store.getState();
const { start: globalStartTime, end: globalEndTime } = getStartEndRangeTime({
type: 'GLOBAL_TIME',
interval: globalTime.selectedTime,
});
return {
startTime: (parseInt(globalStartTime, 10) * 1e3) / 1000,
endTime: (parseInt(globalEndTime, 10) * 1e3) / 1000,
};
};