mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 04:15:57 +08:00
fix: Chart loaders on reload and change of time interval at dashboard (#2068)
* fix: Chart data logic on dashboard reloads * fix: linting issues * fix: added right side loader & css config * fix: loader condition change * fix: linting issues * fix: error state of API * fix: Resolved suggested changes * fix: Error state for API Failed * fix: Default loading state * fix: linting issues * fix: Suggested changes * feat: Added common hook for previous value * chore: usePrevious is made type safety * chore: chart data set is updated * chore: removed eslint rule * fix: commitlint issue on commit Co-authored-by: Vishal Sharma <makeavish786@gmail.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com> Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
parent
fd6f9a90e1
commit
213838a021
@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && npm run commitlint
|
||||
cd frontend && yarn run commitlint --edit $1
|
||||
|
@ -4,9 +4,9 @@ import React from 'react';
|
||||
|
||||
import { SpinerStyle } from './styles';
|
||||
|
||||
function Spinner({ size, tip, height }: SpinnerProps): JSX.Element {
|
||||
function Spinner({ size, tip, height, style }: SpinnerProps): JSX.Element {
|
||||
return (
|
||||
<SpinerStyle height={height}>
|
||||
<SpinerStyle height={height} style={style}>
|
||||
<Spin spinning size={size} tip={tip} indicator={<LoadingOutlined spin />} />
|
||||
</SpinerStyle>
|
||||
);
|
||||
@ -16,11 +16,13 @@ interface SpinnerProps {
|
||||
size?: SpinProps['size'];
|
||||
tip?: SpinProps['tip'];
|
||||
height?: React.CSSProperties['height'];
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
Spinner.defaultProps = {
|
||||
size: undefined,
|
||||
tip: undefined,
|
||||
height: undefined,
|
||||
style: {},
|
||||
};
|
||||
|
||||
export default Spinner;
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
||||
@ -58,6 +58,18 @@ function FullView({
|
||||
}),
|
||||
);
|
||||
|
||||
const chartDataSet = useMemo(
|
||||
() =>
|
||||
getChartData({
|
||||
queryData: [
|
||||
{
|
||||
queryData: response?.data?.payload?.data?.result || [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[response],
|
||||
);
|
||||
|
||||
const isLoading = response.isLoading === true;
|
||||
|
||||
if (isLoading) {
|
||||
@ -86,25 +98,15 @@ function FullView({
|
||||
)}
|
||||
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: getChartData({
|
||||
queryData: [
|
||||
{
|
||||
queryData: response.data?.payload?.data?.result
|
||||
? response.data?.payload?.data?.result
|
||||
: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
onClickHandler,
|
||||
name,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
}}
|
||||
GRAPH_TYPES={widget.panelTypes}
|
||||
data={chartDataSet}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={widget.title}
|
||||
onClickHandler={onClickHandler}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -15,7 +15,7 @@ import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
@ -115,6 +115,18 @@ function FullView({
|
||||
})),
|
||||
);
|
||||
|
||||
const chartDataSet = useMemo(
|
||||
() =>
|
||||
getChartData({
|
||||
queryData: data.map((e) => ({
|
||||
query: e?.map((e) => e.query).join(' ') || '',
|
||||
queryData: e?.map((e) => e.queryData) || [],
|
||||
legend: e?.map((e) => e.legend).join('') || '',
|
||||
})),
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||
}
|
||||
@ -149,23 +161,15 @@ function FullView({
|
||||
)}
|
||||
|
||||
<GridGraphComponent
|
||||
{...{
|
||||
GRAPH_TYPES: widget.panelTypes,
|
||||
data: getChartData({
|
||||
queryData: data.map((e) => ({
|
||||
query: e?.map((e) => e.query).join(' ') || '',
|
||||
queryData: e?.map((e) => e.queryData) || [],
|
||||
legend: e?.map((e) => e.legend).join('') || '',
|
||||
})),
|
||||
}),
|
||||
isStacked: widget.isStacked,
|
||||
opacity: widget.opacity,
|
||||
title: widget.title,
|
||||
onClickHandler,
|
||||
name,
|
||||
yAxisUnit,
|
||||
onDragSelect,
|
||||
}}
|
||||
GRAPH_TYPES={widget.panelTypes}
|
||||
data={chartDataSet}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={widget.title}
|
||||
onClickHandler={onClickHandler}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
onDragSelect={onDragSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Typography } from 'antd';
|
||||
import { ChartData } from 'chart.js';
|
||||
import Spinner from 'components/Spinner';
|
||||
import GridGraphComponent from 'container/GridGraphComponent';
|
||||
import usePreviousValue from 'hooks/usePreviousValue';
|
||||
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import isEmpty from 'lodash-es/isEmpty';
|
||||
@ -18,9 +20,7 @@ import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { GlobalTime } from 'types/actions/globalTime';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import DashboardReducer from 'types/reducer/dashboards';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
@ -28,7 +28,7 @@ import { LayoutProps } from '..';
|
||||
import EmptyWidget from '../EmptyWidget';
|
||||
import WidgetHeader from '../WidgetHeader';
|
||||
import FullView from './FullView/index.metricsBuilder';
|
||||
import { ErrorContainer, FullViewContainer, Modal } from './styles';
|
||||
import { FullViewContainer, Modal } from './styles';
|
||||
|
||||
function GridCardGraph({
|
||||
widget,
|
||||
@ -39,6 +39,7 @@ function GridCardGraph({
|
||||
setLayout,
|
||||
onDragSelect,
|
||||
}: GridCardGraphProps): JSX.Element {
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>('');
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const [modal, setModal] = useState(false);
|
||||
const [deleteModal, setDeleteModal] = useState(false);
|
||||
@ -57,9 +58,7 @@ function GridCardGraph({
|
||||
const selectedData = selectedDashboard?.data;
|
||||
const { variables } = selectedData;
|
||||
|
||||
const response = useQuery<
|
||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||
>(
|
||||
const queryResponse = useQuery(
|
||||
[
|
||||
`GetMetricsQueryRange-${widget.timePreferance}-${globalSelectedInterval}-${widget.id}`,
|
||||
{
|
||||
@ -81,6 +80,11 @@ function GridCardGraph({
|
||||
{
|
||||
keepPreviousData: true,
|
||||
refetchOnMount: false,
|
||||
onError: (error) => {
|
||||
if (error instanceof Error) {
|
||||
setErrorMessage(error.message);
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@ -89,15 +93,15 @@ function GridCardGraph({
|
||||
getChartData({
|
||||
queryData: [
|
||||
{
|
||||
queryData: response?.data?.payload?.data?.result
|
||||
? response?.data?.payload?.data?.result
|
||||
: [],
|
||||
queryData: queryResponse?.data?.payload?.data?.result || [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[response?.data?.payload],
|
||||
[queryResponse],
|
||||
);
|
||||
|
||||
const prevChartDataSetRef = usePreviousValue<ChartData>(chartData);
|
||||
|
||||
const onToggleModal = useCallback(
|
||||
(func: React.Dispatch<React.SetStateAction<boolean>>) => {
|
||||
func((value) => !value);
|
||||
@ -154,10 +158,12 @@ function GridCardGraph({
|
||||
|
||||
const isEmptyLayout = widget?.id === 'empty' || isEmpty(widget);
|
||||
|
||||
if (response.isError && !isEmptyLayout) {
|
||||
if (queryResponse.isError && !isEmptyLayout) {
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
{getModals()}
|
||||
{!isEmpty(widget) && prevChartDataSetRef && (
|
||||
<>
|
||||
<div className="drag-handle">
|
||||
<WidgetHeader
|
||||
parentHover={hovered}
|
||||
@ -165,28 +171,55 @@ function GridCardGraph({
|
||||
widget={widget}
|
||||
onView={handleOnView}
|
||||
onDelete={handleOnDelete}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ErrorContainer>
|
||||
{response.isError && 'Something went wrong'}
|
||||
</ErrorContainer>
|
||||
<GridGraphComponent
|
||||
GRAPH_TYPES={widget.panelTypes}
|
||||
data={prevChartDataSetRef}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={' '}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (response.isFetching) {
|
||||
if (prevChartDataSetRef?.labels === undefined && queryResponse.isLoading) {
|
||||
return (
|
||||
<span>
|
||||
{!isEmpty(widget) && prevChartDataSetRef?.labels ? (
|
||||
<>
|
||||
<div className="drag-handle">
|
||||
<WidgetHeader
|
||||
parentHover={hovered}
|
||||
title={widget?.title}
|
||||
widget={widget}
|
||||
onView={handleOnView}
|
||||
onDelete={handleOnDelete}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
</div>
|
||||
<GridGraphComponent
|
||||
GRAPH_TYPES={widget.panelTypes}
|
||||
data={prevChartDataSetRef}
|
||||
isStacked={widget.isStacked}
|
||||
opacity={widget.opacity}
|
||||
title={' '}
|
||||
name={name}
|
||||
yAxisUnit={yAxisUnit}
|
||||
/>
|
||||
<Spinner height="20vh" tip="Loading..." />
|
||||
</>
|
||||
) : (
|
||||
<Spinner height="20vh" tip="Loading..." />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@ -213,13 +246,15 @@ function GridCardGraph({
|
||||
widget={widget}
|
||||
onView={handleOnView}
|
||||
onDelete={handleOnDelete}
|
||||
queryResponse={queryResponse}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isEmptyLayout && getModals()}
|
||||
|
||||
{!isEmpty(widget) && !!response.data?.payload?.data?.result && (
|
||||
{!isEmpty(widget) && !!queryResponse.data?.payload && (
|
||||
<GridGraphComponent
|
||||
GRAPH_TYPES={widget.panelTypes}
|
||||
data={chartData}
|
||||
|
@ -0,0 +1,13 @@
|
||||
import { themeColors } from 'constants/theme';
|
||||
|
||||
const positionCss: React.CSSProperties['position'] = 'fixed';
|
||||
|
||||
export const spinnerStyles = { position: positionCss, right: '0.5rem' };
|
||||
export const tooltipStyles = {
|
||||
fontSize: '1rem',
|
||||
top: '0.313rem',
|
||||
position: positionCss,
|
||||
right: '0.313rem',
|
||||
color: themeColors.errorColor,
|
||||
};
|
||||
export const errorTooltipPosition = 'top';
|
@ -2,17 +2,23 @@ import {
|
||||
DeleteOutlined,
|
||||
DownOutlined,
|
||||
EditFilled,
|
||||
ExclamationCircleOutlined,
|
||||
FullscreenOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Dropdown, Menu, Typography } from 'antd';
|
||||
import { Dropdown, Menu, Tooltip, Typography } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import useComponentPermission from 'hooks/useComponentPermission';
|
||||
import history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { errorTooltipPosition, spinnerStyles, tooltipStyles } from './config';
|
||||
import {
|
||||
ArrowContainer,
|
||||
HeaderContainer,
|
||||
@ -27,6 +33,10 @@ interface IWidgetHeaderProps {
|
||||
onView: VoidFunction;
|
||||
onDelete: VoidFunction;
|
||||
parentHover: boolean;
|
||||
queryResponse: UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
|
||||
>;
|
||||
errorMessage: string | undefined;
|
||||
}
|
||||
function WidgetHeader({
|
||||
title,
|
||||
@ -34,6 +44,8 @@ function WidgetHeader({
|
||||
onView,
|
||||
onDelete,
|
||||
parentHover,
|
||||
queryResponse,
|
||||
errorMessage,
|
||||
}: IWidgetHeaderProps): JSX.Element {
|
||||
const [localHover, setLocalHover] = useState(false);
|
||||
|
||||
@ -106,6 +118,7 @@ function WidgetHeader({
|
||||
overlayStyle={{ minWidth: 100 }}
|
||||
placement="bottom"
|
||||
>
|
||||
<>
|
||||
<HeaderContainer
|
||||
onMouseOver={(): void => setLocalHover(true)}
|
||||
onMouseOut={(): void => setLocalHover(false)}
|
||||
@ -120,6 +133,15 @@ function WidgetHeader({
|
||||
</ArrowContainer>
|
||||
</HeaderContentContainer>
|
||||
</HeaderContainer>
|
||||
{queryResponse.isFetching && !queryResponse.isError && (
|
||||
<Spinner height="5vh" style={spinnerStyles} />
|
||||
)}
|
||||
{queryResponse.isError && (
|
||||
<Tooltip title={errorMessage} placement={errorTooltipPosition}>
|
||||
<ExclamationCircleOutlined style={tooltipStyles} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import RouteTab from 'components/RouteTab';
|
||||
import ROUTES from 'constants/routes';
|
||||
import React from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { generatePath, useParams } from 'react-router-dom';
|
||||
import { useLocation } from 'react-use';
|
||||
|
||||
@ -85,4 +85,4 @@ function ServiceMetrics(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ServiceMetrics);
|
||||
export default memo(ServiceMetrics);
|
||||
|
13
frontend/src/hooks/usePreviousValue.ts
Normal file
13
frontend/src/hooks/usePreviousValue.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
function usePreviousValue<T>(value: T): T {
|
||||
const ref = useRef<T>();
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
|
||||
return ref.current as T;
|
||||
}
|
||||
|
||||
export default usePreviousValue;
|
Loading…
x
Reference in New Issue
Block a user