mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:15:57 +08:00
feat: add the Trace Explorer page with Query Builder (#2843)
* feat: update the SideNav component * feat: add the Trace Explorer page with Query Builder * chore: build is fixed * chore: tsc build is fixed * chore: menu items is updated --------- Co-authored-by: Nazarenko19 <danil.nazarenko2000@gmail.com> Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
b782bd8909
commit
37fc00b55f
@ -15,6 +15,11 @@ export const ServiceMapPage = Loadable(
|
||||
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
|
||||
);
|
||||
|
||||
export const TracesExplorer = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "Traces Explorer Page" */ 'pages/TracesExplorer'),
|
||||
);
|
||||
|
||||
export const TraceFilter = Loadable(
|
||||
() => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'),
|
||||
);
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
StatusPage,
|
||||
TraceDetail,
|
||||
TraceFilter,
|
||||
TracesExplorer,
|
||||
UnAuthorized,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
@ -140,6 +141,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'TRACE',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_EXPLORER,
|
||||
exact: true,
|
||||
component: TracesExplorer,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
exact: true,
|
||||
|
@ -5,6 +5,7 @@ const ROUTES = {
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
TRACES_EXPLORER: '/traces-explorer',
|
||||
SETTINGS: '/settings',
|
||||
INSTRUMENTATION: '/get-started',
|
||||
USAGE_EXPLORER: '/usage-explorer',
|
||||
@ -31,7 +32,6 @@ const ROUTES = {
|
||||
HOME_PAGE: '/',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
LIST_LICENSES: '/licenses',
|
||||
TRACE_EXPLORER: '/trace-explorer',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
7
frontend/src/container/Controls/config.ts
Normal file
7
frontend/src/container/Controls/config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
export const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
|
||||
|
||||
export const defaultSelectStyle: CSSProperties = {
|
||||
minWidth: '6rem',
|
||||
};
|
69
frontend/src/container/Controls/index.tsx
Normal file
69
frontend/src/container/Controls/index.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Select } from 'antd';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||
import { Container } from './styles';
|
||||
|
||||
interface ControlsProps {
|
||||
count: number;
|
||||
countPerPage: number;
|
||||
isLoading: boolean;
|
||||
handleNavigatePrevious: () => void;
|
||||
handleNavigateNext: () => void;
|
||||
handleCountItemsPerPageChange: (e: number) => void;
|
||||
}
|
||||
|
||||
function Controls(props: ControlsProps): JSX.Element | null {
|
||||
const {
|
||||
count,
|
||||
isLoading,
|
||||
countPerPage,
|
||||
handleNavigatePrevious,
|
||||
handleNavigateNext,
|
||||
handleCountItemsPerPageChange,
|
||||
} = props;
|
||||
|
||||
const isNextAndPreviousDisabled = useMemo(
|
||||
() => isLoading || countPerPage === 0 || count === 0 || count < countPerPage,
|
||||
[isLoading, countPerPage, count],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={isNextAndPreviousDisabled}
|
||||
onClick={handleNavigatePrevious}
|
||||
>
|
||||
<LeftOutlined /> Previous
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={isNextAndPreviousDisabled}
|
||||
onClick={handleNavigateNext}
|
||||
>
|
||||
Next <RightOutlined />
|
||||
</Button>
|
||||
<Select
|
||||
style={defaultSelectStyle}
|
||||
loading={isLoading}
|
||||
value={countPerPage}
|
||||
onChange={handleCountItemsPerPageChange}
|
||||
>
|
||||
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||
<Select.Option
|
||||
key={count}
|
||||
value={count}
|
||||
>{`${count} / page`}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Controls);
|
7
frontend/src/container/Controls/styles.ts
Normal file
7
frontend/src/container/Controls/styles.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
`;
|
@ -1 +0,0 @@
|
||||
export const ITEMS_PER_PAGE_OPTIONS = [25, 50, 100, 200];
|
@ -1,16 +1,11 @@
|
||||
import {
|
||||
CloudDownloadOutlined,
|
||||
FastBackwardOutlined,
|
||||
LeftOutlined,
|
||||
RightOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Divider, Dropdown, MenuProps, Select } from 'antd';
|
||||
import { CloudDownloadOutlined, FastBackwardOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Dropdown, MenuProps } from 'antd';
|
||||
import { Excel } from 'antd-table-saveas-excel';
|
||||
import Controls from 'container/Controls';
|
||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||
import dayjs from 'dayjs';
|
||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||
import { defaultSelectStyle } from 'pages/Logs/config';
|
||||
import * as Papa from 'papaparse';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -26,7 +21,6 @@ import {
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { ILogsReducer } from 'types/reducer/logs';
|
||||
|
||||
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||
import { Container, DownloadLogButton } from './styles';
|
||||
|
||||
function LogControls(): JSX.Element | null {
|
||||
@ -149,15 +143,6 @@ function LogControls(): JSX.Element | null {
|
||||
|
||||
const isLoading = isLogsLoading || isLoadingAggregate;
|
||||
|
||||
const isNextAndPreviousDisabled = useMemo(
|
||||
() =>
|
||||
isLoading ||
|
||||
logLinesPerPage === 0 ||
|
||||
logs.length === 0 ||
|
||||
logs.length < logLinesPerPage,
|
||||
[isLoading, logLinesPerPage, logs.length],
|
||||
);
|
||||
|
||||
if (liveTail !== 'STOPPED') {
|
||||
return null;
|
||||
}
|
||||
@ -179,37 +164,14 @@ function LogControls(): JSX.Element | null {
|
||||
<FastBackwardOutlined /> Go to latest
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={isNextAndPreviousDisabled}
|
||||
onClick={handleNavigatePrevious}
|
||||
>
|
||||
<LeftOutlined /> Previous
|
||||
</Button>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
type="link"
|
||||
disabled={isNextAndPreviousDisabled}
|
||||
onClick={handleNavigateNext}
|
||||
>
|
||||
Next <RightOutlined />
|
||||
</Button>
|
||||
<Select
|
||||
style={defaultSelectStyle}
|
||||
loading={isLoading}
|
||||
value={logLinesPerPage}
|
||||
onChange={handleLogLinesPerPageChange}
|
||||
>
|
||||
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||
<Select.Option
|
||||
key={count}
|
||||
value={count}
|
||||
>{`${count} / page`}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<Controls
|
||||
isLoading={isLoading}
|
||||
count={logs.length}
|
||||
countPerPage={logLinesPerPage}
|
||||
handleNavigatePrevious={handleNavigatePrevious}
|
||||
handleNavigateNext={handleNavigateNext}
|
||||
handleCountItemsPerPageChange={handleLogLinesPerPageChange}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const flattenLogData: Record<string, any> | null = useMemo(
|
||||
const flattenLogData: Record<string, string> | null = useMemo(
|
||||
() => (logData ? flattenObject(logData) : null),
|
||||
[logData],
|
||||
);
|
||||
|
@ -54,7 +54,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
||||
);
|
||||
|
||||
return (
|
||||
<Row gutter={[0, 20]} justify="start">
|
||||
<Row style={{ width: '100%' }} gutter={[0, 20]} justify="start">
|
||||
<Col span={24}>
|
||||
<Row gutter={[0, 50]}>
|
||||
{currentQuery.builder.queryData.map((query, index) => (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Col, Row } from 'antd';
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import { Fragment, memo, ReactNode, useState } from 'react';
|
||||
|
||||
// ** Types
|
||||
@ -46,7 +46,9 @@ export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({
|
||||
<Col span={24}>
|
||||
<StyledInner onClick={handleToggleOpenFilters}>
|
||||
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
||||
{!isOpenedFilters && <span>Add conditions for {filtersTexts}</span>}
|
||||
{!isOpenedFilters && (
|
||||
<Typography>Add conditions for {filtersTexts}</Typography>
|
||||
)}
|
||||
</StyledInner>
|
||||
</Col>
|
||||
{isOpenedFilters && <Col span={24}>{children}</Col>}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { memo } from 'react';
|
||||
|
||||
@ -11,5 +12,9 @@ export const FilterLabel = memo(function FilterLabel({
|
||||
}: FilterLabelProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
return <StyledLabel isDarkMode={isDarkMode}>{label}</StyledLabel>;
|
||||
return (
|
||||
<StyledLabel isDarkMode={isDarkMode}>
|
||||
<Typography>{label}</Typography>
|
||||
</StyledLabel>
|
||||
);
|
||||
});
|
||||
|
@ -30,10 +30,10 @@ export const routeConfig: Record<string, QueryParams[]> = {
|
||||
[ROUTES.SETTINGS]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.SIGN_UP]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.SOMETHING_WENT_WRONG]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACES_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||
[ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||
};
|
||||
|
@ -38,35 +38,36 @@ const menus: SidebarMenu[] = [
|
||||
icon: <BarChartOutlined />,
|
||||
},
|
||||
{
|
||||
key: 'traces',
|
||||
key: ROUTES.TRACE,
|
||||
label: 'Traces',
|
||||
icon: <MenuOutlined />,
|
||||
children: [
|
||||
{
|
||||
key: ROUTES.TRACE,
|
||||
label: 'Traces',
|
||||
},
|
||||
// {
|
||||
// key: ROUTES.TRACE_EXPLORER,
|
||||
// label: 'Explorer',
|
||||
// },
|
||||
],
|
||||
// children: [
|
||||
// {
|
||||
// key: ROUTES.TRACE,
|
||||
// label: 'Traces',
|
||||
// },
|
||||
// TODO: uncomment when will be ready explorer
|
||||
// {
|
||||
// key: ROUTES.TRACES_EXPLORER,
|
||||
// label: "Explorer",
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
key: 'logs',
|
||||
key: ROUTES.LOGS,
|
||||
label: 'Logs',
|
||||
icon: <AlignLeftOutlined />,
|
||||
children: [
|
||||
{
|
||||
key: ROUTES.LOGS,
|
||||
label: 'Search',
|
||||
},
|
||||
// TODO: uncomment when will be ready explorer
|
||||
// {
|
||||
// key: ROUTES.LOGS_EXPLORER,
|
||||
// label: 'Views',
|
||||
// },
|
||||
],
|
||||
// children: [
|
||||
// {
|
||||
// key: ROUTES.LOGS,
|
||||
// label: 'Search',
|
||||
// },
|
||||
// TODO: uncomment when will be ready explorer
|
||||
// {
|
||||
// key: ROUTES.LOGS_EXPLORER,
|
||||
// label: 'Views',
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
key: ROUTES.ALL_DASHBOARD,
|
||||
|
@ -5,6 +5,7 @@ import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
const breadcrumbNameMap = {
|
||||
[ROUTES.APPLICATION]: 'Services',
|
||||
[ROUTES.TRACE]: 'Traces',
|
||||
[ROUTES.TRACES_EXPLORER]: 'Traces Explorer',
|
||||
[ROUTES.SERVICE_MAP]: 'Service Map',
|
||||
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
||||
[ROUTES.INSTRUMENTATION]: 'Get Started',
|
||||
|
25
frontend/src/container/TracesExplorer/Controls/index.tsx
Normal file
25
frontend/src/container/TracesExplorer/Controls/index.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import Controls from 'container/Controls';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
function TraceExplorerControls(): JSX.Element | null {
|
||||
const handleCountItemsPerPageChange = (): void => {};
|
||||
const handleNavigatePrevious = (): void => {};
|
||||
const handleNavigateNext = (): void => {};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Controls
|
||||
isLoading={false}
|
||||
count={0}
|
||||
countPerPage={0}
|
||||
handleNavigatePrevious={handleNavigatePrevious}
|
||||
handleNavigateNext={handleNavigateNext}
|
||||
handleCountItemsPerPageChange={handleCountItemsPerPageChange}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(TraceExplorerControls);
|
8
frontend/src/container/TracesExplorer/Controls/styles.ts
Normal file
8
frontend/src/container/TracesExplorer/Controls/styles.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
`;
|
32
frontend/src/container/TracesExplorer/QuerySection/index.tsx
Normal file
32
frontend/src/container/TracesExplorer/QuerySection/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Button } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { ButtonWrapper, Container } from './styles';
|
||||
|
||||
function QuerySection(): JSX.Element {
|
||||
const { handleRunQuery } = useQueryBuilder();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<QueryBuilder
|
||||
panelType={PANEL_TYPES.TIME_SERIES}
|
||||
config={{
|
||||
queryVariant: 'static',
|
||||
initialDataSource: DataSource.TRACES,
|
||||
}}
|
||||
actions={
|
||||
<ButtonWrapper>
|
||||
<Button onClick={handleRunQuery} type="primary">
|
||||
Run Query
|
||||
</Button>
|
||||
</ButtonWrapper>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuerySection;
|
16
frontend/src/container/TracesExplorer/QuerySection/styles.ts
Normal file
16
frontend/src/container/TracesExplorer/QuerySection/styles.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Col } from 'antd';
|
||||
import Card from 'antd/es/card/Card';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Card)`
|
||||
border: none;
|
||||
background: inherit;
|
||||
|
||||
.ant-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonWrapper = styled(Col)`
|
||||
margin-left: auto;
|
||||
`;
|
@ -0,0 +1,73 @@
|
||||
import Graph from 'components/Graph';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import getChartData from 'lib/getChartData';
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { Container, ErrorText } from './styles';
|
||||
|
||||
function TimeSeriesView(): JSX.Element {
|
||||
const { stagedQuery } = useQueryBuilder();
|
||||
|
||||
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const { data, isLoading, isError } = useGetQueryRange(
|
||||
{
|
||||
query: stagedQuery || initialQueriesMap.traces,
|
||||
graphType: 'graph',
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
params: {
|
||||
dataSource: 'traces',
|
||||
},
|
||||
},
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
globalSelectedTime,
|
||||
stagedQuery,
|
||||
maxTime,
|
||||
minTime,
|
||||
],
|
||||
enabled: !!stagedQuery,
|
||||
},
|
||||
);
|
||||
|
||||
const chartData = useMemo(
|
||||
() =>
|
||||
getChartData({
|
||||
queryData: [
|
||||
{
|
||||
queryData: data?.payload?.data?.result || [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[data],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{isLoading && <Spinner height="50vh" size="small" tip="Loading..." />}
|
||||
{isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>}
|
||||
{!isLoading && !isError && (
|
||||
<Graph
|
||||
animate={false}
|
||||
data={chartData}
|
||||
name="tracesExplorerGraph"
|
||||
type="line"
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default TimeSeriesView;
|
@ -0,0 +1,17 @@
|
||||
import { Typography } from 'antd';
|
||||
import Card from 'antd/es/card/Card';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Card)`
|
||||
position: relative;
|
||||
margin: 0.5rem 0 3.1rem 0;
|
||||
|
||||
.ant-card-body {
|
||||
height: 50vh;
|
||||
min-height: 350px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ErrorText = styled(Typography)`
|
||||
text-align: center;
|
||||
`;
|
@ -1,5 +1,4 @@
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import GetStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import store from 'store';
|
||||
|
||||
export const getDashboardVariables = (): Record<string, unknown> => {
|
||||
@ -13,16 +12,11 @@ export const getDashboardVariables = (): Record<string, unknown> => {
|
||||
data: { variables = {} },
|
||||
} = selectedDashboard;
|
||||
|
||||
const minMax = GetMinMax(globalTime.selectedTime, [
|
||||
globalTime.minTime / 1000000,
|
||||
globalTime.maxTime / 1000000,
|
||||
]);
|
||||
|
||||
const { start, end } = GetStartAndEndTime({
|
||||
const { start, end } = getStartEndRangeTime({
|
||||
type: 'GLOBAL_TIME',
|
||||
minTime: minMax.minTime,
|
||||
maxTime: minMax.maxTime,
|
||||
interval: globalTime.selectedTime,
|
||||
});
|
||||
|
||||
const variablesTuple: Record<string, unknown> = {
|
||||
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
|
||||
SIGNOZ_END_TIME: parseInt(end, 10) * 1e3,
|
||||
|
48
frontend/src/lib/getStartEndRangeTime.ts
Normal file
48
frontend/src/lib/getStartEndRangeTime.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import store from 'store';
|
||||
|
||||
import getMaxMinTime from './getMaxMinTime';
|
||||
import getMinMax from './getMinMax';
|
||||
import getStartAndEndTime from './getStartAndEndTime';
|
||||
|
||||
const getStartEndRangeTime = ({
|
||||
type = 'GLOBAL_TIME',
|
||||
graphType = null,
|
||||
interval = 'custom',
|
||||
}: GetStartEndRangeTimesProps): GetStartEndRangeTimesPayload => {
|
||||
const { globalTime } = store.getState();
|
||||
|
||||
const minMax = getMinMax(interval, [
|
||||
globalTime.minTime / 1000000,
|
||||
globalTime.maxTime / 1000000,
|
||||
]);
|
||||
|
||||
const maxMinTime = getMaxMinTime({
|
||||
graphType,
|
||||
maxTime: minMax.maxTime,
|
||||
minTime: minMax.minTime,
|
||||
});
|
||||
|
||||
const { end, start } = getStartAndEndTime({
|
||||
type,
|
||||
maxTime: maxMinTime.maxTime,
|
||||
minTime: maxMinTime.minTime,
|
||||
});
|
||||
|
||||
return { start, end };
|
||||
};
|
||||
|
||||
interface GetStartEndRangeTimesProps {
|
||||
type?: timePreferenceType;
|
||||
graphType?: ITEMS | null;
|
||||
interval?: Time;
|
||||
}
|
||||
|
||||
interface GetStartEndRangeTimesPayload {
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
|
||||
export default getStartEndRangeTime;
|
6
frontend/src/pages/TracesExplorer/constants.ts
Normal file
6
frontend/src/pages/TracesExplorer/constants.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const CURRENT_TRACES_EXPLORER_TAB = 'currentTab';
|
||||
|
||||
export enum TracesExplorerTabs {
|
||||
TIME_SERIES = 'times-series',
|
||||
TRACES = 'traces',
|
||||
}
|
59
frontend/src/pages/TracesExplorer/index.tsx
Normal file
59
frontend/src/pages/TracesExplorer/index.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { Tabs } from 'antd';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import QuerySection from 'container/TracesExplorer/QuerySection';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
|
||||
import { CURRENT_TRACES_EXPLORER_TAB, TracesExplorerTabs } from './constants';
|
||||
import { Container } from './styles';
|
||||
import { getTabsItems } from './utils';
|
||||
|
||||
function TracesExplorer(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const currentUrlTab = urlQuery.get(
|
||||
CURRENT_TRACES_EXPLORER_TAB,
|
||||
) as TracesExplorerTabs;
|
||||
const currentTab = currentUrlTab || TracesExplorerTabs.TIME_SERIES;
|
||||
const tabsItems = getTabsItems();
|
||||
|
||||
const redirectWithCurrentTab = useCallback(
|
||||
(tabKey: string): void => {
|
||||
urlQuery.set(CURRENT_TRACES_EXPLORER_TAB, tabKey);
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[history, location, urlQuery],
|
||||
);
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
(tabKey: string): void => {
|
||||
redirectWithCurrentTab(tabKey);
|
||||
},
|
||||
[redirectWithCurrentTab],
|
||||
);
|
||||
|
||||
useShareBuilderUrl({ defaultValue: initialQueriesMap.traces });
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUrlTab) return;
|
||||
|
||||
redirectWithCurrentTab(TracesExplorerTabs.TIME_SERIES);
|
||||
}, [currentUrlTab, redirectWithCurrentTab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QuerySection />
|
||||
|
||||
<Container>
|
||||
<Tabs activeKey={currentTab} items={tabsItems} onChange={handleTabChange} />
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TracesExplorer;
|
5
frontend/src/pages/TracesExplorer/styles.ts
Normal file
5
frontend/src/pages/TracesExplorer/styles.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
margin: 1rem 0;
|
||||
`;
|
17
frontend/src/pages/TracesExplorer/utils.tsx
Normal file
17
frontend/src/pages/TracesExplorer/utils.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { TabsProps } from 'antd';
|
||||
import TimeSeriesView from 'container/TracesExplorer/TimeSeriesView';
|
||||
|
||||
import { TracesExplorerTabs } from './constants';
|
||||
|
||||
export const getTabsItems = (): TabsProps['items'] => [
|
||||
{
|
||||
label: 'Time Series',
|
||||
key: TracesExplorerTabs.TIME_SERIES,
|
||||
children: <TimeSeriesView />,
|
||||
},
|
||||
{
|
||||
label: 'Traces',
|
||||
key: TracesExplorerTabs.TRACES,
|
||||
children: <div>Traces tab</div>,
|
||||
},
|
||||
];
|
@ -5,19 +5,16 @@
|
||||
import { getMetricsQueryRange } from 'api/metrics/getQueryRange';
|
||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
||||
import GetMaxMinTime from 'lib/getMaxMinTime';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import GetStartAndEndTime from 'lib/getStartAndEndTime';
|
||||
import getStep from 'lib/getStep';
|
||||
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import store from 'store';
|
||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
|
||||
export async function GetMetricQueryRange({
|
||||
query,
|
||||
@ -25,6 +22,7 @@ export async function GetMetricQueryRange({
|
||||
graphType,
|
||||
selectedTime,
|
||||
variables = {},
|
||||
params = {},
|
||||
}: GetQueryResultsProps): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
||||
const queryData = query[query.queryType];
|
||||
let legendMap: Record<string, string> = {};
|
||||
@ -83,30 +81,18 @@ export async function GetMetricQueryRange({
|
||||
return;
|
||||
}
|
||||
|
||||
const { globalTime } = store.getState();
|
||||
|
||||
const minMax = GetMinMax(globalSelectedInterval, [
|
||||
globalTime.minTime / 1000000,
|
||||
globalTime.maxTime / 1000000,
|
||||
]);
|
||||
|
||||
const getMaxMinTime = GetMaxMinTime({
|
||||
graphType: null,
|
||||
maxTime: minMax.maxTime,
|
||||
minTime: minMax.minTime,
|
||||
});
|
||||
|
||||
const { end, start } = GetStartAndEndTime({
|
||||
const { start, end } = getStartEndRangeTime({
|
||||
type: selectedTime,
|
||||
maxTime: getMaxMinTime.maxTime,
|
||||
minTime: getMaxMinTime.minTime,
|
||||
interval: globalSelectedInterval,
|
||||
});
|
||||
|
||||
const response = await getMetricsQueryRange({
|
||||
start: parseInt(start, 10) * 1e3,
|
||||
end: parseInt(end, 10) * 1e3,
|
||||
step: getStep({ start, end, inputFormat: 'ms' }),
|
||||
variables,
|
||||
...QueryPayload,
|
||||
...params,
|
||||
});
|
||||
if (response.statusCode >= 400) {
|
||||
throw new Error(
|
||||
@ -148,4 +134,5 @@ export interface GetQueryResultsProps {
|
||||
selectedTime: timePreferenceType;
|
||||
globalSelectedInterval: Time;
|
||||
variables?: Record<string, unknown>;
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SIGN_UP: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACES_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
UN_AUTHORIZED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
@ -71,5 +72,4 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
LIST_LICENSES: ['ADMIN'],
|
||||
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user