feat: data time, UI and graph label consistency across FE (#878)

* feat: data time and graph label consistency across FE

* feat: saved state of sidebar and horizontal scroll fix for trace filter page

* feat: add Y-Axis unit for missing metrics graphs

* chore: update node version from 12.18 to 12.22

* fix: 24hr time unit on graph
This commit is contained in:
Pranshu Chittora 2022-03-22 16:22:02 +05:30 committed by GitHub
parent f1f606844a
commit 3ab0e1395a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 110 additions and 34 deletions

View File

@ -1,5 +1,5 @@
# stage1 as builder # stage1 as builder
FROM node:12.18.0 as builder FROM node:12.22.0 as builder
# Add Maintainer Info # Add Maintainer Info
LABEL maintainer="signoz" LABEL maintainer="signoz"

View File

@ -51,6 +51,7 @@ Chart.register(
); );
function Graph({ function Graph({
animate = true,
data, data,
type, type,
title, title,
@ -58,15 +59,14 @@ function Graph({
onClickHandler, onClickHandler,
name, name,
yAxisUnit = 'short', yAxisUnit = 'short',
forceReRender,
}: GraphProps): JSX.Element { }: GraphProps): JSX.Element {
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
const chartRef = useRef<HTMLCanvasElement>(null); const chartRef = useRef<HTMLCanvasElement>(null);
const currentTheme = isDarkMode ? 'dark' : 'light'; const currentTheme = isDarkMode ? 'dark' : 'light';
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
const lineChartRef = useRef<Chart>(); const lineChartRef = useRef<Chart>();
const getGridColor = useCallback(() => { const getGridColor = useCallback(() => {
if (currentTheme === undefined) { if (currentTheme === undefined) {
return 'rgba(231,233,237,0.1)'; return 'rgba(231,233,237,0.1)';
@ -86,6 +86,9 @@ function Graph({
if (chartRef.current !== null) { if (chartRef.current !== null) {
const options: ChartOptions = { const options: ChartOptions = {
animation: {
duration: animate ? 200 : 0,
},
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
interaction: { interaction: {
@ -117,8 +120,8 @@ function Graph({
unit: xAxisTimeUnit?.unitName || 'minute', unit: xAxisTimeUnit?.unitName || 'minute',
stepSize: xAxisTimeUnit?.stepSize || 1, stepSize: xAxisTimeUnit?.stepSize || 1,
displayFormats: { displayFormats: {
millisecond: 'hh:mm:ss', millisecond: 'HH:mm:ss',
second: 'hh:mm:ss', second: 'HH:mm:ss',
minute: 'HH:mm', minute: 'HH:mm',
hour: 'MM/dd HH:mm', hour: 'MM/dd HH:mm',
day: 'MM/dd', day: 'MM/dd',
@ -166,11 +169,23 @@ function Graph({
plugins: [legend(name, data.datasets.length > 3)], plugins: [legend(name, data.datasets.length > 3)],
}); });
} }
}, [chartRef, data, type, title, isStacked, getGridColor, onClickHandler]); }, [
animate,
title,
getGridColor,
xAxisTimeUnit?.unitName,
xAxisTimeUnit?.stepSize,
isStacked,
type,
data,
name,
yAxisUnit,
onClickHandler,
]);
useEffect(() => { useEffect(() => {
buildChart(); buildChart();
}, [buildChart]); }, [buildChart, forceReRender]);
return ( return (
<div style={{ height: '85%' }}> <div style={{ height: '85%' }}>
@ -181,6 +196,7 @@ function Graph({
} }
interface GraphProps { interface GraphProps {
animate?: boolean;
type: ChartType; type: ChartType;
data: Chart['data']; data: Chart['data'];
title?: string; title?: string;
@ -189,6 +205,7 @@ interface GraphProps {
onClickHandler?: graphOnClickHandler; onClickHandler?: graphOnClickHandler;
name: string; name: string;
yAxisUnit?: string; yAxisUnit?: string;
forceReRender?: boolean | null | number;
} }
export type graphOnClickHandler = ( export type graphOnClickHandler = (

View File

@ -5,3 +5,5 @@ export const WITHOUT_SESSION_PATH = ['/redirect'];
export const AUTH0_REDIRECT_PATH = '/redirect'; export const AUTH0_REDIRECT_PATH = '/redirect';
export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION; export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION;
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed'

View File

@ -107,7 +107,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>Application latency in ms</GraphTitle> <GraphTitle>Application latency</GraphTitle>
<GraphContainer> <GraphContainer>
<Graph <Graph
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -175,7 +175,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>Request per sec</GraphTitle> <GraphTitle>Requests</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <FullView
name="request_per_sec" name="request_per_sec"
@ -190,7 +190,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
legend: 'Request per second', legend: 'Request per second',
}, },
])} ])}
yAxisUnit="short" yAxisUnit="reqps"
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -210,7 +210,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element {
</Button> </Button>
<Card> <Card>
<GraphTitle>Error Percentage (%)</GraphTitle> <GraphTitle>Error Percentage</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <FullView
name="error_percentage_%" name="error_percentage_%"

View File

@ -25,7 +25,7 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element {
legend: '{{db_system}}', legend: '{{db_system}}',
}, },
])} ])}
yAxisUnit="short" yAxisUnit="reqps"
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -33,7 +33,7 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element {
<Col span={12}> <Col span={12}>
<Card> <Card>
<GraphTitle>Database Calls Avg Duration (in ms)</GraphTitle> <GraphTitle>Database Calls Avg Duration</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <FullView
name="database_call_avg_duration" name="database_call_avg_duration"

View File

@ -14,7 +14,7 @@ function External({ getWidget }: ExternalProps): JSX.Element {
<Row gutter={24}> <Row gutter={24}>
<Col span={12}> <Col span={12}>
<Card> <Card>
<GraphTitle>External Call Error Percentage (%)</GraphTitle> <GraphTitle>External Call Error Percentage</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <FullView
name="external_call_error_percentage" name="external_call_error_percentage"
@ -68,7 +68,7 @@ function External({ getWidget }: ExternalProps): JSX.Element {
legend: '{{http_url}}', legend: '{{http_url}}',
}, },
])} ])}
yAxisUnit="short" yAxisUnit="reqps"
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>

