mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 08:45:55 +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'),
|
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const TracesExplorer = Loadable(
|
||||||
|
() =>
|
||||||
|
import(/* webpackChunkName: "Traces Explorer Page" */ 'pages/TracesExplorer'),
|
||||||
|
);
|
||||||
|
|
||||||
export const TraceFilter = Loadable(
|
export const TraceFilter = Loadable(
|
||||||
() => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'),
|
() => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'),
|
||||||
);
|
);
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
StatusPage,
|
StatusPage,
|
||||||
TraceDetail,
|
TraceDetail,
|
||||||
TraceFilter,
|
TraceFilter,
|
||||||
|
TracesExplorer,
|
||||||
UnAuthorized,
|
UnAuthorized,
|
||||||
UsageExplorerPage,
|
UsageExplorerPage,
|
||||||
} from './pageComponents';
|
} from './pageComponents';
|
||||||
@ -140,6 +141,13 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'TRACE',
|
key: 'TRACE',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ROUTES.TRACES_EXPLORER,
|
||||||
|
exact: true,
|
||||||
|
component: TracesExplorer,
|
||||||
|
isPrivate: true,
|
||||||
|
key: 'TRACES_EXPLORER',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ROUTES.CHANNELS_NEW,
|
path: ROUTES.CHANNELS_NEW,
|
||||||
exact: true,
|
exact: true,
|
||||||
|
@ -5,6 +5,7 @@ const ROUTES = {
|
|||||||
SERVICE_MAP: '/service-map',
|
SERVICE_MAP: '/service-map',
|
||||||
TRACE: '/trace',
|
TRACE: '/trace',
|
||||||
TRACE_DETAIL: '/trace/:id',
|
TRACE_DETAIL: '/trace/:id',
|
||||||
|
TRACES_EXPLORER: '/traces-explorer',
|
||||||
SETTINGS: '/settings',
|
SETTINGS: '/settings',
|
||||||
INSTRUMENTATION: '/get-started',
|
INSTRUMENTATION: '/get-started',
|
||||||
USAGE_EXPLORER: '/usage-explorer',
|
USAGE_EXPLORER: '/usage-explorer',
|
||||||
@ -31,7 +32,6 @@ const ROUTES = {
|
|||||||
HOME_PAGE: '/',
|
HOME_PAGE: '/',
|
||||||
PASSWORD_RESET: '/password-reset',
|
PASSWORD_RESET: '/password-reset',
|
||||||
LIST_LICENSES: '/licenses',
|
LIST_LICENSES: '/licenses',
|
||||||
TRACE_EXPLORER: '/trace-explorer',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ROUTES;
|
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 {
|
import { CloudDownloadOutlined, FastBackwardOutlined } from '@ant-design/icons';
|
||||||
CloudDownloadOutlined,
|
import { Button, Divider, Dropdown, MenuProps } from 'antd';
|
||||||
FastBackwardOutlined,
|
|
||||||
LeftOutlined,
|
|
||||||
RightOutlined,
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
import { Button, Divider, Dropdown, MenuProps, Select } from 'antd';
|
|
||||||
import { Excel } from 'antd-table-saveas-excel';
|
import { Excel } from 'antd-table-saveas-excel';
|
||||||
|
import Controls from 'container/Controls';
|
||||||
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
import { getGlobalTime } from 'container/LogsSearchFilter/utils';
|
||||||
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
import { getMinMax } from 'container/TopNav/AutoRefresh/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { defaultSelectStyle } from 'pages/Logs/config';
|
|
||||||
import * as Papa from 'papaparse';
|
import * as Papa from 'papaparse';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
@ -26,7 +21,6 @@ import {
|
|||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
import { ILogsReducer } from 'types/reducer/logs';
|
import { ILogsReducer } from 'types/reducer/logs';
|
||||||
|
|
||||||
import { ITEMS_PER_PAGE_OPTIONS } from './config';
|
|
||||||
import { Container, DownloadLogButton } from './styles';
|
import { Container, DownloadLogButton } from './styles';
|
||||||
|
|
||||||
function LogControls(): JSX.Element | null {
|
function LogControls(): JSX.Element | null {
|
||||||
@ -149,15 +143,6 @@ function LogControls(): JSX.Element | null {
|
|||||||
|
|
||||||
const isLoading = isLogsLoading || isLoadingAggregate;
|
const isLoading = isLogsLoading || isLoadingAggregate;
|
||||||
|
|
||||||
const isNextAndPreviousDisabled = useMemo(
|
|
||||||
() =>
|
|
||||||
isLoading ||
|
|
||||||
logLinesPerPage === 0 ||
|
|
||||||
logs.length === 0 ||
|
|
||||||
logs.length < logLinesPerPage,
|
|
||||||
[isLoading, logLinesPerPage, logs.length],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (liveTail !== 'STOPPED') {
|
if (liveTail !== 'STOPPED') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -179,37 +164,14 @@ function LogControls(): JSX.Element | null {
|
|||||||
<FastBackwardOutlined /> Go to latest
|
<FastBackwardOutlined /> Go to latest
|
||||||
</Button>
|
</Button>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<Button
|
<Controls
|
||||||
loading={isLoading}
|
isLoading={isLoading}
|
||||||
size="small"
|
count={logs.length}
|
||||||
type="link"
|
countPerPage={logLinesPerPage}
|
||||||
disabled={isNextAndPreviousDisabled}
|
handleNavigatePrevious={handleNavigatePrevious}
|
||||||
onClick={handleNavigatePrevious}
|
handleNavigateNext={handleNavigateNext}
|
||||||
>
|
handleCountItemsPerPageChange={handleLogLinesPerPageChange}
|
||||||
<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>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ function TableView({ logData }: TableViewProps): JSX.Element | null {
|
|||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
|
||||||
const flattenLogData: Record<string, any> | null = useMemo(
|
const flattenLogData: Record<string, string> | null = useMemo(
|
||||||
() => (logData ? flattenObject(logData) : null),
|
() => (logData ? flattenObject(logData) : null),
|
||||||
[logData],
|
[logData],
|
||||||
);
|
);
|
||||||
|
@ -54,7 +54,7 @@ export const QueryBuilder = memo(function QueryBuilder({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[0, 20]} justify="start">
|
<Row style={{ width: '100%' }} gutter={[0, 20]} justify="start">
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Row gutter={[0, 50]}>
|
<Row gutter={[0, 50]}>
|
||||||
{currentQuery.builder.queryData.map((query, index) => (
|
{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';
|
import { Fragment, memo, ReactNode, useState } from 'react';
|
||||||
|
|
||||||
// ** Types
|
// ** Types
|
||||||
@ -46,7 +46,9 @@ export const AdditionalFiltersToggler = memo(function AdditionalFiltersToggler({
|
|||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<StyledInner onClick={handleToggleOpenFilters}>
|
<StyledInner onClick={handleToggleOpenFilters}>
|
||||||
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
{isOpenedFilters ? <StyledIconClose /> : <StyledIconOpen />}
|
||||||
{!isOpenedFilters && <span>Add conditions for {filtersTexts}</span>}
|
{!isOpenedFilters && (
|
||||||
|
<Typography>Add conditions for {filtersTexts}</Typography>
|
||||||
|
)}
|
||||||
</StyledInner>
|
</StyledInner>
|
||||||
</Col>
|
</Col>
|
||||||
{isOpenedFilters && <Col span={24}>{children}</Col>}
|
{isOpenedFilters && <Col span={24}>{children}</Col>}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Typography } from 'antd';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
@ -11,5 +12,9 @@ export const FilterLabel = memo(function FilterLabel({
|
|||||||
}: FilterLabelProps): JSX.Element {
|
}: FilterLabelProps): JSX.Element {
|
||||||
const isDarkMode = useIsDarkMode();
|
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.SETTINGS]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.SIGN_UP]: [QueryParams.resourceAttributes],
|
[ROUTES.SIGN_UP]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.SOMETHING_WENT_WRONG]: [QueryParams.resourceAttributes],
|
[ROUTES.SOMETHING_WENT_WRONG]: [QueryParams.resourceAttributes],
|
||||||
|
[ROUTES.TRACES_EXPLORER]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.TRACE]: [QueryParams.resourceAttributes],
|
[ROUTES.TRACE]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
|
[ROUTES.TRACE_DETAIL]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
[ROUTES.UN_AUTHORIZED]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
[ROUTES.USAGE_EXPLORER]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
[ROUTES.VERSION]: [QueryParams.resourceAttributes],
|
||||||
[ROUTES.TRACE_EXPLORER]: [QueryParams.resourceAttributes],
|
|
||||||
};
|
};
|
||||||
|
@ -38,35 +38,36 @@ const menus: SidebarMenu[] = [
|
|||||||
icon: <BarChartOutlined />,
|
icon: <BarChartOutlined />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'traces',
|
key: ROUTES.TRACE,
|
||||||
label: 'Traces',
|
label: 'Traces',
|
||||||
icon: <MenuOutlined />,
|
icon: <MenuOutlined />,
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
key: ROUTES.TRACE,
|
// key: ROUTES.TRACE,
|
||||||
label: 'Traces',
|
// label: 'Traces',
|
||||||
},
|
// },
|
||||||
// {
|
// TODO: uncomment when will be ready explorer
|
||||||
// key: ROUTES.TRACE_EXPLORER,
|
// {
|
||||||
// label: 'Explorer',
|
// key: ROUTES.TRACES_EXPLORER,
|
||||||
// },
|
// label: "Explorer",
|
||||||
],
|
// },
|
||||||
|
// ],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'logs',
|
key: ROUTES.LOGS,
|
||||||
label: 'Logs',
|
label: 'Logs',
|
||||||
icon: <AlignLeftOutlined />,
|
icon: <AlignLeftOutlined />,
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
key: ROUTES.LOGS,
|
// key: ROUTES.LOGS,
|
||||||
label: 'Search',
|
// label: 'Search',
|
||||||
},
|
// },
|
||||||
// TODO: uncomment when will be ready explorer
|
// TODO: uncomment when will be ready explorer
|
||||||
// {
|
// {
|
||||||
// key: ROUTES.LOGS_EXPLORER,
|
// key: ROUTES.LOGS_EXPLORER,
|
||||||
// label: 'Views',
|
// label: 'Views',
|
||||||
// },
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: ROUTES.ALL_DASHBOARD,
|
key: ROUTES.ALL_DASHBOARD,
|
||||||
|
@ -5,6 +5,7 @@ import { Link, RouteComponentProps, withRouter } from 'react-router-dom';
|
|||||||
const breadcrumbNameMap = {
|
const breadcrumbNameMap = {
|
||||||
[ROUTES.APPLICATION]: 'Services',
|
[ROUTES.APPLICATION]: 'Services',
|
||||||
[ROUTES.TRACE]: 'Traces',
|
[ROUTES.TRACE]: 'Traces',
|
||||||
|
[ROUTES.TRACES_EXPLORER]: 'Traces Explorer',
|
||||||
[ROUTES.SERVICE_MAP]: 'Service Map',
|
[ROUTES.SERVICE_MAP]: 'Service Map',
|
||||||
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
[ROUTES.USAGE_EXPLORER]: 'Usage Explorer',
|
||||||
[ROUTES.INSTRUMENTATION]: 'Get Started',
|
[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 getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||||
import GetStartAndEndTime from 'lib/getStartAndEndTime';
|
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
export const getDashboardVariables = (): Record<string, unknown> => {
|
export const getDashboardVariables = (): Record<string, unknown> => {
|
||||||
@ -13,16 +12,11 @@ export const getDashboardVariables = (): Record<string, unknown> => {
|
|||||||
data: { variables = {} },
|
data: { variables = {} },
|
||||||
} = selectedDashboard;
|
} = selectedDashboard;
|
||||||
|
|
||||||
const minMax = GetMinMax(globalTime.selectedTime, [
|
const { start, end } = getStartEndRangeTime({
|
||||||
globalTime.minTime / 1000000,
|
|
||||||
globalTime.maxTime / 1000000,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const { start, end } = GetStartAndEndTime({
|
|
||||||
type: 'GLOBAL_TIME',
|
type: 'GLOBAL_TIME',
|
||||||
minTime: minMax.minTime,
|
interval: globalTime.selectedTime,
|
||||||
maxTime: minMax.maxTime,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const variablesTuple: Record<string, unknown> = {
|
const variablesTuple: Record<string, unknown> = {
|
||||||
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
|
SIGNOZ_START_TIME: parseInt(start, 10) * 1e3,
|
||||||
SIGNOZ_END_TIME: parseInt(end, 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 { getMetricsQueryRange } from 'api/metrics/getQueryRange';
|
||||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
import { Time } from 'container/TopNav/DateTimeSelection/config';
|
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 getStep from 'lib/getStep';
|
||||||
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
import store from 'store';
|
|
||||||
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
|
||||||
import { SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
import { EQueryType } from 'types/common/dashboard';
|
||||||
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
|
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
|
||||||
|
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||||
|
|
||||||
export async function GetMetricQueryRange({
|
export async function GetMetricQueryRange({
|
||||||
query,
|
query,
|
||||||
@ -25,6 +22,7 @@ export async function GetMetricQueryRange({
|
|||||||
graphType,
|
graphType,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
variables = {},
|
variables = {},
|
||||||
|
params = {},
|
||||||
}: GetQueryResultsProps): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
}: GetQueryResultsProps): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
||||||
const queryData = query[query.queryType];
|
const queryData = query[query.queryType];
|
||||||
let legendMap: Record<string, string> = {};
|
let legendMap: Record<string, string> = {};
|
||||||
@ -83,30 +81,18 @@ export async function GetMetricQueryRange({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { globalTime } = store.getState();
|
const { start, end } = getStartEndRangeTime({
|
||||||
|
|
||||||
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({
|
|
||||||
type: selectedTime,
|
type: selectedTime,
|
||||||
maxTime: getMaxMinTime.maxTime,
|
interval: globalSelectedInterval,
|
||||||
minTime: getMaxMinTime.minTime,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await getMetricsQueryRange({
|
const response = await getMetricsQueryRange({
|
||||||
start: parseInt(start, 10) * 1e3,
|
start: parseInt(start, 10) * 1e3,
|
||||||
end: parseInt(end, 10) * 1e3,
|
end: parseInt(end, 10) * 1e3,
|
||||||
step: getStep({ start, end, inputFormat: 'ms' }),
|
step: getStep({ start, end, inputFormat: 'ms' }),
|
||||||
variables,
|
variables,
|
||||||
...QueryPayload,
|
...QueryPayload,
|
||||||
|
...params,
|
||||||
});
|
});
|
||||||
if (response.statusCode >= 400) {
|
if (response.statusCode >= 400) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -148,4 +134,5 @@ export interface GetQueryResultsProps {
|
|||||||
selectedTime: timePreferenceType;
|
selectedTime: timePreferenceType;
|
||||||
globalSelectedInterval: Time;
|
globalSelectedInterval: Time;
|
||||||
variables?: Record<string, unknown>;
|
variables?: Record<string, unknown>;
|
||||||
|
params?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
|||||||
SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
SIGN_UP: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SIGN_UP: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
|
TRACES_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
TRACE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
TRACE: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
TRACE_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
TRACE_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
UN_AUTHORIZED: ['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: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||||
LIST_LICENSES: ['ADMIN'],
|
LIST_LICENSES: ['ADMIN'],
|
||||||
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user