mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 18:45:56 +08:00
801 dropdown is added in the dashboard page (#1669)
* chore: update the import from constant rather than static string * chore: removed redundant div * feat: added auto refresh component * refactor: top nav is refactored
This commit is contained in:
parent
a3b03ef0ca
commit
65af8c1b98
@ -11,3 +11,5 @@ export const INVITE_MEMBERS_HASH = '#invite-team-members';
|
|||||||
|
|
||||||
export const SIGNOZ_UPGRADE_PLAN_URL =
|
export const SIGNOZ_UPGRADE_PLAN_URL =
|
||||||
'https://upgrade.signoz.io/upgrade-from-app';
|
'https://upgrade.signoz.io/upgrade-from-app';
|
||||||
|
|
||||||
|
export const DASHBOARD_TIME_IN_DURATION = 'refreshInterval';
|
||||||
|
63
frontend/src/container/TopNav/AutoRefresh/config.ts
Normal file
63
frontend/src/container/TopNav/AutoRefresh/config.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
export const options: IOptions[] = [
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
key: 'off',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5s',
|
||||||
|
key: '5s',
|
||||||
|
value: 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10s',
|
||||||
|
key: '10s',
|
||||||
|
value: 10000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30s',
|
||||||
|
key: '30s',
|
||||||
|
value: 30000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1m',
|
||||||
|
key: '1m',
|
||||||
|
value: 60000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '5m',
|
||||||
|
key: '5m',
|
||||||
|
value: 300000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '10m',
|
||||||
|
key: '10m',
|
||||||
|
value: 600000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '30m',
|
||||||
|
key: '30m',
|
||||||
|
value: 1800000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1h',
|
||||||
|
key: '1h',
|
||||||
|
value: 3600000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '2h',
|
||||||
|
key: '2h',
|
||||||
|
value: 7200000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '1d',
|
||||||
|
key: '1d',
|
||||||
|
value: 86400000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface IOptions {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
value: number;
|
||||||
|
}
|
108
frontend/src/container/TopNav/AutoRefresh/index.tsx
Normal file
108
frontend/src/container/TopNav/AutoRefresh/index.tsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { Select } from 'antd';
|
||||||
|
import get from 'api/browser/localstorage/get';
|
||||||
|
import set from 'api/browser/localstorage/set';
|
||||||
|
import { DASHBOARD_TIME_IN_DURATION } from 'constants/app';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useInterval } from 'react-use';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { AppState } from 'store/reducers';
|
||||||
|
import AppActions from 'types/actions';
|
||||||
|
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
||||||
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import { options } from './config';
|
||||||
|
import { SelectContainer } from './styles';
|
||||||
|
|
||||||
|
function AutoRefresh({ disabled = false }: AutoRefreshProps): JSX.Element {
|
||||||
|
const { minTime: initialMinTime, selectedTime } = useSelector<
|
||||||
|
AppState,
|
||||||
|
GlobalReducer
|
||||||
|
>((state) => state.globalTime);
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const params = useUrlQuery();
|
||||||
|
|
||||||
|
const localStorageData = JSON.parse(get(DASHBOARD_TIME_IN_DURATION) || '{}');
|
||||||
|
|
||||||
|
const localStorageValue = useMemo(() => localStorageData[pathname], [
|
||||||
|
pathname,
|
||||||
|
localStorageData,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
const [selectedOption, setSelectedOption] = useState<string>(
|
||||||
|
localStorageValue || options[0].key,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedOption(localStorageValue || options[0].key);
|
||||||
|
}, [localStorageValue, params]);
|
||||||
|
|
||||||
|
const getOption = useMemo(
|
||||||
|
() => options.find((option) => option.key === selectedOption),
|
||||||
|
[selectedOption],
|
||||||
|
);
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
const selectedValue = getOption?.value;
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedOption !== 'off' && selectedValue) {
|
||||||
|
const min = initialMinTime / 1000000;
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_TIME_INTERVAL,
|
||||||
|
payload: {
|
||||||
|
maxTime: dayjs().valueOf() * 1000000,
|
||||||
|
minTime: dayjs(min).subtract(selectedValue, 'second').valueOf() * 1000000,
|
||||||
|
selectedTime,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, getOption?.value || 0);
|
||||||
|
|
||||||
|
const onChangeHandler = useCallback(
|
||||||
|
(value: unknown) => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
setSelectedOption(value);
|
||||||
|
params.set(DASHBOARD_TIME_IN_DURATION, value);
|
||||||
|
set(
|
||||||
|
DASHBOARD_TIME_IN_DURATION,
|
||||||
|
JSON.stringify({ ...localStorageData, [pathname]: value }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[params, pathname, localStorageData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectContainer
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={onChangeHandler}
|
||||||
|
value={selectedOption}
|
||||||
|
>
|
||||||
|
{options.map((option) => (
|
||||||
|
<Select.Option key={option.key} value={option.key}>
|
||||||
|
{option.label}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</SelectContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AutoRefreshProps {
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoRefresh.defaultProps = {
|
||||||
|
disabled: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AutoRefresh;
|
9
frontend/src/container/TopNav/AutoRefresh/styles.ts
Normal file
9
frontend/src/container/TopNav/AutoRefresh/styles.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Select } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const SelectContainer = styled(Select)`
|
||||||
|
&&& {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 4rem;
|
||||||
|
}
|
||||||
|
`;
|
@ -2,7 +2,10 @@ import React, { useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { RefreshTextContainer, Typography } from './styles';
|
import { RefreshTextContainer, Typography } from './styles';
|
||||||
|
|
||||||
function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
|
function RefreshText({
|
||||||
|
onLastRefreshHandler,
|
||||||
|
refreshButtonHidden,
|
||||||
|
}: RefreshTextProps): JSX.Element {
|
||||||
const [refreshText, setRefreshText] = useState<string>('');
|
const [refreshText, setRefreshText] = useState<string>('');
|
||||||
|
|
||||||
// this is to update the refresh text
|
// this is to update the refresh text
|
||||||
@ -19,7 +22,7 @@ function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
|
|||||||
}, [onLastRefreshHandler, refreshText]);
|
}, [onLastRefreshHandler, refreshText]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RefreshTextContainer>
|
<RefreshTextContainer refreshButtonHidden={refreshButtonHidden}>
|
||||||
<Typography>{refreshText}</Typography>
|
<Typography>{refreshText}</Typography>
|
||||||
</RefreshTextContainer>
|
</RefreshTextContainer>
|
||||||
);
|
);
|
||||||
@ -27,6 +30,7 @@ function RefreshText({ onLastRefreshHandler }: RefreshTextProps): JSX.Element {
|
|||||||
|
|
||||||
interface RefreshTextProps {
|
interface RefreshTextProps {
|
||||||
onLastRefreshHandler: () => string;
|
onLastRefreshHandler: () => string;
|
||||||
|
refreshButtonHidden: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RefreshText;
|
export default RefreshText;
|
||||||
|
@ -67,3 +67,19 @@ export const getOptions = (routes: string): Option[] => {
|
|||||||
}
|
}
|
||||||
return Options;
|
return Options;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const routesToSkip = [
|
||||||
|
ROUTES.SETTINGS,
|
||||||
|
ROUTES.LIST_ALL_ALERT,
|
||||||
|
ROUTES.TRACE_DETAIL,
|
||||||
|
ROUTES.ALL_CHANNELS,
|
||||||
|
ROUTES.USAGE_EXPLORER,
|
||||||
|
ROUTES.INSTRUMENTATION,
|
||||||
|
ROUTES.VERSION,
|
||||||
|
ROUTES.ALL_DASHBOARD,
|
||||||
|
ROUTES.ORG_SETTINGS,
|
||||||
|
ROUTES.ERROR_DETAIL,
|
||||||
|
ROUTES.ALERTS_NEW,
|
||||||
|
ROUTES.EDIT_ALERTS,
|
||||||
|
ROUTES.LIST_ALL_ALERT,
|
||||||
|
];
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SyncOutlined } from '@ant-design/icons';
|
||||||
import { Button, Select as DefaultSelect } from 'antd';
|
import { Button, Select as DefaultSelect } from 'antd';
|
||||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||||
@ -14,10 +15,11 @@ import { AppState } from 'store/reducers';
|
|||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
|
|
||||||
|
import AutoRefresh from '../AutoRefresh';
|
||||||
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
import CustomDateTimeModal, { DateTimeRangeType } from '../CustomDateTimeModal';
|
||||||
import { getDefaultOption, getOptions, Time } from './config';
|
import { getDefaultOption, getOptions, Time } from './config';
|
||||||
import RefreshText from './Refresh';
|
import RefreshText from './Refresh';
|
||||||
import { Container, Form, FormItem } from './styles';
|
import { Form, FormItem } from './styles';
|
||||||
|
|
||||||
const { Option } = DefaultSelect;
|
const { Option } = DefaultSelect;
|
||||||
|
|
||||||
@ -240,6 +242,8 @@ function DateTimeSelection({
|
|||||||
setStartTime(dayjs(preStartTime));
|
setStartTime(dayjs(preStartTime));
|
||||||
setEndTime(dayjs(preEndTime));
|
setEndTime(dayjs(preEndTime));
|
||||||
|
|
||||||
|
setRefreshButtonHidden(updatedTime === 'custom');
|
||||||
|
|
||||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||||
}, [
|
}, [
|
||||||
location.pathname,
|
location.pathname,
|
||||||
@ -253,7 +257,7 @@ function DateTimeSelection({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<>
|
||||||
<Form
|
<Form
|
||||||
form={formSelector}
|
form={formSelector}
|
||||||
layout="inline"
|
layout="inline"
|
||||||
@ -272,9 +276,15 @@ function DateTimeSelection({
|
|||||||
</DefaultSelect>
|
</DefaultSelect>
|
||||||
|
|
||||||
<FormItem hidden={refreshButtonHidden}>
|
<FormItem hidden={refreshButtonHidden}>
|
||||||
<Button type="primary" onClick={onRefreshHandler}>
|
<Button
|
||||||
Refresh
|
icon={<SyncOutlined />}
|
||||||
</Button>
|
type="primary"
|
||||||
|
onClick={onRefreshHandler}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
<FormItem>
|
||||||
|
<AutoRefresh disabled={refreshButtonHidden} />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
@ -282,6 +292,7 @@ function DateTimeSelection({
|
|||||||
{...{
|
{...{
|
||||||
onLastRefreshHandler,
|
onLastRefreshHandler,
|
||||||
}}
|
}}
|
||||||
|
refreshButtonHidden={refreshButtonHidden}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CustomDateTimeModal
|
<CustomDateTimeModal
|
||||||
@ -291,7 +302,7 @@ function DateTimeSelection({
|
|||||||
setCustomDTPickerVisible(false);
|
setCustomDTPickerVisible(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { Form as FormComponent, Typography as TypographyComponent } from 'antd';
|
import { Form as FormComponent, Typography as TypographyComponent } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const Container = styled.div`
|
|
||||||
justify-content: flex-end;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Form = styled(FormComponent)`
|
export const Form = styled(FormComponent)`
|
||||||
&&& {
|
&&& {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -23,6 +19,12 @@ export const FormItem = styled(Form.Item)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RefreshTextContainer = styled.div`
|
interface Props {
|
||||||
|
refreshButtonHidden: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RefreshTextContainer = styled.div<Props>`
|
||||||
min-height: 2rem;
|
min-height: 2rem;
|
||||||
|
visibility: ${({ refreshButtonHidden }): string =>
|
||||||
|
refreshButtonHidden ? 'hidden' : 'visible'};
|
||||||
`;
|
`;
|
||||||
|
@ -1,52 +1,40 @@
|
|||||||
import { Col } from 'antd';
|
import { Col } from 'antd';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import React, { useMemo } from 'react';
|
||||||
import React from 'react';
|
import { matchPath, useHistory } from 'react-router-dom';
|
||||||
import { matchPath } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ShowBreadcrumbs from './Breadcrumbs';
|
import ShowBreadcrumbs from './Breadcrumbs';
|
||||||
import DateTimeSelector from './DateTimeSelection';
|
import DateTimeSelector from './DateTimeSelection';
|
||||||
|
import { routesToSkip } from './DateTimeSelection/config';
|
||||||
import { Container } from './styles';
|
import { Container } from './styles';
|
||||||
|
|
||||||
const routesToSkip = [
|
|
||||||
ROUTES.SETTINGS,
|
|
||||||
ROUTES.LIST_ALL_ALERT,
|
|
||||||
ROUTES.TRACE_DETAIL,
|
|
||||||
ROUTES.ALL_CHANNELS,
|
|
||||||
ROUTES.USAGE_EXPLORER,
|
|
||||||
ROUTES.INSTRUMENTATION,
|
|
||||||
ROUTES.VERSION,
|
|
||||||
ROUTES.ALL_DASHBOARD,
|
|
||||||
ROUTES.ORG_SETTINGS,
|
|
||||||
ROUTES.ERROR_DETAIL,
|
|
||||||
ROUTES.ALERTS_NEW,
|
|
||||||
ROUTES.EDIT_ALERTS,
|
|
||||||
ROUTES.LIST_ALL_ALERT,
|
|
||||||
];
|
|
||||||
|
|
||||||
function TopNav(): JSX.Element | null {
|
function TopNav(): JSX.Element | null {
|
||||||
if (history.location.pathname === ROUTES.SIGN_UP) {
|
const { location } = useHistory();
|
||||||
|
|
||||||
|
const isRouteToSkip = useMemo(
|
||||||
|
() =>
|
||||||
|
routesToSkip.some((route) =>
|
||||||
|
matchPath(location.pathname, { path: route, exact: true }),
|
||||||
|
),
|
||||||
|
[location.pathname],
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSignUpPage = useMemo(
|
||||||
|
() => matchPath(location.pathname, { path: ROUTES.SIGN_UP, exact: true }),
|
||||||
|
[location.pathname],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSignUpPage) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkRouteExists = (currentPath: string): boolean => {
|
|
||||||
for (let i = 0; i < routesToSkip.length; i += 1) {
|
|
||||||
if (
|
|
||||||
matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true })
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
<ShowBreadcrumbs />
|
<ShowBreadcrumbs />
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{!checkRouteExists(history.location.pathname) && (
|
{!isRouteToSkip && (
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<DateTimeSelector />
|
<DateTimeSelector />
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -2,6 +2,7 @@ import { Time } from 'container/TopNav/DateTimeSelection/config';
|
|||||||
import GetMinMax from 'lib/getMinMax';
|
import GetMinMax from 'lib/getMinMax';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
import { UPDATE_TIME_INTERVAL } from 'types/actions/globalTime';
|
||||||
|
|
||||||
export const UpdateTimeInterval = (
|
export const UpdateTimeInterval = (
|
||||||
interval: Time,
|
interval: Time,
|
||||||
@ -11,7 +12,7 @@ export const UpdateTimeInterval = (
|
|||||||
const { maxTime, minTime } = GetMinMax(interval, dateTimeRange);
|
const { maxTime, minTime } = GetMinMax(interval, dateTimeRange);
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_TIME_INTERVAL',
|
type: UPDATE_TIME_INTERVAL,
|
||||||
payload: {
|
payload: {
|
||||||
maxTime,
|
maxTime,
|
||||||
minTime,
|
minTime,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user