View File

@ -1,13 +1,16 @@
import { Menu, Typography } from 'antd'; import { Menu, Typography } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import history from 'lib/history'; import history from 'lib/history';
import setTheme from 'lib/theme/setTheme'; import setTheme from 'lib/theme/setTheme';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useLayoutEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux'; import { connect, useDispatch, useSelector } from 'react-redux';
import { NavLink, useLocation } from 'react-router-dom'; import { NavLink, useLocation } from 'react-router-dom';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk'; import { ThunkDispatch } from 'redux-thunk';
import { ToggleDarkMode } from 'store/actions'; import { ToggleDarkMode } from 'store/actions';
import { SideBarCollapse } from 'store/actions/app';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
@ -24,7 +27,10 @@ import {
} from './styles'; } from './styles';
function SideNav({ toggleDarkMode }: Props): JSX.Element { function SideNav({ toggleDarkMode }: Props): JSX.Element {
const [collapsed, setCollapsed] = useState<boolean>(false); const dispatch = useDispatch();
const [collapsed, setCollapsed] = useState<boolean>(
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
);
const { pathname } = useLocation(); const { pathname } = useLocation();
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app); const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
@ -53,6 +59,10 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
setCollapsed((collapsed) => !collapsed); setCollapsed((collapsed) => !collapsed);
}, []); }, []);
useLayoutEffect(() => {
dispatch(SideBarCollapse(collapsed));
}, [collapsed]);
const onClickHandler = useCallback( const onClickHandler = useCallback(
(to: string) => { (to: string) => {
if (pathname !== to) { if (pathname !== to) {
@ -62,7 +72,7 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element {
[pathname], [pathname],
); );
const onClickSlackHandler = () => { const onClickSlackHandler = (): void => {
window.open('https://signoz.io/slack', '_blank'); window.open('https://signoz.io/slack', '_blank');
}; };

View File

@ -3,6 +3,7 @@ import Graph from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { useMeasure } from 'react-use';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { TraceReducer } from 'types/reducer/trace'; import { TraceReducer } from 'types/reducer/trace';
@ -10,6 +11,8 @@ import { getChartData, getChartDataforGroupBy } from './config';
import { Container } from './styles'; import { Container } from './styles';
function TraceGraph(): JSX.Element { function TraceGraph(): JSX.Element {
const [ref, { width }] = useMeasure();
const { spansGraph, selectedGroupBy, yAxisUnit } = useSelector< const { spansGraph, selectedGroupBy, yAxisUnit } = useSelector<
AppState, AppState,
TraceReducer TraceReducer
@ -21,7 +24,7 @@ function TraceGraph(): JSX.Element {
return selectedGroupBy.length === 0 return selectedGroupBy.length === 0
? getChartData(payload) ? getChartData(payload)
: getChartDataforGroupBy(payload); : getChartDataforGroupBy(payload);
}, [payload]); }, [payload, selectedGroupBy.length]);
if (error) { if (error) {
return ( return (
@ -40,12 +43,14 @@ function TraceGraph(): JSX.Element {
} }
return ( return (
<Container> <Container ref={ref}>
<Graph <Graph
animate={false}
data={ChartData} data={ChartData}
name="traceGraph" name="traceGraph"
type="line" type="line"
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
forceReRender={width}
/> />
</Container> </Container>
); );

View File

@ -1,13 +1,17 @@
import React from 'react';
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
interface Props { interface Props {
center?: boolean; center?: boolean;
ref?: React.RefObject<HTMLDivElement> | null; // The ref type provided by react-use is incorrect -> https://github.com/streamich/react-use/issues/1264 Open Issue
} }
export const Container = styled.div<Props>` export const Container = styled.div<Props>`
height: 25vh; height: 25vh !important;
margin-top: 1rem; margin-top: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
overflow: auto;
width: 100% !important;
${({ center }) => ${({ center }) =>
center && center &&

View File

@ -67,35 +67,35 @@ export const functions: Dropdown[] = [
key: 'ratePerSec', key: 'ratePerSec',
yAxisUnit: 'reqps', yAxisUnit: 'reqps',
}, },
{ displayValue: 'Sum(duration in ns)', key: 'sum', yAxisUnit: 'ns' }, { displayValue: 'Sum (duration)', key: 'sum', yAxisUnit: 'ns' },
{ displayValue: 'Avg(duration in ns)', key: 'avg', yAxisUnit: 'ns' }, { displayValue: 'Avg (duration)', key: 'avg', yAxisUnit: 'ns' },
{ {
displayValue: 'Max(duration in ns)', displayValue: 'Max (duration)',
key: 'max', key: 'max',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },
{ {
displayValue: 'Min(duration in ns)', displayValue: 'Min (duration)',
key: 'min', key: 'min',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },
{ {
displayValue: '50th percentile(duration in ns)', displayValue: '50th percentile (duration)',
key: 'p50', key: 'p50',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },
{ {
displayValue: '90th percentile(duration in ns)', displayValue: '90th percentile (duration)',
key: 'p90', key: 'p90',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },
{ {
displayValue: '95th percentile(duration in ns)', displayValue: '95th percentile (duration)',
key: 'p95', key: 'p95',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },
{ {
displayValue: '99th percentile(duration in ns)', displayValue: '99th percentile (duration)',
key: 'p99', key: 'p99',
yAxisUnit: 'ns', yAxisUnit: 'ns',
}, },

View File

@ -3,6 +3,6 @@ import styled from 'styled-components';
export const SelectComponent = styled(Select)` export const SelectComponent = styled(Select)`
&&& { &&& {
min-width: 10rem; min-width: 12rem;
} }
`; `;

View File

@ -43,7 +43,7 @@ function TraceTable({ getSpansAggregate }: TraceProps): JSX.Element {
sorter: true, sorter: true,
render: (value: TableType['timestamp']): JSX.Element => { render: (value: TableType['timestamp']): JSX.Element => {
const day = dayjs(value); const day = dayjs(value);
return <div>{day.format('DD/MM/YYYY hh:mm:ss A')}</div>; return <div>{day.format('YYYY/MM/DD HH:mm:ss')}</div>;
}, },
}, },
{ {

View File

@ -1 +1,2 @@
export * from './sideBarCollapse';
export * from './toggleDarkMode'; export * from './toggleDarkMode';

View File

@ -0,0 +1,16 @@
import setLocalStorageKey from 'api/browser/localstorage/set';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
export const SideBarCollapse = (
collapseState: boolean,
): ((dispatch: Dispatch<AppActions>) => void) => {
setLocalStorageKey(IS_SIDEBAR_COLLAPSED, `${collapseState}`);
return (dispatch: Dispatch<AppActions>): void => {
dispatch({
type: 'SIDEBAR_COLLAPSE',
payload: collapseState,
});
};
};

View File

@ -1,12 +1,19 @@
import getLocalStorageKey from 'api/browser/localstorage/get'; import getLocalStorageKey from 'api/browser/localstorage/get';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import { IS_LOGGED_IN } from 'constants/auth'; import { IS_LOGGED_IN } from 'constants/auth';
import getTheme from 'lib/theme/getTheme'; import getTheme from 'lib/theme/getTheme';
import { AppAction, LOGGED_IN, SWITCH_DARK_MODE } from 'types/actions/app'; import {
AppAction,
LOGGED_IN,
SIDEBAR_COLLAPSE,
SWITCH_DARK_MODE,
} from 'types/actions/app';
import InitialValueTypes from 'types/reducer/app'; import InitialValueTypes from 'types/reducer/app';
const InitialValue: InitialValueTypes = { const InitialValue: InitialValueTypes = {
isDarkMode: getTheme() === 'darkMode', isDarkMode: getTheme() === 'darkMode',
isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes', isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes',
isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
}; };
const appReducer = ( const appReducer = (
@ -28,6 +35,13 @@ const appReducer = (
}; };
} }
case SIDEBAR_COLLAPSE: {
return {
...state,
isSideBarCollapsed: action.payload,
};
}
default: default:
return state; return state;
} }

View File

@ -1,5 +1,6 @@
export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE'; export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE';
export const LOGGED_IN = 'LOGGED_IN'; export const LOGGED_IN = 'LOGGED_IN';
export const SIDEBAR_COLLAPSE = 'SIDEBAR_COLLAPSE';
export interface SwitchDarkMode { export interface SwitchDarkMode {
type: typeof SWITCH_DARK_MODE; type: typeof SWITCH_DARK_MODE;
@ -9,4 +10,9 @@ export interface LoggedInUser {
type: typeof LOGGED_IN; type: typeof LOGGED_IN;
} }
export type AppAction = SwitchDarkMode | LoggedInUser; export interface SideBarCollapse {
type: typeof SIDEBAR_COLLAPSE;
payload: boolean;
}
export type AppAction = SwitchDarkMode | LoggedInUser | SideBarCollapse;

View File

@ -1,4 +1,5 @@
export default interface AppReducer { export default interface AppReducer {
isDarkMode: boolean; isDarkMode: boolean;
isLoggedIn: boolean; isLoggedIn: boolean;
isSideBarCollapsed: boolean;
} }