mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:36:03 +08:00
feat: add the traces view for the traces explorer (#2966)
* feat: add dynamic table based on query * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the list view for the traces explorer * feat: add the table view for the traces explorer * feat: add the table view for the traces explorer * feat: add the trace view for the traces explorer * feat: update the traces view tab for the traces explorer page * feat: update the traces view --------- Co-authored-by: Yevhen Shevchenko <y.shevchenko@seedium.io> Co-authored-by: Nazarenko19 <danil.nazarenko2000@gmail.com> Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
parent
78da014b52
commit
10a3a6d3e5
@ -232,8 +232,8 @@ export const PANEL_TYPES: Record<PanelTypeKeys, GRAPH_TYPES> = {
|
||||
VALUE: 'value',
|
||||
TABLE: 'table',
|
||||
LIST: 'list',
|
||||
EMPTY_WIDGET: 'EMPTY_WIDGET',
|
||||
TRACE: 'trace',
|
||||
EMPTY_WIDGET: 'EMPTY_WIDGET',
|
||||
};
|
||||
|
||||
export type IQueryBuilderState = 'search';
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
|
||||
import { Button, Select } from 'antd';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import { defaultSelectStyle, ITEMS_PER_PAGE_OPTIONS } from './config';
|
||||
import { defaultSelectStyle } from './config';
|
||||
import { Container } from './styles';
|
||||
|
||||
function Controls({
|
||||
offset = 0,
|
||||
perPageOptions = DEFAULT_PER_PAGE_OPTIONS,
|
||||
isLoading,
|
||||
totalCount,
|
||||
countPerPage,
|
||||
@ -51,7 +52,7 @@ function Controls({
|
||||
value={countPerPage}
|
||||
onChange={handleCountItemsPerPageChange}
|
||||
>
|
||||
{ITEMS_PER_PAGE_OPTIONS.map((count) => (
|
||||
{perPageOptions.map((count) => (
|
||||
<Select.Option
|
||||
key={count}
|
||||
value={count}
|
||||
@ -64,10 +65,12 @@ function Controls({
|
||||
|
||||
Controls.defaultProps = {
|
||||
offset: 0,
|
||||
perPageOptions: DEFAULT_PER_PAGE_OPTIONS,
|
||||
};
|
||||
|
||||
export interface ControlsProps {
|
||||
offset?: Pagination['offset'];
|
||||
perPageOptions?: number[];
|
||||
totalCount: number;
|
||||
countPerPage: Pagination['limit'];
|
||||
isLoading: boolean;
|
||||
|
@ -40,6 +40,7 @@ export const Query = memo(function Query({
|
||||
const {
|
||||
operators,
|
||||
isMetricsDataSource,
|
||||
isTracePanelType,
|
||||
listOfAdditionalFilters,
|
||||
handleChangeAggregatorAttribute,
|
||||
handleChangeDataSource,
|
||||
@ -352,13 +353,15 @@ export const Query = memo(function Query({
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<AdditionalFiltersToggler listOfAdditionalFilter={listOfAdditionalFilters}>
|
||||
<Row gutter={[0, 11]} justify="space-between">
|
||||
{renderAdditionalFilters()}
|
||||
</Row>
|
||||
</AdditionalFiltersToggler>
|
||||
</Col>
|
||||
{!isTracePanelType && (
|
||||
<Col span={24}>
|
||||
<AdditionalFiltersToggler listOfAdditionalFilter={listOfAdditionalFilters}>
|
||||
<Row gutter={[0, 11]} justify="space-between">
|
||||
{renderAdditionalFilters()}
|
||||
</Row>
|
||||
</AdditionalFiltersToggler>
|
||||
</Col>
|
||||
)}
|
||||
<Row style={{ width: '100%' }}>
|
||||
<Input
|
||||
onChange={handleChangeQueryLegend}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { TableProps } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
||||
import { ReactNode } from 'react';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@ -11,4 +12,5 @@ export type QueryTableProps = Omit<
|
||||
queryTableData: QueryDataV3[];
|
||||
query: Query;
|
||||
renderActionCell?: (record: RowData) => ReactNode;
|
||||
modifyColumns?: (columns: ColumnsType<RowData>) => ColumnsType<RowData>;
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ export function QueryTable({
|
||||
queryTableData,
|
||||
query,
|
||||
renderActionCell,
|
||||
modifyColumns,
|
||||
...props
|
||||
}: QueryTableProps): JSX.Element {
|
||||
const { columns, dataSource } = useMemo(
|
||||
@ -39,9 +40,13 @@ export function QueryTable({
|
||||
return currentColumns;
|
||||
}, [columns]);
|
||||
|
||||
const tableColumns = modifyColumns
|
||||
? modifyColumns(modifiedColumns)
|
||||
: modifiedColumns;
|
||||
|
||||
return (
|
||||
<ResizeTable
|
||||
columns={modifiedColumns}
|
||||
columns={tableColumns}
|
||||
tableLayout="fixed"
|
||||
dataSource={dataSource}
|
||||
scroll={{ x: true }}
|
||||
|
@ -33,9 +33,9 @@ function TimeSeriesViewContainer({
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
globalSelectedTime,
|
||||
stagedQuery,
|
||||
maxTime,
|
||||
minTime,
|
||||
stagedQuery,
|
||||
],
|
||||
enabled: !!stagedQuery,
|
||||
},
|
||||
|
@ -1,25 +1,51 @@
|
||||
import Controls from 'container/Controls';
|
||||
import Controls, { ControlsProps } from 'container/Controls';
|
||||
import OptionsMenu from 'container/OptionsMenu';
|
||||
import { OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||
import useQueryPagination from 'hooks/queryPagination/useQueryPagination';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
function TraceExplorerControls(): JSX.Element | null {
|
||||
const handleCountItemsPerPageChange = (): void => {};
|
||||
const handleNavigatePrevious = (): void => {};
|
||||
const handleNavigateNext = (): void => {};
|
||||
function TraceExplorerControls({
|
||||
isLoading,
|
||||
totalCount,
|
||||
perPageOptions,
|
||||
config,
|
||||
}: TraceExplorerControlsProps): JSX.Element | null {
|
||||
const {
|
||||
pagination,
|
||||
handleCountItemsPerPageChange,
|
||||
handleNavigateNext,
|
||||
handleNavigatePrevious,
|
||||
} = useQueryPagination(totalCount, perPageOptions);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{config && <OptionsMenu config={{ addColumn: config?.addColumn }} />}
|
||||
|
||||
<Controls
|
||||
isLoading={false}
|
||||
totalCount={0}
|
||||
countPerPage={25}
|
||||
handleNavigatePrevious={handleNavigatePrevious}
|
||||
handleNavigateNext={handleNavigateNext}
|
||||
isLoading={isLoading}
|
||||
totalCount={totalCount}
|
||||
offset={pagination.offset}
|
||||
countPerPage={pagination.limit}
|
||||
perPageOptions={perPageOptions}
|
||||
handleCountItemsPerPageChange={handleCountItemsPerPageChange}
|
||||
handleNavigateNext={handleNavigateNext}
|
||||
handleNavigatePrevious={handleNavigatePrevious}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
TraceExplorerControls.defaultProps = {
|
||||
config: null,
|
||||
};
|
||||
|
||||
type TraceExplorerControlsProps = Pick<
|
||||
ControlsProps,
|
||||
'isLoading' | 'totalCount' | 'perPageOptions'
|
||||
> & {
|
||||
config?: OptionsMenuConfig | null;
|
||||
};
|
||||
|
||||
export default memo(TraceExplorerControls);
|
||||
|
@ -3,6 +3,7 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { QueryBuilder } from 'container/QueryBuilder';
|
||||
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { memo } from 'react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { ButtonWrapper, Container } from './styles';
|
||||
@ -10,7 +11,7 @@ import { ButtonWrapper, Container } from './styles';
|
||||
function QuerySection(): JSX.Element {
|
||||
const { handleRunQuery } = useQueryBuilder();
|
||||
|
||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.TIME_SERIES);
|
||||
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -32,4 +33,4 @@ function QuerySection(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export default QuerySection;
|
||||
export default memo(QuerySection);
|
||||
|
49
frontend/src/container/TracesExplorer/TracesView/configs.tsx
Normal file
49
frontend/src/container/TracesExplorer/TracesView/configs.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
|
||||
import { DEFAULT_PER_PAGE_OPTIONS } from 'hooks/queryPagination';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { ListItem } from 'types/api/widgets/getQuery';
|
||||
|
||||
export const PER_PAGE_OPTIONS: number[] = [10, ...DEFAULT_PER_PAGE_OPTIONS];
|
||||
|
||||
export const columns: ColumnsType<ListItem['data']> = [
|
||||
{
|
||||
title: 'Root Service Name',
|
||||
dataIndex: 'subQuery.serviceName',
|
||||
key: 'serviceName',
|
||||
},
|
||||
{
|
||||
title: 'Root Operation Name',
|
||||
dataIndex: 'subQuery.name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: 'Root Duration (in ms)',
|
||||
dataIndex: 'subQuery.durationNano',
|
||||
key: 'durationNano',
|
||||
render: (duration: number): JSX.Element => (
|
||||
<Typography>{getMs(String(duration))}ms</Typography>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'No of Spans',
|
||||
dataIndex: 'span_count',
|
||||
key: 'span_count',
|
||||
},
|
||||
{
|
||||
title: 'TraceID',
|
||||
dataIndex: 'traceID',
|
||||
key: 'traceID',
|
||||
render: (traceID: string): JSX.Element => (
|
||||
<Typography.Link
|
||||
href={generatePath(ROUTES.TRACE_DETAIL, {
|
||||
id: traceID,
|
||||
})}
|
||||
>
|
||||
{traceID}
|
||||
</Typography.Link>
|
||||
),
|
||||
},
|
||||
];
|
87
frontend/src/container/TracesExplorer/TracesView/index.tsx
Normal file
87
frontend/src/container/TracesExplorer/TracesView/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import Typography from 'antd/es/typography/Typography';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { Pagination, URL_PAGINATION } from 'hooks/queryPagination';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import TraceExplorerControls from '../Controls';
|
||||
import { columns, PER_PAGE_OPTIONS } from './configs';
|
||||
import { ActionsContainer, Container } from './styles';
|
||||
|
||||
function TracesView(): JSX.Element {
|
||||
const { stagedQuery, panelType } = useQueryBuilder();
|
||||
|
||||
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
||||
URL_PAGINATION,
|
||||
);
|
||||
|
||||
const { data, isLoading } = useGetQueryRange(
|
||||
{
|
||||
query: stagedQuery || initialQueriesMap.traces,
|
||||
graphType: panelType || PANEL_TYPES.TRACE,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
params: {
|
||||
dataSource: 'traces',
|
||||
},
|
||||
tableParams: {
|
||||
pagination: paginationQueryData,
|
||||
},
|
||||
},
|
||||
{
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
stagedQuery,
|
||||
panelType,
|
||||
paginationQueryData,
|
||||
],
|
||||
enabled: !!stagedQuery && panelType === PANEL_TYPES.TRACE,
|
||||
},
|
||||
);
|
||||
|
||||
const responseData = data?.payload?.data?.newResult?.data?.result[0]?.list;
|
||||
const tableData = useMemo(
|
||||
() => responseData?.map((listItem) => listItem.data),
|
||||
[responseData],
|
||||
);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<ActionsContainer>
|
||||
<Typography>
|
||||
Showing up to X of the slowest traces form the selected time range
|
||||
</Typography>
|
||||
<TraceExplorerControls
|
||||
isLoading={isLoading}
|
||||
totalCount={responseData?.length || 0}
|
||||
perPageOptions={PER_PAGE_OPTIONS}
|
||||
/>
|
||||
</ActionsContainer>
|
||||
<ResizeTable
|
||||
loading={isLoading}
|
||||
columns={columns}
|
||||
tableLayout="fixed"
|
||||
dataSource={tableData}
|
||||
scroll={{ x: true }}
|
||||
pagination={false}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default TracesView;
|
13
frontend/src/container/TracesExplorer/TracesView/styles.ts
Normal file
13
frontend/src/container/TracesExplorer/TracesView/styles.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
`;
|
||||
|
||||
export const ActionsContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`;
|
@ -122,6 +122,10 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
|
||||
[query.dataSource],
|
||||
);
|
||||
|
||||
const isTracePanelType = useMemo(() => panelType === PANEL_TYPES.TRACE, [
|
||||
panelType,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialDataSource && dataSource !== initialDataSource) return;
|
||||
|
||||
@ -142,6 +146,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
|
||||
}, [dataSource, aggregateOperator, getNewListOfAdditionalFilters]);
|
||||
|
||||
return {
|
||||
isTracePanelType,
|
||||
isMetricsDataSource,
|
||||
operators,
|
||||
listOfAdditionalFilters,
|
||||
|
@ -1,8 +1,3 @@
|
||||
import { Pagination } from './types';
|
||||
|
||||
export const URL_PAGINATION = 'pagination';
|
||||
|
||||
export const defaultPaginationConfig: Pagination = {
|
||||
offset: 0,
|
||||
limit: 25,
|
||||
};
|
||||
export const DEFAULT_PER_PAGE_OPTIONS: number[] = [25, 50, 100, 200];
|
||||
|
@ -1,4 +1,4 @@
|
||||
export interface Pagination {
|
||||
offset: number;
|
||||
limit: 25 | 50 | 100 | 200;
|
||||
limit: number;
|
||||
}
|
||||
|
@ -1,12 +1,23 @@
|
||||
import { ControlsProps } from 'container/Controls';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { defaultPaginationConfig, URL_PAGINATION } from './config';
|
||||
import { DEFAULT_PER_PAGE_OPTIONS, URL_PAGINATION } from './config';
|
||||
import { Pagination } from './types';
|
||||
import { checkIsValidPaginationData } from './utils';
|
||||
import {
|
||||
checkIsValidPaginationData,
|
||||
getDefaultPaginationConfig,
|
||||
} from './utils';
|
||||
|
||||
const useQueryPagination = (
|
||||
totalCount: number,
|
||||
perPageOptions: number[] = DEFAULT_PER_PAGE_OPTIONS,
|
||||
): UseQueryPagination => {
|
||||
const defaultPaginationConfig = useMemo(
|
||||
() => getDefaultPaginationConfig(perPageOptions),
|
||||
[perPageOptions],
|
||||
);
|
||||
|
||||
const useQueryPagination = (totalCount: number): UseQueryPagination => {
|
||||
const {
|
||||
query: paginationQuery,
|
||||
queryData: paginationQueryData,
|
||||
@ -45,12 +56,19 @@ const useQueryPagination = (totalCount: number): UseQueryPagination => {
|
||||
useEffect(() => {
|
||||
const isValidPaginationData = checkIsValidPaginationData(
|
||||
paginationQueryData || defaultPaginationConfig,
|
||||
perPageOptions,
|
||||
);
|
||||
|
||||
if (paginationQuery && isValidPaginationData) return;
|
||||
|
||||
redirectWithCurrentPagination(defaultPaginationConfig);
|
||||
}, [paginationQuery, paginationQueryData, redirectWithCurrentPagination]);
|
||||
}, [
|
||||
defaultPaginationConfig,
|
||||
perPageOptions,
|
||||
paginationQuery,
|
||||
paginationQueryData,
|
||||
redirectWithCurrentPagination,
|
||||
]);
|
||||
|
||||
return {
|
||||
pagination: paginationQueryData || defaultPaginationConfig,
|
||||
|
@ -1,12 +1,20 @@
|
||||
import { DEFAULT_PER_PAGE_OPTIONS } from './config';
|
||||
import { Pagination } from './types';
|
||||
|
||||
export const checkIsValidPaginationData = ({
|
||||
limit,
|
||||
offset,
|
||||
}: Pagination): boolean =>
|
||||
export const checkIsValidPaginationData = (
|
||||
{ limit, offset }: Pagination,
|
||||
perPageOptions: number[],
|
||||
): boolean =>
|
||||
Boolean(
|
||||
limit &&
|
||||
(limit === 25 || limit === 50 || limit === 100 || limit === 200) &&
|
||||
offset &&
|
||||
offset > 0,
|
||||
Number.isInteger(limit) &&
|
||||
limit > 0 &&
|
||||
offset >= 0 &&
|
||||
perPageOptions.find((option) => option === limit),
|
||||
);
|
||||
|
||||
export const getDefaultPaginationConfig = (
|
||||
perPageOptions = DEFAULT_PER_PAGE_OPTIONS,
|
||||
): Pagination => ({
|
||||
offset: 0,
|
||||
limit: perPageOptions[0],
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ const useUrlQueryData = <T>(
|
||||
const location = useLocation();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const query = urlQuery.get(queryKey);
|
||||
const query = useMemo(() => urlQuery.get(queryKey), [queryKey, urlQuery]);
|
||||
|
||||
const queryData: T = useMemo(() => (query ? JSON.parse(query) : defaultData), [
|
||||
query,
|
||||
|
@ -15,12 +15,16 @@ export const getOperatorsBySourceAndPanelType = ({
|
||||
}: GetQueryOperatorsParams): SelectOption<string, string>[] => {
|
||||
let operatorsByDataSource = mapOfOperators[dataSource];
|
||||
|
||||
if (panelType === PANEL_TYPES.LIST) {
|
||||
if (panelType === PANEL_TYPES.LIST || panelType === PANEL_TYPES.TRACE) {
|
||||
operatorsByDataSource = operatorsByDataSource.filter(
|
||||
(operator) => operator.value === StringOperators.NOOP,
|
||||
);
|
||||
}
|
||||
if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) {
|
||||
if (
|
||||
dataSource !== DataSource.METRICS &&
|
||||
panelType !== PANEL_TYPES.LIST &&
|
||||
panelType !== PANEL_TYPES.TRACE
|
||||
) {
|
||||
operatorsByDataSource = operatorsByDataSource.filter(
|
||||
(operator) => operator.value !== StringOperators.NOOP,
|
||||
);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { GetQueryResultsProps } from 'store/actions/dashboard/getQueryResults';
|
||||
import {
|
||||
MapData,
|
||||
MapQueryDataToApiResult,
|
||||
@ -6,6 +7,7 @@ import {
|
||||
export const mapQueryDataToApi = <Data extends MapData, Key extends keyof Data>(
|
||||
data: Data[],
|
||||
nameField: Key,
|
||||
tableParams?: GetQueryResultsProps['tableParams'],
|
||||
): MapQueryDataToApiResult<Record<string, Data>> => {
|
||||
const newLegendMap: Record<string, string> = {};
|
||||
|
||||
@ -14,6 +16,10 @@ export const mapQueryDataToApi = <Data extends MapData, Key extends keyof Data>(
|
||||
...acc,
|
||||
[query[nameField] as string]: {
|
||||
...query,
|
||||
...tableParams?.pagination,
|
||||
...(tableParams?.selectColumns
|
||||
? { selectColumns: tableParams?.selectColumns }
|
||||
: null),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
|
||||
import { toCapitalize } from 'lib/toCapitalize';
|
||||
import { ReactNode } from 'react';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery';
|
||||
import { ListItem, QueryDataV3, SeriesItem } from 'types/api/widgets/getQuery';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
type CreateTableDataFromQueryParams = Pick<
|
||||
@ -47,6 +47,10 @@ type GetDynamicColumns = (
|
||||
query: Query,
|
||||
) => DynamicColumns;
|
||||
|
||||
type ListItemData = ListItem['data'];
|
||||
type ListItemKey = keyof ListItemData;
|
||||
type SeriesItemLabels = SeriesItem['labels'];
|
||||
|
||||
const isFormula = (queryName: string): boolean =>
|
||||
FORMULA_REGEXP.test(queryName);
|
||||
|
||||
@ -72,57 +76,77 @@ const prepareColumnTitle = (title: string): string => {
|
||||
return toCapitalize(title);
|
||||
};
|
||||
|
||||
const createLabels = <T extends ListItemData | SeriesItemLabels>(
|
||||
labels: T,
|
||||
label: keyof T,
|
||||
dynamicColumns: DynamicColumns,
|
||||
): void => {
|
||||
if (isColumnExist(label as string, dynamicColumns)) return;
|
||||
if (isFormula(label as string)) return;
|
||||
|
||||
const labelValue = labels[label];
|
||||
|
||||
const isNumber = !Number.isNaN(parseFloat(String(labelValue)));
|
||||
|
||||
const fieldObj: DynamicColumn = {
|
||||
key: label as string,
|
||||
data: [],
|
||||
type: 'field',
|
||||
sortable: isNumber,
|
||||
};
|
||||
|
||||
dynamicColumns.push(fieldObj);
|
||||
};
|
||||
|
||||
const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => {
|
||||
const dynamicColumns: DynamicColumns = [];
|
||||
|
||||
queryTableData.forEach((currentQuery) => {
|
||||
if (!currentQuery.series) return;
|
||||
|
||||
if (!isColumnExist('timestamp', dynamicColumns)) {
|
||||
dynamicColumns.push({
|
||||
key: 'timestamp',
|
||||
data: [],
|
||||
type: 'field',
|
||||
sortable: true,
|
||||
if (currentQuery.list) {
|
||||
currentQuery.list.forEach((listItem) => {
|
||||
Object.keys(listItem.data).forEach((label) => {
|
||||
createLabels<ListItemData>(
|
||||
listItem.data,
|
||||
label as ListItemKey,
|
||||
dynamicColumns,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
currentQuery.series.forEach((seria) => {
|
||||
Object.keys(seria.labels).forEach((label) => {
|
||||
if (isColumnExist(label, dynamicColumns)) return;
|
||||
if (isFormula(label)) return;
|
||||
|
||||
const labelValue = seria.labels[label];
|
||||
|
||||
const isNumber = !Number.isNaN(parseFloat(labelValue));
|
||||
|
||||
const fieldObj: DynamicColumn = {
|
||||
key: label,
|
||||
if (currentQuery.series) {
|
||||
if (!isColumnExist('timestamp', dynamicColumns)) {
|
||||
dynamicColumns.push({
|
||||
key: 'timestamp',
|
||||
data: [],
|
||||
type: 'field',
|
||||
sortable: isNumber,
|
||||
};
|
||||
sortable: true,
|
||||
});
|
||||
}
|
||||
|
||||
dynamicColumns.push(fieldObj);
|
||||
currentQuery.series.forEach((seria) => {
|
||||
Object.keys(seria.labels).forEach((label) => {
|
||||
createLabels<SeriesItemLabels>(seria.labels, label, dynamicColumns);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (!isFormula(currentQuery.queryName)) {
|
||||
const builderQuery = query.builder.queryData.find(
|
||||
(q) => q.queryName === currentQuery.queryName,
|
||||
);
|
||||
if (!isFormula(currentQuery.queryName)) {
|
||||
const builderQuery = query.builder.queryData.find(
|
||||
(q) => q.queryName === currentQuery.queryName,
|
||||
);
|
||||
|
||||
const operator = builderQuery ? builderQuery.aggregateOperator : '';
|
||||
const operator = builderQuery ? builderQuery.aggregateOperator : '';
|
||||
|
||||
if (isColumnExist(operator, dynamicColumns)) return;
|
||||
if (isColumnExist(operator, dynamicColumns)) return;
|
||||
|
||||
const operatorColumn: DynamicColumn = {
|
||||
key: operator,
|
||||
data: [],
|
||||
type: 'operator',
|
||||
sortable: true,
|
||||
};
|
||||
dynamicColumns.push(operatorColumn);
|
||||
const operatorColumn: DynamicColumn = {
|
||||
key: operator,
|
||||
data: [],
|
||||
type: 'operator',
|
||||
sortable: true,
|
||||
};
|
||||
dynamicColumns.push(operatorColumn);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -194,22 +218,47 @@ const fillDataFromSeria = (
|
||||
});
|
||||
};
|
||||
|
||||
const fillDataFromList = (
|
||||
listItem: ListItem,
|
||||
columns: DynamicColumns,
|
||||
): void => {
|
||||
columns.forEach((column) => {
|
||||
if (isFormula(column.key as string)) return;
|
||||
|
||||
Object.keys(listItem.data).forEach((label) => {
|
||||
if (column.key === label) {
|
||||
if (listItem.data[label as ListItemKey]) {
|
||||
column.data.push(listItem.data[label as ListItemKey] as string | number);
|
||||
} else {
|
||||
column.data.push('N/A');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const fillColumnsData: FillColumnData = (queryTableData, cols, query) => {
|
||||
const fields = cols.filter((item) => item.type === 'field');
|
||||
const operators = cols.filter((item) => item.type === 'operator');
|
||||
const resultColumns = [...fields, ...operators];
|
||||
|
||||
queryTableData.forEach((currentQuery) => {
|
||||
if (!currentQuery.series) return;
|
||||
|
||||
const currentOperator = getQueryOperator(
|
||||
query.builder.queryData,
|
||||
currentQuery.queryName,
|
||||
);
|
||||
|
||||
currentQuery.series.forEach((seria) => {
|
||||
fillDataFromSeria(seria, resultColumns, currentOperator);
|
||||
});
|
||||
if (currentQuery.series) {
|
||||
currentQuery.series.forEach((seria) => {
|
||||
fillDataFromSeria(seria, resultColumns, currentOperator);
|
||||
});
|
||||
}
|
||||
|
||||
if (currentQuery.list) {
|
||||
currentQuery.list.forEach((listItem) => {
|
||||
fillDataFromList(listItem, resultColumns);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const rowsLength = resultColumns.length > 0 ? resultColumns[0].data.length : 0;
|
||||
|
@ -16,7 +16,7 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
@ -34,19 +34,38 @@ function TracesExplorer(): JSX.Element {
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const tabsItems = getTabsItems();
|
||||
const currentTab = panelType || PANEL_TYPES.TIME_SERIES;
|
||||
const currentTab = panelType || PANEL_TYPES.LIST;
|
||||
|
||||
const isMultipleQueries = useMemo(
|
||||
() =>
|
||||
currentQuery.builder.queryData.length > 1 ||
|
||||
currentQuery.builder.queryFormulas.length > 0,
|
||||
[currentQuery],
|
||||
);
|
||||
|
||||
const defaultQuery = useMemo(
|
||||
() =>
|
||||
updateAllQueriesOperators(
|
||||
initialQueriesMap.traces,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
PANEL_TYPES.LIST,
|
||||
DataSource.TRACES,
|
||||
),
|
||||
[updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
const isGroupByExist = useMemo(() => {
|
||||
const groupByCount: number = currentQuery.builder.queryData.reduce<number>(
|
||||
(acc, query) => acc + query.groupBy.length,
|
||||
0,
|
||||
);
|
||||
|
||||
return groupByCount > 0;
|
||||
}, [currentQuery]);
|
||||
|
||||
const tabsItems = getTabsItems({
|
||||
isListViewDisabled: isMultipleQueries || isGroupByExist,
|
||||
});
|
||||
|
||||
const exportDefaultQuery = useMemo(
|
||||
() =>
|
||||
updateAllQueriesOperators(
|
||||
@ -114,6 +133,17 @@ function TracesExplorer(): JSX.Element {
|
||||
|
||||
useShareBuilderUrl(defaultQuery);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldChangeView = isMultipleQueries || isGroupByExist;
|
||||
|
||||
if (
|
||||
(currentTab === PANEL_TYPES.LIST || currentTab === PANEL_TYPES.TRACE) &&
|
||||
shouldChangeView
|
||||
) {
|
||||
handleTabChange(PANEL_TYPES.TIME_SERIES);
|
||||
}
|
||||
}, [currentTab, isMultipleQueries, isGroupByExist, handleTabChange]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<QuerySection />
|
||||
|
@ -1,17 +1,25 @@
|
||||
import { TabsProps } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import TimeSeriesView from 'container/TimeSeriesView';
|
||||
import TracesView from 'container/TracesExplorer/TracesView';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const getTabsItems = (): TabsProps['items'] => [
|
||||
interface GetTabsItemsProps {
|
||||
isListViewDisabled: boolean;
|
||||
}
|
||||
|
||||
export const getTabsItems = ({
|
||||
isListViewDisabled,
|
||||
}: GetTabsItemsProps): TabsProps['items'] => [
|
||||
{
|
||||
label: 'Traces',
|
||||
key: PANEL_TYPES.TRACE,
|
||||
children: <TracesView />,
|
||||
disabled: isListViewDisabled,
|
||||
},
|
||||
{
|
||||
label: 'Time Series',
|
||||
key: PANEL_TYPES.TIME_SERIES,
|
||||
children: <TimeSeriesView dataSource={DataSource.TRACES} />,
|
||||
},
|
||||
{
|
||||
label: 'Traces',
|
||||
key: PANEL_TYPES.TRACE,
|
||||
children: <div>Traces tab</div>,
|
||||
},
|
||||
];
|
||||
|
@ -16,12 +16,14 @@ import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { Pagination } from 'hooks/queryPagination';
|
||||
|
||||
export async function GetMetricQueryRange({
|
||||
query,
|
||||
globalSelectedInterval,
|
||||
graphType,
|
||||
selectedTime,
|
||||
tableParams,
|
||||
variables = {},
|
||||
params = {},
|
||||
}: GetQueryResultsProps): Promise<SuccessResponse<MetricRangePayloadProps>> {
|
||||
@ -38,8 +40,9 @@ export async function GetMetricQueryRange({
|
||||
switch (query.queryType) {
|
||||
case EQueryType.QUERY_BUILDER: {
|
||||
const { queryData: data, queryFormulas } = query.builder;
|
||||
const currentQueryData = mapQueryDataToApi(data, 'queryName');
|
||||
const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams);
|
||||
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
|
||||
|
||||
const builderQueries = {
|
||||
...currentQueryData.data,
|
||||
...currentFormulas.data,
|
||||
@ -140,4 +143,8 @@ export interface GetQueryResultsProps {
|
||||
globalSelectedInterval: Time;
|
||||
variables?: Record<string, unknown>;
|
||||
params?: Record<string, unknown>;
|
||||
tableParams?: {
|
||||
pagination?: Pagination;
|
||||
selectColumns?: any;
|
||||
};
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export interface PayloadProps {
|
||||
result: QueryData[];
|
||||
}
|
||||
|
||||
type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
|
||||
export type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
|
||||
|
||||
export interface QueryData {
|
||||
metric: {
|
||||
|
@ -18,6 +18,7 @@ export type HandleChangeQueryData = <
|
||||
export type UseQueryOperations = (
|
||||
params: UseQueryOperationsParams,
|
||||
) => {
|
||||
isTracePanelType: boolean;
|
||||
isMetricsDataSource: boolean;
|
||||
operators: SelectOption<string, string>[];
|
||||
listOfAdditionalFilters: string[];
|
||||
|
@ -143,8 +143,8 @@ export type PanelTypeKeys =
|
||||
| 'VALUE'
|
||||
| 'TABLE'
|
||||
| 'LIST'
|
||||
| 'EMPTY_WIDGET'
|
||||
| 'TRACE';
|
||||
| 'TRACE'
|
||||
| 'EMPTY_WIDGET';
|
||||
|
||||
export type ReduceOperators = 'last' | 'sum' | 'avg' | 'max' | 'min';
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user