Merge pull request #3179 from SigNoz/release/v0.24.0

Release/v0.24.0
This commit is contained in:
Prashant Shahi 2023-07-20 12:50:48 +05:30 committed by GitHub
commit ef0e63c35b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 2316 additions and 1269 deletions

View File

@ -137,7 +137,7 @@ services:
condition: on-failure condition: on-failure
query-service: query-service:
image: signoz/query-service:0.23.1 image: signoz/query-service:0.24.0
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports: # ports:
# - "6060:6060" # pprof port # - "6060:6060" # pprof port
@ -166,7 +166,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
frontend: frontend:
image: signoz/frontend:0.23.1 image: signoz/frontend:0.24.0
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure
@ -179,7 +179,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -208,7 +208,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -41,7 +41,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector: otel-collector:
container_name: otel-collector container_name: otel-collector
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
# user: root # required for reading docker container logs # user: root # required for reading docker container logs
volumes: volumes:
@ -67,7 +67,7 @@ services:
otel-collector-metrics: otel-collector-metrics:
container_name: otel-collector-metrics container_name: otel-collector-metrics
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -153,7 +153,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service: query-service:
image: signoz/query-service:${DOCKER_TAG:-0.23.1} image: signoz/query-service:${DOCKER_TAG:-0.24.0}
container_name: query-service container_name: query-service
command: ["-config=/root/config/prometheus.yml"] command: ["-config=/root/config/prometheus.yml"]
# ports: # ports:
@ -181,7 +181,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
frontend: frontend:
image: signoz/frontend:${DOCKER_TAG:-0.23.1} image: signoz/frontend:${DOCKER_TAG:-0.24.0}
container_name: frontend container_name: frontend
restart: on-failure restart: on-failure
depends_on: depends_on:
@ -193,7 +193,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.3} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.4}
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -219,7 +219,7 @@ services:
<<: *clickhouse-depend <<: *clickhouse-depend
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.3} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.4}
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml

View File

@ -69,6 +69,7 @@
"papaparse": "5.4.1", "papaparse": "5.4.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-force-graph": "^1.41.0", "react-force-graph": "^1.41.0",
"react-grid-layout": "^1.3.4", "react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.1", "react-i18next": "^11.16.1",

View File

@ -9,9 +9,9 @@ const loginPrecheck = async (
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => { ): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try { try {
const response = await axios.get( const response = await axios.get(
`/loginPrecheck?email=${props.email}&ref=${encodeURIComponent( `/loginPrecheck?email=${encodeURIComponent(
window.location.href, props.email,
)}`, )}&ref=${encodeURIComponent(window.location.href)}`,
); );
return { return {

View File

@ -288,12 +288,11 @@ function Graph({
if (chartHasData) { if (chartHasData) {
chartPlugins.push(createIntersectionCursorPlugin()); chartPlugins.push(createIntersectionCursorPlugin());
chartPlugins.push(createDragSelectPlugin()); chartPlugins.push(createDragSelectPlugin());
chartPlugins.push(legend(name, data.datasets.length > 3));
} else { } else {
chartPlugins.push(emptyGraph); chartPlugins.push(emptyGraph);
} }
chartPlugins.push(legend(name, data.datasets.length > 3));
lineChartRef.current = new Chart(chartRef.current, { lineChartRef.current = new Chart(chartRef.current, {
type, type,
data, data,

View File

@ -1,6 +1,8 @@
/* eslint-disable react/jsx-props-no-spreading */
import { Table } from 'antd'; import { Table } from 'antd';
import type { TableProps } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import { dragColumnParams } from 'hooks/useDragColumns/configs';
import { import {
SyntheticEvent, SyntheticEvent,
useCallback, useCallback,
@ -8,12 +10,18 @@ import {
useMemo, useMemo,
useState, useState,
} from 'react'; } from 'react';
import ReactDragListView from 'react-drag-listview';
import { ResizeCallbackData } from 'react-resizable'; import { ResizeCallbackData } from 'react-resizable';
import ResizableHeader from './ResizableHeader'; import ResizableHeader from './ResizableHeader';
import { DragSpanStyle } from './styles';
import { ResizeTableProps } from './types';
// eslint-disable-next-line @typescript-eslint/no-explicit-any function ResizeTable({
function ResizeTable({ columns, ...restprops }: TableProps<any>): JSX.Element { columns,
onDragColumn,
...restProps
}: ResizeTableProps): JSX.Element {
const [columnsData, setColumns] = useState<ColumnsType>([]); const [columnsData, setColumns] = useState<ColumnsType>([]);
const handleResize = useCallback( const handleResize = useCallback(
@ -31,16 +39,32 @@ function ResizeTable({ columns, ...restprops }: TableProps<any>): JSX.Element {
[columnsData], [columnsData],
); );
const mergeColumns = useMemo( const mergedColumns = useMemo(
() => () =>
columnsData.map((col, index) => ({ columnsData.map((col, index) => ({
...col, ...col,
...(onDragColumn && {
title: (
<DragSpanStyle className="dragHandler">
{col?.title?.toString() || ''}
</DragSpanStyle>
),
}),
onHeaderCell: (column: ColumnsType<unknown>[number]): unknown => ({ onHeaderCell: (column: ColumnsType<unknown>[number]): unknown => ({
width: column.width, width: column.width,
onResize: handleResize(index), onResize: handleResize(index),
}), }),
})), })) as ColumnsType<any>,
[columnsData, handleResize], [columnsData, onDragColumn, handleResize],
);
const tableParams = useMemo(
() => ({
...restProps,
components: { header: { cell: ResizableHeader } },
columns: mergedColumns,
}),
[mergedColumns, restProps],
); );
useEffect(() => { useEffect(() => {
@ -49,15 +73,17 @@ function ResizeTable({ columns, ...restprops }: TableProps<any>): JSX.Element {
} }
}, [columns]); }, [columns]);
return ( return onDragColumn ? (
<Table <ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}>
// eslint-disable-next-line react/jsx-props-no-spreading <Table {...tableParams} />
{...restprops} </ReactDragListView.DragColumn>
components={{ header: { cell: ResizableHeader } }} ) : (
// eslint-disable-next-line @typescript-eslint/no-explicit-any <Table {...tableParams} />
columns={mergeColumns as ColumnsType<any>}
/>
); );
} }
ResizeTable.defaultProps = {
onDragColumn: undefined,
};
export default ResizeTable; export default ResizeTable;

View File

@ -2,10 +2,16 @@ import styled from 'styled-components';
export const SpanStyle = styled.span` export const SpanStyle = styled.span`
position: absolute; position: absolute;
right: -5px; right: -0.313rem;
bottom: 0; bottom: 0;
z-index: 1; z-index: 1;
width: 10px; width: 0.625rem;
height: 100%; height: 100%;
cursor: col-resize; cursor: col-resize;
`; `;
export const DragSpanStyle = styled.span`
display: flex;
margin: -1rem;
padding: 1rem;
`;

View File

@ -0,0 +1,6 @@
import { TableProps } from 'antd';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export interface ResizeTableProps extends TableProps<any> {
onDragColumn?: (fromIndex: number, toIndex: number) => void;
}

View File

@ -8,4 +8,5 @@ export enum FeatureKeys {
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS', QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS', QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
DISABLE_UPSELL = 'DISABLE_UPSELL', DISABLE_UPSELL = 'DISABLE_UPSELL',
USE_SPAN_METRICS = 'USE_SPAN_METRICS',
} }

View File

@ -8,4 +8,6 @@ export enum LOCALSTORAGE {
LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW', LOGS_LINES_PER_ROW = 'LOGS_LINES_PER_ROW',
LOGS_LIST_OPTIONS = 'LOGS_LIST_OPTIONS', LOGS_LIST_OPTIONS = 'LOGS_LIST_OPTIONS',
TRACES_LIST_OPTIONS = 'TRACES_LIST_OPTIONS', TRACES_LIST_OPTIONS = 'TRACES_LIST_OPTIONS',
TRACES_LIST_COLUMNS = 'TRACES_LIST_COLUMNS',
LOGS_LIST_COLUMNS = 'LOGS_LIST_COLUMNS',
} }

View File

@ -0,0 +1,72 @@
import { Select, Spin } from 'antd';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import { useOrderByFilter } from 'container/QueryBuilder/filters/OrderByFilter/useOrderByFilter';
import { selectStyle } from 'container/QueryBuilder/filters/QueryBuilderSearch/config';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import { memo, useMemo } from 'react';
import { StringOperators } from 'types/common/queryBuilder';
function ExplorerOrderBy({ query, onChange }: OrderByFilterProps): JSX.Element {
const {
debouncedSearchText,
selectedValue,
aggregationOptions,
generateOptions,
createOptions,
handleChange,
handleSearchKeys,
} = useOrderByFilter({ query, onChange });
const { data, isFetching } = useGetAggregateKeys(
{
aggregateAttribute: query.aggregateAttribute.key,
dataSource: query.dataSource,
aggregateOperator: query.aggregateOperator,
searchText: debouncedSearchText,
},
{
keepPreviousData: true,
},
);
const options = useMemo(() => {
const keysOptions = createOptions(data?.payload?.attributeKeys || []);
const customOptions = createOptions([
{ key: 'timestamp', isColumn: true, type: null, dataType: null },
]);
const baseOptions = [
...customOptions,
...(query.aggregateOperator === StringOperators.NOOP
? []
: aggregationOptions),
...keysOptions,
];
return generateOptions(baseOptions);
}, [
aggregationOptions,
createOptions,
data?.payload?.attributeKeys,
generateOptions,
query.aggregateOperator,
]);
return (
<Select
mode="tags"
style={selectStyle}
onSearch={handleSearchKeys}
showSearch
showArrow={false}
value={selectedValue}
labelInValue
options={options}
notFoundContent={isFetching ? <Spin size="small" /> : null}
onChange={handleChange}
/>
);
}
export default memo(ExplorerOrderBy);

View File

@ -1,137 +0,0 @@
import { Button } from 'antd';
import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown';
import GridGraphComponent from 'container/GridGraphComponent';
import {
timeItems,
timePreferance,
} from 'container/NewWidget/RightContainer/timeItems';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import getChartData from 'lib/getChartData';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
import { TimeContainer } from './styles';
function FullView({
widget,
fullViewOptions = true,
onClickHandler,
name,
yAxisUnit,
onDragSelect,
isDependedDataLoaded = false,
}: FullViewProps): JSX.Element {
const { selectedTime: globalSelectedTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const getSelectedTime = useCallback(
() =>
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
[widget],
);
const [selectedTime, setSelectedTime] = useState<timePreferance>({
name: getSelectedTime()?.name || '',
enum: widget?.timePreferance || 'GLOBAL_TIME',
});
const queryKey = useMemo(
() =>
`FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
[selectedTime, globalSelectedTime, widget],
);
const updatedQuery = useStepInterval(widget?.query);
const response = useGetQueryRange(
{
selectedTime: selectedTime.enum,
graphType: widget.panelTypes,
query: updatedQuery,
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(),
},
{
queryKey,
enabled: !isDependedDataLoaded,
},
);
const chartDataSet = useMemo(
() =>
getChartData({
queryData: [
{
queryData: response?.data?.payload?.data?.result || [],
},
],
}),
[response],
);
if (response.status === 'idle' || response.status === 'loading') {
return <Spinner height="100%" size="large" tip="Loading..." />;
}
return (
<>
{fullViewOptions && (
<TimeContainer>
<TimePreference
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
/>
<Button
onClick={(): void => {
response.refetch();
}}
type="primary"
>
Refresh
</Button>
</TimeContainer>
)}
<GridGraphComponent
GRAPH_TYPES={widget.panelTypes}
data={chartDataSet}
isStacked={widget.isStacked}
opacity={widget.opacity}
title={widget.title}
onClickHandler={onClickHandler}
name={name}
yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect}
/>
</>
);
}
interface FullViewProps {
widget: Widgets;
fullViewOptions?: boolean;
onClickHandler?: GraphOnClickHandler;
name: string;
yAxisUnit?: string;
onDragSelect?: (start: number, end: number) => void;
isDependedDataLoaded?: boolean;
}
FullView.defaultProps = {
fullViewOptions: undefined,
onClickHandler: undefined,
yAxisUnit: undefined,
onDragSelect: undefined,
isDependedDataLoaded: undefined,
};
export default FullView;

View File

@ -1,5 +1,4 @@
import { Button, Typography } from 'antd'; import { Button } from 'antd';
import getQueryResult from 'api/widgets/getQuery';
import { GraphOnClickHandler } from 'components/Graph'; import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown'; import TimePreference from 'components/TimePreferenceDropDown';
@ -7,22 +6,18 @@ import GridGraphComponent from 'container/GridGraphComponent';
import { import {
timeItems, timeItems,
timePreferance, timePreferance,
timePreferenceType,
} from 'container/NewWidget/RightContainer/timeItems'; } from 'container/NewWidget/RightContainer/timeItems';
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import getChartData from 'lib/getChartData'; import getChartData from 'lib/getChartData';
import GetMaxMinTime from 'lib/getMaxMinTime';
import GetMinMax from 'lib/getMinMax';
import getStartAndEndTime from 'lib/getStartAndEndTime';
import getStep from 'lib/getStep';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { PromQLWidgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { NotFoundContainer, TimeContainer } from './styles'; import { TimeContainer } from './styles';
function FullView({ function FullView({
widget, widget,
@ -31,8 +26,9 @@ function FullView({
name, name,
yAxisUnit, yAxisUnit,
onDragSelect, onDragSelect,
isDependedDataLoaded = false,
}: FullViewProps): JSX.Element { }: FullViewProps): JSX.Element {
const { minTime, maxTime, selectedTime: globalSelectedTime } = useSelector< const { selectedTime: globalSelectedTime } = useSelector<
AppState, AppState,
GlobalReducer GlobalReducer
>((state) => state.globalTime); >((state) => state.globalTime);
@ -48,110 +44,55 @@ function FullView({
enum: widget?.timePreferance || 'GLOBAL_TIME', enum: widget?.timePreferance || 'GLOBAL_TIME',
}); });
const maxMinTime = GetMaxMinTime({ const queryKey = useMemo(
graphType: widget.panelTypes, () =>
maxTime, `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
minTime, [selectedTime, globalSelectedTime, widget],
});
const getMinMax = (
time: timePreferenceType,
): { min: string | number; max: string | number } => {
if (time === 'GLOBAL_TIME') {
const minMax = GetMinMax(globalSelectedTime, [
minTime / 1000000,
maxTime / 1000000,
]);
return {
min: convertToNanoSecondsToSecond(minMax.minTime / 1000),
max: convertToNanoSecondsToSecond(minMax.maxTime / 1000),
};
}
const minMax = getStartAndEndTime({
type: selectedTime.enum,
maxTime: maxMinTime.maxTime,
minTime: maxMinTime.minTime,
});
return { min: parseInt(minMax.start, 10), max: parseInt(minMax.end, 10) };
};
const queryMinMax = getMinMax(selectedTime.enum);
const queryLength = widget.query.filter((e) => e.query.length !== 0);
const response = useQueries(
queryLength.map((query) => ({
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
queryFn: () =>
getQueryResult({
end: queryMinMax.max.toString(),
query: query.query,
start: queryMinMax.min.toString(),
step: `${getStep({
start: queryMinMax.min,
end: queryMinMax.max,
inputFormat: 's',
})}`,
}),
queryHash: `${query.query}-${query.legend}-${selectedTime.enum}`,
retryOnMount: false,
})),
); );
const isError = const updatedQuery = useStepInterval(widget?.query);
response.find((e) => e?.data?.statusCode !== 200) !== undefined ||
response.some((e) => e.isError === true);
const isLoading = response.some((e) => e.isLoading === true); const response = useGetQueryRange(
{
const errorMessage = response.find((e) => e.data?.error !== null)?.data?.error; selectedTime: selectedTime.enum,
graphType: widget.panelTypes,
const data = response.map((responseOfQuery) => query: updatedQuery,
responseOfQuery?.data?.payload?.result.map((e, index) => ({ globalSelectedInterval: globalSelectedTime,
query: queryLength[index]?.query, variables: getDashboardVariables(),
queryData: e, },
legend: queryLength[index]?.legend, {
})), queryKey,
enabled: !isDependedDataLoaded,
},
); );
const chartDataSet = useMemo( const chartDataSet = useMemo(
() => () =>
getChartData({ getChartData({
queryData: data.map((e) => ({ queryData: [
query: e?.map((e) => e.query).join(' ') || '', {
queryData: e?.map((e) => e.queryData) || [], queryData: response?.data?.payload?.data?.result || [],
legend: e?.map((e) => e.legend).join('') || '', },
})), ],
}), }),
[data], [response],
); );
if (isLoading) { if (response.status === 'idle' || response.status === 'loading') {
return <Spinner height="100%" size="large" tip="Loading..." />; return <Spinner height="100%" size="large" tip="Loading..." />;
} }
if (isError || data === undefined || data[0] === undefined) {
return (
<NotFoundContainer>
<Typography>{errorMessage}</Typography>
</NotFoundContainer>
);
}
return ( return (
<> <>
{fullViewOptions && ( {fullViewOptions && (
<TimeContainer> <TimeContainer>
<TimePreference <TimePreference
{...{ selectedTime={selectedTime}
selectedTime, setSelectedTime={setSelectedTime}
setSelectedTime,
}}
/> />
<Button <Button
onClick={(): void => { onClick={(): void => {
response.forEach((e) => e.refetch()); response.refetch();
}} }}
type="primary" type="primary"
> >
@ -176,12 +117,13 @@ function FullView({
} }
interface FullViewProps { interface FullViewProps {
widget: PromQLWidgets; widget: Widgets;
fullViewOptions?: boolean; fullViewOptions?: boolean;
onClickHandler?: GraphOnClickHandler; onClickHandler?: GraphOnClickHandler;
name: string; name: string;
yAxisUnit?: string; yAxisUnit?: string;
onDragSelect?: (start: number, end: number) => void; onDragSelect?: (start: number, end: number) => void;
isDependedDataLoaded?: boolean;
} }
FullView.defaultProps = { FullView.defaultProps = {
@ -189,6 +131,7 @@ FullView.defaultProps = {
onClickHandler: undefined, onClickHandler: undefined,
yAxisUnit: undefined, yAxisUnit: undefined,
onDragSelect: undefined, onDragSelect: undefined,
isDependedDataLoaded: undefined,
}; };
export default FullView; export default FullView;

View File

@ -1,5 +1,6 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import { ChartData } from 'chart.js'; import { ChartData } from 'chart.js';
import { GraphOnClickHandler } from 'components/Graph';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import GridGraphComponent from 'container/GridGraphComponent'; import GridGraphComponent from 'container/GridGraphComponent';
import { UpdateDashboard } from 'container/GridGraphLayout/utils'; import { UpdateDashboard } from 'container/GridGraphLayout/utils';
@ -35,12 +36,16 @@ import { Widgets } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards'; import DashboardReducer from 'types/reducer/dashboards';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import {
getSelectedDashboard,
getSelectedDashboardVariable,
} from 'utils/dashboard/selectedDashboard';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { LayoutProps } from '..'; import { LayoutProps } from '..';
import EmptyWidget from '../EmptyWidget'; import EmptyWidget from '../EmptyWidget';
import WidgetHeader from '../WidgetHeader'; import WidgetHeader from '../WidgetHeader';
import FullView from './FullView/index.metricsBuilder'; import FullView from './FullView';
import { FullViewContainer, Modal } from './styles'; import { FullViewContainer, Modal } from './styles';
function GridCardGraph({ function GridCardGraph({
@ -51,6 +56,10 @@ function GridCardGraph({
layout = [], layout = [],
setLayout, setLayout,
onDragSelect, onDragSelect,
onClickHandler,
allowDelete,
allowClone,
allowEdit,
}: GridCardGraphProps): JSX.Element { }: GridCardGraphProps): JSX.Element {
const { ref: graphRef, inView: isGraphVisible } = useInView({ const { ref: graphRef, inView: isGraphVisible } = useInView({
threshold: 0, threshold: 0,
@ -77,9 +86,9 @@ function GridCardGraph({
const { dashboards } = useSelector<AppState, DashboardReducer>( const { dashboards } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards, (state) => state.dashboards,
); );
const [selectedDashboard] = dashboards;
const selectedData = selectedDashboard?.data; const selectedDashboard = getSelectedDashboard(dashboards);
const { variables } = selectedData; const variables = getSelectedDashboardVariable(dashboards);
const updatedQuery = useStepInterval(widget?.query); const updatedQuery = useStepInterval(widget?.query);
@ -172,10 +181,10 @@ function GridCardGraph({
h: 2, h: 2,
y: 0, y: 0,
}, },
...(selectedDashboard.data.layout || []), ...(selectedDashboard?.data.layout || []),
]; ];
if (widget) { if (widget && selectedDashboard) {
await UpdateDashboard( await UpdateDashboard(
{ {
data: selectedDashboard.data, data: selectedDashboard.data,
@ -257,6 +266,9 @@ function GridCardGraph({
onClone={onCloneHandler} onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
allowClone={allowClone}
allowDelete={allowDelete}
allowEdit={allowEdit}
/> />
</div> </div>
<GridGraphComponent <GridGraphComponent
@ -267,6 +279,7 @@ function GridCardGraph({
title={' '} title={' '}
name={name} name={name}
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
onClickHandler={onClickHandler}
/> />
</> </>
)} )}
@ -289,6 +302,9 @@ function GridCardGraph({
onClone={onCloneHandler} onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
allowClone={allowClone}
allowDelete={allowDelete}
allowEdit={allowEdit}
/> />
</div> </div>
<GridGraphComponent <GridGraphComponent
@ -299,6 +315,7 @@ function GridCardGraph({
title={' '} title={' '}
name={name} name={name}
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
onClickHandler={onClickHandler}
/> />
</> </>
) : ( ) : (
@ -335,6 +352,9 @@ function GridCardGraph({
onClone={onCloneHandler} onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
allowClone={allowClone}
allowDelete={allowDelete}
allowEdit={allowEdit}
/> />
</div> </div>
)} )}
@ -351,6 +371,7 @@ function GridCardGraph({
name={name} name={name}
yAxisUnit={yAxisUnit} yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
onClickHandler={onClickHandler}
/> />
)} )}
@ -374,10 +395,18 @@ interface GridCardGraphProps extends DispatchProps {
// eslint-disable-next-line react/require-default-props // eslint-disable-next-line react/require-default-props
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>; setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
onDragSelect?: (start: number, end: number) => void; onDragSelect?: (start: number, end: number) => void;
onClickHandler?: GraphOnClickHandler;
allowDelete?: boolean;
allowClone?: boolean;
allowEdit?: boolean;
} }
GridCardGraph.defaultProps = { GridCardGraph.defaultProps = {
onDragSelect: undefined, onDragSelect: undefined,
onClickHandler: undefined,
allowDelete: true,
allowClone: true,
allowEdit: true,
}; };
const mapDispatchToProps = ( const mapDispatchToProps = (

View File

@ -0,0 +1,13 @@
export enum MenuItemKeys {
View = 'view',
Edit = 'edit',
Delete = 'delete',
Clone = 'clone',
}
export const MENUITEM_KEYS_VS_LABELS = {
[MenuItemKeys.View]: 'View',
[MenuItemKeys.Edit]: 'Edit',
[MenuItemKeys.Delete]: 'Delete',
[MenuItemKeys.Clone]: 'Clone',
};

View File

@ -27,24 +27,29 @@ import {
spinnerStyles, spinnerStyles,
tooltipStyles, tooltipStyles,
} from './config'; } from './config';
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
import { import {
ArrowContainer, ArrowContainer,
HeaderContainer, HeaderContainer,
HeaderContentContainer, HeaderContentContainer,
} from './styles'; } from './styles';
import { KeyMethodMappingProps, MenuItem, TWidgetOptions } from './types';
import { generateMenuList, isTWidgetOptions } from './utils';
type TWidgetOptions = 'view' | 'edit' | 'delete' | string;
interface IWidgetHeaderProps { interface IWidgetHeaderProps {
title: string; title: string;
widget: Widgets; widget: Widgets;
onView: VoidFunction; onView: VoidFunction;
onDelete: VoidFunction; onDelete?: VoidFunction;
onClone: VoidFunction; onClone?: VoidFunction;
parentHover: boolean; parentHover: boolean;
queryResponse: UseQueryResult< queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps> | ErrorResponse SuccessResponse<MetricRangePayloadProps> | ErrorResponse
>; >;
errorMessage: string | undefined; errorMessage: string | undefined;
allowDelete?: boolean;
allowClone?: boolean;
allowEdit?: boolean;
} }
function WidgetHeader({ function WidgetHeader({
title, title,
@ -55,6 +60,9 @@ function WidgetHeader({
parentHover, parentHover,
queryResponse, queryResponse,
errorMessage, errorMessage,
allowClone = true,
allowDelete = true,
allowEdit = true,
}: IWidgetHeaderProps): JSX.Element { }: IWidgetHeaderProps): JSX.Element {
const [localHover, setLocalHover] = useState(false); const [localHover, setLocalHover] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
@ -70,24 +78,22 @@ function WidgetHeader({
); );
}, [widget.id, widget.panelTypes, widget.query]); }, [widget.id, widget.panelTypes, widget.query]);
const keyMethodMapping: { const keyMethodMapping: KeyMethodMappingProps<TWidgetOptions> = useMemo(
[K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction };
} = useMemo(
() => ({ () => ({
view: { view: {
key: 'view', key: MenuItemKeys.View,
method: onView, method: onView,
}, },
edit: { edit: {
key: 'edit', key: MenuItemKeys.Edit,
method: onEditHandler, method: onEditHandler,
}, },
delete: { delete: {
key: 'delete', key: MenuItemKeys.Delete,
method: onDelete, method: onDelete,
}, },
clone: { clone: {
key: 'clone', key: MenuItemKeys.Clone,
method: onClone, method: onClone,
}, },
}), }),
@ -95,12 +101,14 @@ function WidgetHeader({
); );
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback( const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
({ key }: { key: TWidgetOptions }): void => { ({ key }: { key: string }): void => {
if (isTWidgetOptions(key)) {
const functionToCall = keyMethodMapping[key]?.method; const functionToCall = keyMethodMapping[key]?.method;
if (functionToCall) { if (functionToCall) {
functionToCall(); functionToCall();
setIsOpen(false); setIsOpen(false);
} }
}
}, },
[keyMethodMapping], [keyMethodMapping],
); );
@ -111,45 +119,53 @@ function WidgetHeader({
role, role,
); );
const menuList: MenuItemType[] = useMemo( const actions = useMemo(
() => [ (): MenuItem[] => [
{ {
key: keyMethodMapping.view.key, key: MenuItemKeys.View,
icon: <FullscreenOutlined />, icon: <FullscreenOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
isVisible: true,
disabled: queryResponse.isLoading, disabled: queryResponse.isLoading,
label: 'View',
}, },
{ {
key: keyMethodMapping.edit.key, key: MenuItemKeys.Edit,
icon: <EditFilled />, icon: <EditFilled />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Edit],
isVisible: allowEdit,
disabled: !editWidget, disabled: !editWidget,
label: 'Edit',
}, },
{ {
key: keyMethodMapping.clone.key, key: MenuItemKeys.Clone,
icon: <CopyOutlined />, icon: <CopyOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Clone],
isVisible: allowClone,
disabled: !editWidget, disabled: !editWidget,
label: 'Clone',
}, },
{ {
key: keyMethodMapping.delete.key, key: MenuItemKeys.Delete,
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Delete],
isVisible: allowDelete,
disabled: !deleteWidget, disabled: !deleteWidget,
danger: true, danger: true,
label: 'Delete',
}, },
], ],
[ [
allowEdit,
allowClone,
allowDelete,
queryResponse.isLoading,
deleteWidget, deleteWidget,
editWidget, editWidget,
keyMethodMapping.delete.key,
keyMethodMapping.edit.key,
keyMethodMapping.view.key,
keyMethodMapping.clone.key,
queryResponse.isLoading,
], ],
); );
const menuList: MenuItemType[] = useMemo(
(): MenuItemType[] => generateMenuList(actions, keyMethodMapping),
[actions, keyMethodMapping],
);
const onClickHandler = useCallback(() => { const onClickHandler = useCallback(() => {
setIsOpen((open) => !open); setIsOpen((open) => !open);
}, []); }, []);
@ -200,4 +216,12 @@ function WidgetHeader({
); );
} }
WidgetHeader.defaultProps = {
onDelete: undefined,
onClone: undefined,
allowDelete: true,
allowClone: true,
allowEdit: true,
};
export default WidgetHeader; export default WidgetHeader;

View File

@ -9,6 +9,8 @@ export const HeaderContainer = styled.div<{ hover: boolean }>`
font-size: 0.8rem; font-size: 0.8rem;
cursor: all-scroll; cursor: all-scroll;
position: absolute; position: absolute;
top: 0;
left: 0;
`; `;
export const HeaderContentContainer = styled.span` export const HeaderContentContainer = styled.span`

View File

@ -0,0 +1,25 @@
import { ReactNode } from 'react';
import { MenuItemKeys } from './contants';
export interface MenuItem {
key: TWidgetOptions;
icon: ReactNode;
label: string;
isVisible: boolean;
disabled: boolean;
danger?: boolean;
}
export type TWidgetOptions =
| MenuItemKeys.View
| MenuItemKeys.Edit
| MenuItemKeys.Delete
| MenuItemKeys.Clone;
export type KeyMethodMappingProps<T extends TWidgetOptions> = {
[K in T]: {
key: TWidgetOptions;
method?: VoidFunction;
};
};

View File

@ -0,0 +1,24 @@
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import { MenuItemKeys } from './contants';
import { KeyMethodMappingProps, MenuItem, TWidgetOptions } from './types';
export const generateMenuList = (
actions: MenuItem[],
keyMethodMapping: KeyMethodMappingProps<TWidgetOptions>,
): MenuItemType[] =>
actions
.filter((action: MenuItem) => action.isVisible)
.map(({ key, icon: Icon, label, disabled, ...rest }) => ({
key: keyMethodMapping[key].key,
icon: Icon,
label,
disabled,
...rest,
}));
export const isTWidgetOptions = (value: string): value is TWidgetOptions =>
value === MenuItemKeys.View ||
value === MenuItemKeys.Edit ||
value === MenuItemKeys.Delete ||
value === MenuItemKeys.Clone;

View File

@ -326,6 +326,13 @@ function GridGraph(props: Props): JSX.Element {
errorMessage, errorMessage,
]); ]);
useEffect(
() => (): void => {
toggleAddWidget(false);
},
[toggleAddWidget],
);
return ( return (
<GraphLayoutContainer <GraphLayoutContainer
addPanelLoading={addPanelLoading} addPanelLoading={addPanelLoading}

View File

@ -1,13 +1,15 @@
import { Button } from 'antd'; import { Button } from 'antd';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder'; import { QueryBuilder } from 'container/QueryBuilder';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { ButtonWrapperStyled } from 'pages/LogsExplorer/styles'; import { ButtonWrapperStyled } from 'pages/LogsExplorer/styles';
import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils'; import { prepareQueryWithDefaultTimestamp } from 'pages/LogsExplorer/utils';
import { memo, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
function LogExplorerQuerySection(): JSX.Element { function LogExplorerQuerySection(): JSX.Element {
@ -23,6 +25,7 @@ function LogExplorerQuerySection(): JSX.Element {
}, [updateAllQueriesOperators]); }, [updateAllQueriesOperators]);
useShareBuilderUrl(defaultValue); useShareBuilderUrl(defaultValue);
const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => {
const isTable = panelTypes === PANEL_TYPES.TABLE; const isTable = panelTypes === PANEL_TYPES.TABLE;
const config: QueryBuilderProps['filterConfigs'] = { const config: QueryBuilderProps['filterConfigs'] = {
@ -32,11 +35,26 @@ function LogExplorerQuerySection(): JSX.Element {
return config; return config;
}, [panelTypes]); }, [panelTypes]);
const renderOrderBy = useCallback(
({ query, onChange }: OrderByFilterProps): JSX.Element => (
<ExplorerOrderBy query={query} onChange={onChange} />
),
[],
);
const queryComponents = useMemo(
(): QueryBuilderProps['queryComponents'] => ({
...(panelTypes === PANEL_TYPES.LIST ? { renderOrderBy } : {}),
}),
[panelTypes, renderOrderBy],
);
return ( return (
<QueryBuilder <QueryBuilder
panelType={panelTypes} panelType={panelTypes}
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }} config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents}
actions={ actions={
<ButtonWrapperStyled> <ButtonWrapperStyled>
<Button type="primary" onClick={handleRunQuery}> <Button type="primary" onClick={handleRunQuery}>

View File

@ -0,0 +1,24 @@
import { dragColumnParams } from 'hooks/useDragColumns/configs';
import ReactDragListView from 'react-drag-listview';
import { TableComponents } from 'react-virtuoso';
import { TableStyled } from './styles';
interface LogsCustomTableProps {
handleDragEnd: (fromIndex: number, toIndex: number) => void;
}
export const LogsCustomTable = ({
handleDragEnd,
}: LogsCustomTableProps): TableComponents['Table'] =>
function CustomTable({ style, children }): JSX.Element {
return (
<ReactDragListView.DragColumn
// eslint-disable-next-line react/jsx-props-no-spreading
{...dragColumnParams}
onDragEnd={handleDragEnd}
>
<TableStyled style={style}>{children}</TableStyled>
</ReactDragListView.DragColumn>
);
};

View File

@ -1,22 +1,26 @@
import { ColumnTypeRender } from 'components/Logs/TableView/types'; import { ColumnTypeRender } from 'components/Logs/TableView/types';
import { useTableView } from 'components/Logs/TableView/useTableView'; import { useTableView } from 'components/Logs/TableView/useTableView';
import { cloneElement, ReactElement, ReactNode, useCallback } from 'react'; import { LOCALSTORAGE } from 'constants/localStorage';
import useDragColumns from 'hooks/useDragColumns';
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
import {
cloneElement,
ReactElement,
ReactNode,
useCallback,
useMemo,
} from 'react';
import { TableComponents, TableVirtuoso } from 'react-virtuoso'; import { TableComponents, TableVirtuoso } from 'react-virtuoso';
import { infinityDefaultStyles } from './config'; import { infinityDefaultStyles } from './config';
import { LogsCustomTable } from './LogsCustomTable';
import { import {
TableCellStyled, TableCellStyled,
TableHeaderCellStyled, TableHeaderCellStyled,
TableRowStyled, TableRowStyled,
TableStyled,
} from './styles'; } from './styles';
import { InfinityTableProps } from './types'; import { InfinityTableProps } from './types';
// eslint-disable-next-line react/function-component-definition
const CustomTable: TableComponents['Table'] = ({ style, children }) => (
<TableStyled style={style}>{children}</TableStyled>
);
// eslint-disable-next-line react/function-component-definition // eslint-disable-next-line react/function-component-definition
const CustomTableRow: TableComponents['TableRow'] = ({ const CustomTableRow: TableComponents['TableRow'] = ({
children, children,
@ -31,11 +35,25 @@ function InfinityTable({
}: InfinityTableProps): JSX.Element | null { }: InfinityTableProps): JSX.Element | null {
const { onEndReached } = infitiyTableProps; const { onEndReached } = infitiyTableProps;
const { dataSource, columns } = useTableView(tableViewProps); const { dataSource, columns } = useTableView(tableViewProps);
const { draggedColumns, onDragColumns } = useDragColumns<
Record<string, unknown>
>(LOCALSTORAGE.LOGS_LIST_COLUMNS);
const tableColumns = useMemo(
() => getDraggedColumns<Record<string, unknown>>(columns, draggedColumns),
[columns, draggedColumns],
);
const handleDragEnd = useCallback(
(fromIndex: number, toIndex: number) =>
onDragColumns(tableColumns, fromIndex, toIndex),
[tableColumns, onDragColumns],
);
const itemContent = useCallback( const itemContent = useCallback(
(index: number, log: Record<string, unknown>): JSX.Element => ( (index: number, log: Record<string, unknown>): JSX.Element => (
<> <>
{columns.map((column) => { {tableColumns.map((column) => {
if (!column.render) return <td>Empty</td>; if (!column.render) return <td>Empty</td>;
const element: ColumnTypeRender<Record<string, unknown>> = column.render( const element: ColumnTypeRender<Record<string, unknown>> = column.render(
@ -60,20 +78,29 @@ function InfinityTable({
})} })}
</> </>
), ),
[columns], [tableColumns],
); );
const tableHeader = useCallback( const tableHeader = useCallback(
() => ( () => (
<tr> <tr>
{columns.map((column) => ( {tableColumns.map((column) => {
<TableHeaderCellStyled key={column.key}> const isDragColumn = column.key !== 'expand';
return (
<TableHeaderCellStyled
isDragColumn={isDragColumn}
key={column.key}
// eslint-disable-next-line react/jsx-props-no-spreading
{...(isDragColumn && { className: 'dragHandler' })}
>
{column.title as string} {column.title as string}
</TableHeaderCellStyled> </TableHeaderCellStyled>
))} );
})}
</tr> </tr>
), ),
[columns], [tableColumns],
); );
return ( return (
@ -81,7 +108,8 @@ function InfinityTable({
style={infinityDefaultStyles} style={infinityDefaultStyles}
data={dataSource} data={dataSource}
components={{ components={{
Table: CustomTable, // eslint-disable-next-line react/jsx-props-no-spreading
Table: LogsCustomTable({ handleDragEnd }),
// TODO: fix it in the future // TODO: fix it in the future
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore

View File

@ -1,6 +1,10 @@
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
import styled from 'styled-components'; import styled from 'styled-components';
interface TableHeaderCellStyledProps {
isDragColumn: boolean;
}
export const TableStyled = styled.table` export const TableStyled = styled.table`
width: 100%; width: 100%;
border-top: 1px solid rgba(253, 253, 253, 0.12); border-top: 1px solid rgba(253, 253, 253, 0.12);
@ -26,10 +30,12 @@ export const TableRowStyled = styled.tr`
} }
`; `;
export const TableHeaderCellStyled = styled.th` export const TableHeaderCellStyled = styled.th<TableHeaderCellStyledProps>`
padding: 0.5rem; padding: 0.5rem;
border-inline-end: 1px solid rgba(253, 253, 253, 0.12); border-inline-end: 1px solid rgba(253, 253, 253, 0.12);
background-color: #1d1d1d; background-color: #1d1d1d;
${({ isDragColumn }): string => (isDragColumn ? 'cursor: col-resize;' : '')}
&:first-child { &:first-child {
border-start-start-radius: 2px; border-start-start-radius: 2px;
} }

View File

@ -3,6 +3,7 @@ import LogDetail from 'components/LogDetail';
import TabLabel from 'components/TabLabel'; import TabLabel from 'components/TabLabel';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { import {
initialAutocompleteData,
initialQueriesMap, initialQueriesMap,
OPERATORS, OPERATORS,
PANEL_TYPES, PANEL_TYPES,
@ -17,6 +18,7 @@ import LogsExplorerChart from 'container/LogsExplorerChart';
import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerList from 'container/LogsExplorerList';
import LogsExplorerTable from 'container/LogsExplorerTable'; import LogsExplorerTable from 'container/LogsExplorerTable';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants';
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
@ -73,6 +75,7 @@ function LogsExplorerViews(): JSX.Element {
stagedQuery, stagedQuery,
panelType, panelType,
updateAllQueriesOperators, updateAllQueriesOperators,
updateQueriesData,
redirectWithQueryBuilderData, redirectWithQueryBuilderData,
} = useQueryBuilder(); } = useQueryBuilder();
@ -175,26 +178,40 @@ function LogsExplorerViews(): JSX.Element {
setActiveLog(null); setActiveLog(null);
}, []); }, []);
const getUpdateQuery = useCallback(
(newPanelType: GRAPH_TYPES): Query => {
let query = updateAllQueriesOperators(
currentQuery,
newPanelType,
DataSource.TRACES,
);
if (newPanelType === PANEL_TYPES.LIST) {
query = updateQueriesData(query, 'queryData', (item) => ({
...item,
orderBy: item.orderBy.filter((item) => item.columnName !== SIGNOZ_VALUE),
aggregateAttribute: initialAutocompleteData,
}));
}
return query;
},
[currentQuery, updateAllQueriesOperators, updateQueriesData],
);
const handleChangeView = useCallback( const handleChangeView = useCallback(
(newPanelType: string) => { (type: string) => {
const newPanelType = type as GRAPH_TYPES;
if (newPanelType === panelType) return; if (newPanelType === panelType) return;
const query = updateAllQueriesOperators( const query = getUpdateQuery(newPanelType);
currentQuery,
newPanelType as GRAPH_TYPES,
DataSource.LOGS,
);
redirectWithQueryBuilderData(query, { redirectWithQueryBuilderData(query, {
[queryParamNamesMap.panelTypes]: newPanelType, [queryParamNamesMap.panelTypes]: newPanelType,
}); });
}, },
[ [panelType, getUpdateQuery, redirectWithQueryBuilderData],
currentQuery,
panelType,
updateAllQueriesOperators,
redirectWithQueryBuilderData,
],
); );
const getRequestData = useCallback( const getRequestData = useCallback(

View File

@ -2,7 +2,10 @@ import { PANEL_TYPES } from 'constants/queryBuilder';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
export const getWidgetQueryBuilder = (query: Widgets['query']): Widgets => ({ export const getWidgetQueryBuilder = (
query: Widgets['query'],
title = '',
): Widgets => ({
description: '', description: '',
id: v4(), id: v4(),
isStacked: false, isStacked: false,
@ -11,5 +14,5 @@ export const getWidgetQueryBuilder = (query: Widgets['query']): Widgets => ({
panelTypes: PANEL_TYPES.TIME_SERIES, panelTypes: PANEL_TYPES.TIME_SERIES,
query, query,
timePreferance: 'GLOBAL_TIME', timePreferance: 'GLOBAL_TIME',
title: '', title,
}); });

View File

@ -1,7 +1,10 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder'; import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
import { IServiceName } from '../Tabs/types';
import { import {
getQueryBuilderQueries, getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula, getQueryBuilderQuerieswithFormula,
@ -12,35 +15,42 @@ export const databaseCallsRPS = ({
legend, legend,
tagFilterItems, tagFilterItems,
}: DatabaseCallsRPSProps): QueryBuilderData => { }: DatabaseCallsRPSProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = { const autocompleteData: BaseAutocompleteData[] = [
dataType: 'float64', {
key: WidgetKeys.SignozDBLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_db_latency_count',
type: null, type: null,
}; },
const groupBy: BaseAutocompleteData[] = [
{ dataType: 'string', isColumn: false, key: 'db_system', type: 'tag' },
]; ];
const itemsA: TagFilterItem[] = [ const groupBy: BaseAutocompleteData[] = [
{ dataType: DataType.STRING, isColumn: false, key: 'db_system', type: 'tag' },
];
const filterItems: TagFilterItem[][] = [
[
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
],
]; ];
const legends = [legend];
return getQueryBuilderQueries({ return getQueryBuilderQueries({
metricName, autocompleteData,
groupBy, groupBy,
legend, legends,
itemsA, filterItems,
dataSource: DataSource.METRICS,
}); });
}; };
@ -48,32 +58,29 @@ export const databaseCallsAvgDuration = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
}: DatabaseCallProps): QueryBuilderData => { }: DatabaseCallProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozDbLatencySum,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_db_latency_sum',
type: null, type: null,
}; };
const metricNameB: BaseAutocompleteData = { const autocompleteDataB: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozDBLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_db_latency_count',
type: null, type: null,
}; };
const expression = 'A/B';
const legendFormula = 'Average Duration';
const legend = '';
const disabled = true;
const additionalItemsA: TagFilterItem[] = [ const additionalItemsA: TagFilterItem[] = [
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
@ -81,14 +88,14 @@ export const databaseCallsAvgDuration = ({
const additionalItemsB = additionalItemsA; const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({ return getQueryBuilderQuerieswithFormula({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend: '',
disabled, disabled: true,
expression, expression: FORMULA.DATABASE_CALLS_AVG_DURATION,
legendFormula, legendFormula: 'Average Duration',
}); });
}; };
@ -97,6 +104,6 @@ interface DatabaseCallsRPSProps extends DatabaseCallProps {
} }
interface DatabaseCallProps { interface DatabaseCallProps {
servicename: string | undefined; servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[]; tagFilterItems: TagFilterItem[];
} }

View File

@ -1,14 +1,22 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder'; import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant';
import { IServiceName } from '../Tabs/types';
import { import {
getQueryBuilderQueries, getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula, getQueryBuilderQuerieswithFormula,
} from './MetricsPageQueriesFactory'; } from './MetricsPageQueriesFactory';
const groupBy: BaseAutocompleteData[] = [ const groupBy: BaseAutocompleteData[] = [
{ dataType: 'string', isColumn: false, key: 'address', type: 'tag' }, {
dataType: DataType.STRING,
isColumn: false,
key: WidgetKeys.Address,
type: MetricsType.Tag,
},
]; ];
export const externalCallErrorPercent = ({ export const externalCallErrorPercent = ({
@ -16,39 +24,39 @@ export const externalCallErrorPercent = ({
legend, legend,
tagFilterItems, tagFilterItems,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozExternalCallLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_count',
type: null, type: null,
}; };
const metricNameB: BaseAutocompleteData = { const autocompleteDataB: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozExternalCallLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_count',
type: null, type: null,
}; };
const additionalItemsA: TagFilterItem[] = [ const additionalItemsA: TagFilterItem[] = [
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
{ {
id: '', id: '',
key: { key: {
dataType: 'int64', key: WidgetKeys.StatusCode,
dataType: DataType.INT64,
isColumn: false, isColumn: false,
key: 'status_code', type: MetricsType.Tag,
type: 'tag',
}, },
op: 'IN', op: OPERATORS.IN,
value: ['STATUS_CODE_ERROR'], value: ['STATUS_CODE_ERROR'],
}, },
...tagFilterItems, ...tagFilterItems,
@ -57,22 +65,22 @@ export const externalCallErrorPercent = ({
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
]; ];
const legendFormula = legend; const legendFormula = legend;
const expression = 'A*100/B'; const expression = FORMULA.ERROR_PERCENTAGE;
const disabled = true; const disabled = true;
return getQueryBuilderQuerieswithFormula({ return getQueryBuilderQuerieswithFormula({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend,
@ -87,19 +95,19 @@ export const externalCallDuration = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
}: ExternalCallProps): QueryBuilderData => { }: ExternalCallProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: 'float64', dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_sum', key: WidgetKeys.SignozExternalCallLatencySum,
type: null, type: null,
}; };
const metricNameB: BaseAutocompleteData = { const autocompleteDataB: BaseAutocompleteData = {
dataType: 'float64', dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_count', key: WidgetKeys.SignozExternalCallLatencyCount,
type: null, type: null,
}; };
const expression = 'A/B'; const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
const legendFormula = 'Average Duration'; const legendFormula = 'Average Duration';
const legend = ''; const legend = '';
const disabled = true; const disabled = true;
@ -107,12 +115,12 @@ export const externalCallDuration = ({
{ {
id: '', id: '',
key: { key: {
dataType: 'string', dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', key: WidgetKeys.Service_name,
type: 'resource', type: MetricsType.Resource,
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
@ -120,8 +128,8 @@ export const externalCallDuration = ({
const additionalItemsB = additionalItemsA; const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({ return getQueryBuilderQuerieswithFormula({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend,
@ -136,31 +144,38 @@ export const externalCallRpsByAddress = ({
legend, legend,
tagFilterItems, tagFilterItems,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = { const autocompleteData: BaseAutocompleteData[] = [
dataType: 'float64', {
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_count', key: WidgetKeys.SignozExternalCallLatencyCount,
type: null, type: null,
}; },
const itemsA: TagFilterItem[] = [ ];
const filterItems: TagFilterItem[][] = [
[
{ {
id: '', id: '',
key: { key: {
dataType: 'string', dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', key: WidgetKeys.Service_name,
type: 'resource', type: MetricsType.Resource,
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
],
]; ];
const legends: string[] = [legend];
return getQueryBuilderQueries({ return getQueryBuilderQueries({
metricName, autocompleteData,
groupBy, groupBy,
legend, legends,
itemsA, filterItems,
dataSource: DataSource.METRICS,
}); });
}; };
@ -169,31 +184,31 @@ export const externalCallDurationByAddress = ({
legend, legend,
tagFilterItems, tagFilterItems,
}: ExternalCallDurationByAddressProps): QueryBuilderData => { }: ExternalCallDurationByAddressProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: 'float64', dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_sum', key: WidgetKeys.SignozExternalCallLatencySum,
type: null, type: null,
}; };
const metricNameB: BaseAutocompleteData = { const autocompleteDataB: BaseAutocompleteData = {
dataType: 'float64', dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_external_call_latency_count', key: WidgetKeys.SignozExternalCallLatencyCount,
type: null, type: null,
}; };
const expression = 'A/B'; const expression = FORMULA.DATABASE_CALLS_AVG_DURATION;
const legendFormula = legend; const legendFormula = legend;
const disabled = true; const disabled = true;
const additionalItemsA: TagFilterItem[] = [ const additionalItemsA: TagFilterItem[] = [
{ {
id: '', id: '',
key: { key: {
dataType: 'string', dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', key: WidgetKeys.Service_name,
type: 'resource', type: MetricsType.Resource,
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
...tagFilterItems, ...tagFilterItems,
@ -201,8 +216,8 @@ export const externalCallDurationByAddress = ({
const additionalItemsB = additionalItemsA; const additionalItemsB = additionalItemsA;
return getQueryBuilderQuerieswithFormula({ return getQueryBuilderQuerieswithFormula({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend,
@ -218,6 +233,6 @@ interface ExternalCallDurationByAddressProps extends ExternalCallProps {
} }
export interface ExternalCallProps { export interface ExternalCallProps {
servicename: string | undefined; servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[]; tagFilterItems: TagFilterItem[];
} }

View File

@ -5,44 +5,64 @@ import {
import getStep from 'lib/getStep'; import getStep from 'lib/getStep';
import store from 'store'; import store from 'store';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { import {
IBuilderQuery,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import {
DataSource,
MetricAggregateOperator, MetricAggregateOperator,
QueryBuilderData, QueryBuilderData,
} from 'types/common/queryBuilder'; } from 'types/common/queryBuilder';
export const getQueryBuilderQueries = ({ export const getQueryBuilderQueries = ({
metricName, autocompleteData,
groupBy = [], groupBy = [],
legend, legends,
itemsA, filterItems,
aggregateOperator,
dataSource,
queryNameAndExpression,
}: BuilderQueriesProps): QueryBuilderData => ({ }: BuilderQueriesProps): QueryBuilderData => ({
queryFormulas: [], queryFormulas: [],
queryData: [ queryData: autocompleteData.map((item, index) => {
{ const newQueryData: IBuilderQuery = {
...initialQueryBuilderFormValuesMap.metrics, ...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE, aggregateOperator: ((): string => {
if (aggregateOperator) {
return aggregateOperator[index];
}
return MetricAggregateOperator.SUM_RATE;
})(),
disabled: false, disabled: false,
groupBy, groupBy,
aggregateAttribute: metricName, aggregateAttribute: item,
legend, legend: legends[index],
stepInterval: getStep({ stepInterval: getStep({
end: store.getState().globalTime.maxTime, end: store.getState().globalTime.maxTime,
inputFormat: 'ns', inputFormat: 'ns',
start: store.getState().globalTime.minTime, start: store.getState().globalTime.minTime,
}), }),
reduceTo: 'sum',
filters: { filters: {
items: itemsA, items: filterItems[index],
op: 'AND', op: 'AND',
}, },
}, reduceTo: 'sum',
], dataSource,
};
if (queryNameAndExpression) {
newQueryData.queryName = queryNameAndExpression[index];
newQueryData.expression = queryNameAndExpression[index];
}
return newQueryData;
}),
}); });
export const getQueryBuilderQuerieswithFormula = ({ export const getQueryBuilderQuerieswithFormula = ({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend,
@ -65,7 +85,7 @@ export const getQueryBuilderQuerieswithFormula = ({
disabled, disabled,
groupBy, groupBy,
legend, legend,
aggregateAttribute: metricNameA, aggregateAttribute: autocompleteDataA,
reduceTo: 'sum', reduceTo: 'sum',
filters: { filters: {
items: additionalItemsA, items: additionalItemsA,
@ -83,7 +103,7 @@ export const getQueryBuilderQuerieswithFormula = ({
disabled, disabled,
groupBy, groupBy,
legend, legend,
aggregateAttribute: metricNameB, aggregateAttribute: autocompleteDataB,
queryName: 'B', queryName: 'B',
expression: 'B', expression: 'B',
reduceTo: 'sum', reduceTo: 'sum',
@ -101,15 +121,18 @@ export const getQueryBuilderQuerieswithFormula = ({
}); });
interface BuilderQueriesProps { interface BuilderQueriesProps {
metricName: BaseAutocompleteData; autocompleteData: BaseAutocompleteData[];
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];
legend: string; legends: string[];
itemsA: TagFilterItem[]; filterItems: TagFilterItem[][];
aggregateOperator?: string[];
dataSource: DataSource;
queryNameAndExpression?: string[];
} }
interface BuilderQuerieswithFormulaProps { interface BuilderQuerieswithFormulaProps {
metricNameA: BaseAutocompleteData; autocompleteDataA: BaseAutocompleteData;
metricNameB: BaseAutocompleteData; autocompleteDataB: BaseAutocompleteData;
legend: string; legend: string;
disabled: boolean; disabled: boolean;
groupBy?: BaseAutocompleteData[]; groupBy?: BaseAutocompleteData[];

View File

@ -1,55 +1,131 @@
import { OPERATORS } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder'; import { DataSource, QueryBuilderData } from 'types/common/queryBuilder';
import {
DataType,
FORMULA,
GraphTitle,
LATENCY_AGGREGATEOPERATOR,
LATENCY_AGGREGATEOPERATOR_SPAN_METRICS,
MetricsType,
OPERATION_LEGENDS,
QUERYNAME_AND_EXPRESSION,
WidgetKeys,
} from '../constant';
import { IServiceName } from '../Tabs/types';
import { import {
getQueryBuilderQueries, getQueryBuilderQueries,
getQueryBuilderQuerieswithFormula, getQueryBuilderQuerieswithFormula,
} from './MetricsPageQueriesFactory'; } from './MetricsPageQueriesFactory';
export const latency = ({
servicename,
tagFilterItems,
isSpanMetricEnable = false,
topLevelOperationsRoute,
}: LatencyProps): QueryBuilderData => {
const newAutoCompleteData: BaseAutocompleteData = {
key: isSpanMetricEnable
? WidgetKeys.Signoz_latency_bucket
: WidgetKeys.DurationNano,
dataType: DataType.FLOAT64,
isColumn: true,
type: isSpanMetricEnable ? null : MetricsType.Tag,
};
const autocompleteData: BaseAutocompleteData[] = Array(3).fill(
newAutoCompleteData,
);
const filterItem: TagFilterItem[] = [
{
id: '',
key: {
key: isSpanMetricEnable ? WidgetKeys.Service_name : WidgetKeys.ServiceName,
dataType: DataType.STRING,
type: isSpanMetricEnable ? MetricsType.Resource : MetricsType.Tag,
isColumn: !isSpanMetricEnable,
},
op: isSpanMetricEnable ? OPERATORS.IN : OPERATORS['='],
value: isSpanMetricEnable ? [servicename] : servicename,
},
{
id: '',
key: {
dataType: DataType.STRING,
isColumn: !isSpanMetricEnable,
key: isSpanMetricEnable ? WidgetKeys.Operation : WidgetKeys.Name,
type: MetricsType.Tag,
},
op: OPERATORS.IN.toLowerCase(), // TODO: need to remove toLowerCase() this once backend is changed
value: [...topLevelOperationsRoute],
},
...tagFilterItems,
];
const filterItems: TagFilterItem[][] = Array(3).fill([...filterItem]);
return getQueryBuilderQueries({
autocompleteData,
legends: LATENCY_AGGREGATEOPERATOR,
filterItems,
aggregateOperator: isSpanMetricEnable
? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS
: LATENCY_AGGREGATEOPERATOR,
dataSource: isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES,
queryNameAndExpression: QUERYNAME_AND_EXPRESSION,
});
};
export const operationPerSec = ({ export const operationPerSec = ({
servicename, servicename,
tagFilterItems, tagFilterItems,
topLevelOperations, topLevelOperations,
}: OperationPerSecProps): QueryBuilderData => { }: OperationPerSecProps): QueryBuilderData => {
const metricName: BaseAutocompleteData = { const autocompleteData: BaseAutocompleteData[] = [
dataType: 'float64', {
key: WidgetKeys.SignozLatencyCount,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_latency_count',
type: null, type: null,
}; },
const legend = 'Operations'; ];
const itemsA: TagFilterItem[] = [ const filterItems: TagFilterItem[][] = [
[
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Operation,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'operation', type: MetricsType.Tag,
type: 'tag',
}, },
op: 'IN', op: OPERATORS.IN,
value: topLevelOperations, value: topLevelOperations,
}, },
...tagFilterItems, ...tagFilterItems,
],
]; ];
return getQueryBuilderQueries({ return getQueryBuilderQueries({
metricName, autocompleteData,
legend, legends: OPERATION_LEGENDS,
itemsA, filterItems,
dataSource: DataSource.METRICS,
}); });
}; };
@ -58,50 +134,50 @@ export const errorPercentage = ({
tagFilterItems, tagFilterItems,
topLevelOperations, topLevelOperations,
}: OperationPerSecProps): QueryBuilderData => { }: OperationPerSecProps): QueryBuilderData => {
const metricNameA: BaseAutocompleteData = { const autocompleteDataA: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozCallsTotal,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_calls_total',
type: null, type: null,
}; };
const metricNameB: BaseAutocompleteData = { const autocompleteDataB: BaseAutocompleteData = {
dataType: 'float64', key: WidgetKeys.SignozCallsTotal,
dataType: DataType.FLOAT64,
isColumn: true, isColumn: true,
key: 'signoz_calls_total',
type: null, type: null,
}; };
const additionalItemsA: TagFilterItem[] = [ const additionalItemsA: TagFilterItem[] = [
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Operation,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'operation', type: MetricsType.Tag,
type: 'tag',
}, },
op: 'IN', op: OPERATORS.IN,
value: topLevelOperations, value: topLevelOperations,
}, },
{ {
id: '', id: '',
key: { key: {
dataType: 'int64', key: WidgetKeys.StatusCode,
dataType: DataType.INT64,
isColumn: false, isColumn: false,
key: 'status_code', type: MetricsType.Tag,
type: 'tag',
}, },
op: 'IN', op: OPERATORS.IN,
value: ['STATUS_CODE_ERROR'], value: ['STATUS_CODE_ERROR'],
}, },
...tagFilterItems, ...tagFilterItems,
@ -111,46 +187,49 @@ export const errorPercentage = ({
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Service_name,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'service_name', type: MetricsType.Resource,
type: 'resource',
}, },
op: 'IN', op: OPERATORS.IN,
value: [`${servicename}`], value: [`${servicename}`],
}, },
{ {
id: '', id: '',
key: { key: {
dataType: 'string', key: WidgetKeys.Operation,
dataType: DataType.STRING,
isColumn: false, isColumn: false,
key: 'operation', type: MetricsType.Tag,
type: 'tag',
}, },
op: 'IN', op: OPERATORS.IN,
value: topLevelOperations, value: topLevelOperations,
}, },
...tagFilterItems, ...tagFilterItems,
]; ];
const legendFormula = 'Error Percentage';
const legend = legendFormula;
const expression = 'A*100/B';
const disabled = true;
return getQueryBuilderQuerieswithFormula({ return getQueryBuilderQuerieswithFormula({
metricNameA, autocompleteDataA,
metricNameB, autocompleteDataB,
additionalItemsA, additionalItemsA,
additionalItemsB, additionalItemsB,
legend, legend: GraphTitle.ERROR_PERCENTAGE,
disabled, disabled: true,
expression, expression: FORMULA.ERROR_PERCENTAGE,
legendFormula, legendFormula: GraphTitle.ERROR_PERCENTAGE,
}); });
}; };
export interface OperationPerSecProps { export interface OperationPerSecProps {
servicename: string | undefined; servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[]; tagFilterItems: TagFilterItem[];
topLevelOperations: string[]; topLevelOperations: string[];
} }
export interface LatencyProps {
servicename: IServiceName['servicename'];
tagFilterItems: TagFilterItem[];
isSpanMetricEnable?: boolean;
topLevelOperationsRoute: string[];
}

View File

@ -1,5 +1,5 @@
import { Col } from 'antd'; import { Col } from 'antd';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder'; import Graph from 'container/GridGraphLayout/Graph/';
import { import {
databaseCallsAvgDuration, databaseCallsAvgDuration,
databaseCallsRPS, databaseCallsRPS,
@ -15,9 +15,11 @@ import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { GraphTitle } from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, GraphTitle, Row } from '../styles'; import { Card, GraphContainer, Row } from '../styles';
import { Button } from './styles'; import { Button } from './styles';
import { IServiceName } from './types';
import { import {
dbSystemTags, dbSystemTags,
handleNonInQueryRange, handleNonInQueryRange,
@ -26,7 +28,7 @@ import {
} from './util'; } from './util';
function DBCall(): JSX.Element { function DBCall(): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<IServiceName>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0); const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { queries } = useResourceAttribute(); const { queries } = useResourceAttribute();
@ -48,7 +50,8 @@ function DBCall(): JSX.Element {
const databaseCallsRPSWidget = useMemo( const databaseCallsRPSWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: databaseCallsRPS({ builder: databaseCallsRPS({
@ -58,12 +61,15 @@ function DBCall(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.DATABASE_CALLS_RPS,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
const databaseCallsAverageDurationWidget = useMemo( const databaseCallsAverageDurationWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: databaseCallsAvgDuration({ builder: databaseCallsAvgDuration({
@ -72,7 +78,9 @@ function DBCall(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.DATABASE_CALLS_AVG_DURATION,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
@ -92,11 +100,9 @@ function DBCall(): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>Database Calls RPS</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="database_call_rps" name="database_call_rps"
fullViewOptions={false}
widget={databaseCallsRPSWidget} widget={databaseCallsRPSWidget}
yAxisUnit="reqps" yAxisUnit="reqps"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -108,6 +114,9 @@ function DBCall(): JSX.Element {
'database_call_rps', 'database_call_rps',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -127,11 +136,9 @@ function DBCall(): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>Database Calls Avg Duration</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="database_call_avg_duration" name="database_call_avg_duration"
fullViewOptions={false}
widget={databaseCallsAverageDurationWidget} widget={databaseCallsAverageDurationWidget}
yAxisUnit="ms" yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -143,6 +150,9 @@ function DBCall(): JSX.Element {
'database_call_avg_duration', 'database_call_avg_duration',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>

View File

@ -1,5 +1,5 @@
import { Col } from 'antd'; import { Col } from 'antd';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder'; import Graph from 'container/GridGraphLayout/Graph/';
import { import {
externalCallDuration, externalCallDuration,
externalCallDurationByAddress, externalCallDurationByAddress,
@ -16,10 +16,11 @@ import { useParams } from 'react-router-dom';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { GraphTitle, legend } from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, GraphTitle, Row } from '../styles'; import { Card, GraphContainer, Row } from '../styles';
import { legend } from './constant';
import { Button } from './styles'; import { Button } from './styles';
import { IServiceName } from './types';
import { import {
handleNonInQueryRange, handleNonInQueryRange,
onGraphClickHandler, onGraphClickHandler,
@ -29,7 +30,7 @@ import {
function External(): JSX.Element { function External(): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0); const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<IServiceName>();
const { queries } = useResourceAttribute(); const { queries } = useResourceAttribute();
const tagFilterItems = useMemo( const tagFilterItems = useMemo(
@ -40,7 +41,8 @@ function External(): JSX.Element {
const externalCallErrorWidget = useMemo( const externalCallErrorWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: externalCallErrorPercent({ builder: externalCallErrorPercent({
@ -50,7 +52,9 @@ function External(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
@ -61,7 +65,8 @@ function External(): JSX.Element {
const externalCallDurationWidget = useMemo( const externalCallDurationWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: externalCallDuration({ builder: externalCallDuration({
@ -70,13 +75,16 @@ function External(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.EXTERNAL_CALL_DURATION,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
const externalCallRPSWidget = useMemo( const externalCallRPSWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: externalCallRpsByAddress({ builder: externalCallRpsByAddress({
@ -86,13 +94,16 @@ function External(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
const externalCallDurationAddressWidget = useMemo( const externalCallDurationAddressWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: externalCallDurationByAddress({ builder: externalCallDurationByAddress({
@ -102,7 +113,9 @@ function External(): JSX.Element {
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS,
),
[servicename, tagFilterItems], [servicename, tagFilterItems],
); );
@ -124,11 +137,9 @@ function External(): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>External Call Error Percentage</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="external_call_error_percentage" name="external_call_error_percentage"
fullViewOptions={false}
widget={externalCallErrorWidget} widget={externalCallErrorWidget}
yAxisUnit="%" yAxisUnit="%"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -140,6 +151,9 @@ function External(): JSX.Element {
'external_call_error_percentage', 'external_call_error_percentage',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -161,11 +175,9 @@ function External(): JSX.Element {
</Button> </Button>
<Card> <Card>
<GraphTitle>External Call duration</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="external_call_duration" name="external_call_duration"
fullViewOptions={false}
widget={externalCallDurationWidget} widget={externalCallDurationWidget}
yAxisUnit="ms" yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -177,6 +189,9 @@ function External(): JSX.Element {
'external_call_duration', 'external_call_duration',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -199,11 +214,9 @@ function External(): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <Card>
<GraphTitle>External Call RPS(by Address)</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="external_call_rps_by_address" name="external_call_rps_by_address"
fullViewOptions={false}
widget={externalCallRPSWidget} widget={externalCallRPSWidget}
yAxisUnit="reqps" yAxisUnit="reqps"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -215,6 +228,9 @@ function External(): JSX.Element {
'external_call_rps_by_address', 'external_call_rps_by_address',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>
@ -236,11 +252,9 @@ function External(): JSX.Element {
</Button> </Button>
<Card> <Card>
<GraphTitle>External Call duration(by Address)</GraphTitle>
<GraphContainer> <GraphContainer>
<FullView <Graph
name="external_call_duration_by_address" name="external_call_duration_by_address"
fullViewOptions={false}
widget={externalCallDurationAddressWidget} widget={externalCallDurationAddressWidget}
yAxisUnit="ms" yAxisUnit="ms"
onClickHandler={(ChartEvent, activeElements, chart, data): void => { onClickHandler={(ChartEvent, activeElements, chart, data): void => {
@ -252,6 +266,9 @@ function External(): JSX.Element {
'external_call_duration_by_address', 'external_call_duration_by_address',
); );
}} }}
allowClone={false}
allowDelete={false}
allowEdit={false}
/> />
</GraphContainer> </GraphContainer>
</Card> </Card>

View File

@ -1,16 +1,9 @@
import { Typography } from 'antd';
import getServiceOverview from 'api/metrics/getServiceOverview';
import getTopLevelOperations, { import getTopLevelOperations, {
ServiceDataProps, ServiceDataProps,
} from 'api/metrics/getTopLevelOperations'; } from 'api/metrics/getTopLevelOperations';
import getTopOperations from 'api/metrics/getTopOperations';
import axios from 'axios';
import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js'; import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js';
import Graph from 'components/Graph';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import FullView from 'container/GridGraphLayout/Graph/FullView/index.metricsBuilder';
import { routeConfig } from 'container/SideNav/config'; import { routeConfig } from 'container/SideNav/config';
import { getQueryString } from 'container/SideNav/helper'; import { getQueryString } from 'container/SideNav/helper';
import useResourceAttribute from 'hooks/useResourceAttribute'; import useResourceAttribute from 'hooks/useResourceAttribute';
@ -18,32 +11,30 @@ import {
convertRawQueriesToTraceSelectedTags, convertRawQueriesToTraceSelectedTags,
resourceAttributesToTagFilterItems, resourceAttributesToTagFilterItems,
} from 'hooks/useResourceAttribute/utils'; } from 'hooks/useResourceAttribute/utils';
import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond';
import { colors } from 'lib/getRandomColor';
import getStep from 'lib/getStep';
import history from 'lib/history'; import history from 'lib/history';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useQueries, UseQueryResult } from 'react-query'; import { useQuery } from 'react-query';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions'; import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/metrics/getServiceOverview';
import { PayloadProps as PayloadPropsTopOpertions } from 'types/api/metrics/getTopOperations';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace'; import { Tags } from 'types/reducer/trace';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { SOMETHING_WENT_WRONG } from '../../../constants/api'; import { GraphTitle } from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory'; import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { import {
errorPercentage, errorPercentage,
operationPerSec, operationPerSec,
} from '../MetricsPageQueries/OverviewQueries'; } from '../MetricsPageQueries/OverviewQueries';
import { Card, Col, GraphContainer, GraphTitle, Row } from '../styles'; import { Col, Row } from '../styles';
import TopOperationsTable from '../TopOperationsTable'; import ServiceOverview from './Overview/ServiceOverview';
import TopLevelOperation from './Overview/TopLevelOperations';
import TopOperation from './Overview/TopOperation';
import { Button } from './styles'; import { Button } from './styles';
import { IServiceName } from './types';
import { import {
handleNonInQueryRange, handleNonInQueryRange,
onGraphClickHandler, onGraphClickHandler,
@ -54,7 +45,7 @@ function Application(): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
); );
const { servicename } = useParams<{ servicename?: string }>(); const { servicename } = useParams<IServiceName>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0); const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { search } = useLocation(); const { search } = useLocation();
const { queries } = useResourceAttribute(); const { queries } = useResourceAttribute();
@ -86,53 +77,15 @@ function Application(): JSX.Element {
[handleSetTimeStamp], [handleSetTimeStamp],
); );
const queryResult = useQueries< const {
[ data: topLevelOperations,
UseQueryResult<PayloadProps>, isLoading: topLevelOperationsLoading,
UseQueryResult<PayloadPropsTopOpertions>, error: topLevelOperationsError,
UseQueryResult<ServiceDataProps>, isError: topLevelOperationsIsError,
] } = useQuery<ServiceDataProps>({
>([
{
queryKey: [servicename, selectedTags, minTime, maxTime],
queryFn: (): Promise<PayloadProps> =>
getServiceOverview({
service: servicename || '',
start: minTime,
end: maxTime,
step: getStep({
start: minTime,
end: maxTime,
inputFormat: 'ns',
}),
selectedTags,
}),
},
{
queryKey: [minTime, maxTime, servicename, selectedTags],
queryFn: (): Promise<PayloadPropsTopOpertions> =>
getTopOperations({
service: servicename || '',
start: minTime,
end: maxTime,
selectedTags,
}),
},
{
queryKey: [servicename, minTime, maxTime, selectedTags], queryKey: [servicename, minTime, maxTime, selectedTags],
queryFn: (): Promise<ServiceDataProps> => getTopLevelOperations(), queryFn: getTopLevelOperations,
}, });
]);
const serviceOverview = queryResult[0].data;
const serviceOverviewError = queryResult[0].error;
const serviceOverviewIsError = queryResult[0].isError;
const serviceOverviewIsLoading = queryResult[0].isLoading;
const topOperations = queryResult[1].data;
const topLevelOperations = queryResult[2].data;
const topLevelOperationsError = queryResult[2].error;
const topLevelOperationsIsError = queryResult[2].isError;
const topLevelOperationsIsLoading = queryResult[2].isLoading;
const selectedTraceTags: string = JSON.stringify( const selectedTraceTags: string = JSON.stringify(
convertRawQueriesToTraceSelectedTags(queries) || [], convertRawQueriesToTraceSelectedTags(queries) || [],
@ -144,40 +97,47 @@ function Application(): JSX.Element {
[queries], [queries],
); );
const topLevelOperationsRoute = useMemo(
() => (topLevelOperations ? topLevelOperations[servicename || ''] : []),
[servicename, topLevelOperations],
);
const operationPerSecWidget = useMemo( const operationPerSecWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: operationPerSec({ builder: operationPerSec({
servicename, servicename,
tagFilterItems, tagFilterItems,
topLevelOperations: topLevelOperations topLevelOperations: topLevelOperationsRoute,
? topLevelOperations[servicename || '']
: [],
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
[servicename, topLevelOperations, tagFilterItems], GraphTitle.RATE_PER_OPS,
),
[servicename, tagFilterItems, topLevelOperationsRoute],
); );
const errorPercentageWidget = useMemo( const errorPercentageWidget = useMemo(
() => () =>
getWidgetQueryBuilder({ getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [], promql: [],
builder: errorPercentage({ builder: errorPercentage({
servicename, servicename,
tagFilterItems, tagFilterItems,
topLevelOperations: topLevelOperations topLevelOperations: topLevelOperationsRoute,
? topLevelOperations[servicename || '']
: [],
}), }),
clickhouse_sql: [], clickhouse_sql: [],
id: uuid(), id: uuid(),
}), },
[servicename, topLevelOperations, tagFilterItems], GraphTitle.ERROR_PERCENTAGE,
),
[servicename, tagFilterItems, topLevelOperationsRoute],
); );
const onDragSelect = useCallback( const onDragSelect = useCallback(
@ -212,107 +172,18 @@ function Application(): JSX.Element {
); );
}; };
const generalChartDataProperties = useCallback(
(title: string, colorIndex: number) => ({
borderColor: colors[colorIndex],
label: title,
showLine: true,
borderWidth: 1.5,
spanGaps: true,
pointRadius: 2,
pointHoverRadius: 4,
}),
[],
);
const dataSets = useMemo(() => {
if (!serviceOverview) {
return [];
}
return [
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p99)),
),
...generalChartDataProperties('p99 Latency', 0),
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p95)),
),
...generalChartDataProperties('p95 Latency', 1),
},
{
data: serviceOverview.map((e) =>
parseFloat(convertToNanoSecondsToSecond(e.p50)),
),
...generalChartDataProperties('p50 Latency', 2),
},
];
}, [generalChartDataProperties, serviceOverview]);
const data = useMemo(() => {
if (!serviceOverview) {
return {
datasets: [],
labels: [],
};
}
return {
datasets: dataSets,
labels: serviceOverview.map(
(e) => new Date(parseFloat(convertToNanoSecondsToSecond(e.timestamp))),
),
};
}, [serviceOverview, dataSets]);
return ( return (
<> <>
<Row gutter={24}> <Row gutter={24}>
<Col span={12}> <Col span={12}>
<Button <ServiceOverview
type="default"
size="small"
id="Service_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
})}
>
View Traces
</Button>
<Card>
{serviceOverviewIsError ? (
<Typography>
{axios.isAxiosError(serviceOverviewError)
? serviceOverviewError.response?.data
: SOMETHING_WENT_WRONG}
</Typography>
) : (
<>
<GraphTitle>Latency</GraphTitle>
{serviceOverviewIsLoading && (
<Spinner size="large" tip="Loading..." height="40vh" />
)}
{!serviceOverviewIsLoading && (
<GraphContainer>
<Graph
animate={false}
onClickHandler={handleGraphClick('Service')}
name="service_latency"
type="line"
data={data}
yAxisUnit="ms"
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
handleGraphClick={handleGraphClick}
selectedTimeStamp={selectedTimeStamp}
selectedTraceTags={selectedTraceTags}
tagFilterItems={tagFilterItems}
topLevelOperationsRoute={topLevelOperationsRoute}
/> />
</GraphContainer>
)}
</>
)}
</Card>
</Col> </Col>
<Col span={12}> <Col span={12}>
@ -328,30 +199,17 @@ function Application(): JSX.Element {
> >
View Traces View Traces
</Button> </Button>
<Card> <TopLevelOperation
{topLevelOperationsIsError ? ( handleGraphClick={handleGraphClick}
<Typography> onDragSelect={onDragSelect}
{axios.isAxiosError(topLevelOperationsError) topLevelOperationsError={topLevelOperationsError}
? topLevelOperationsError.response?.data topLevelOperationsLoading={topLevelOperationsLoading}
: SOMETHING_WENT_WRONG} topLevelOperationsIsError={topLevelOperationsIsError}
</Typography>
) : (
<>
<GraphTitle>Rate (ops/s)</GraphTitle>
<GraphContainer>
<FullView
name="operations_per_sec" name="operations_per_sec"
fullViewOptions={false}
onClickHandler={handleGraphClick('Rate')}
widget={operationPerSecWidget} widget={operationPerSecWidget}
yAxisUnit="ops" yAxisUnit="ops"
onDragSelect={onDragSelect} opName="Rate"
isDependedDataLoaded={topLevelOperationsIsLoading}
/> />
</GraphContainer>
</>
)}
</Card>
</Col> </Col>
</Row> </Row>
<Row gutter={24}> <Row gutter={24}>
@ -367,43 +225,28 @@ function Application(): JSX.Element {
View Traces View Traces
</Button> </Button>
<Card> <TopLevelOperation
{topLevelOperationsIsError ? ( handleGraphClick={handleGraphClick}
<Typography> onDragSelect={onDragSelect}
{axios.isAxiosError(topLevelOperationsError) topLevelOperationsError={topLevelOperationsError}
? topLevelOperationsError.response?.data topLevelOperationsLoading={topLevelOperationsLoading}
: SOMETHING_WENT_WRONG} topLevelOperationsIsError={topLevelOperationsIsError}
</Typography>
) : (
<>
<GraphTitle>Error Percentage</GraphTitle>
<GraphContainer>
<FullView
name="error_percentage_%" name="error_percentage_%"
fullViewOptions={false}
onClickHandler={handleGraphClick('Error')}
widget={errorPercentageWidget} widget={errorPercentageWidget}
yAxisUnit="%" yAxisUnit="%"
onDragSelect={onDragSelect} opName="Error"
isDependedDataLoaded={topLevelOperationsIsLoading}
/> />
</GraphContainer>
</>
)}
</Card>
</Col> </Col>
<Col span={12}> <Col span={12}>
<Card> <TopOperation />
<TopOperationsTable data={topOperations || []} />
</Card>
</Col> </Col>
</Row> </Row>
</> </>
); );
} }
type ClickHandlerType = ( export type ClickHandlerType = (
ChartEvent: ChartEvent, ChartEvent: ChartEvent,
activeElements: ActiveElement[], activeElements: ActiveElement[],
chart: Chart, chart: Chart,

View File

@ -0,0 +1,93 @@
import { FeatureKeys } from 'constants/features';
import Graph from 'container/GridGraphLayout/Graph/';
import { GraphTitle } from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { latency } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
import useFeatureFlag from 'hooks/useFeatureFlag';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { ClickHandlerType } from '../Overview';
import { Button } from '../styles';
import { IServiceName } from '../types';
import { onViewTracePopupClick } from '../util';
function ServiceOverview({
onDragSelect,
handleGraphClick,
selectedTraceTags,
selectedTimeStamp,
tagFilterItems,
topLevelOperationsRoute,
}: ServiceOverviewProps): JSX.Element {
const { servicename } = useParams<IServiceName>();
const isSpanMetricEnable = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS)
?.active;
const latencyWidget = useMemo(
() =>
getWidgetQueryBuilder(
{
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: latency({
servicename,
tagFilterItems,
isSpanMetricEnable,
topLevelOperationsRoute,
}),
clickhouse_sql: [],
id: uuid(),
},
GraphTitle.LATENCY,
),
[servicename, tagFilterItems, isSpanMetricEnable, topLevelOperationsRoute],
);
return (
<>
<Button
type="default"
size="small"
id="Service_button"
onClick={onViewTracePopupClick({
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
})}
>
View Traces
</Button>
<Card>
<GraphContainer>
<Graph
name="service_latency"
onDragSelect={onDragSelect}
widget={latencyWidget}
yAxisUnit="ns"
onClickHandler={handleGraphClick('Service')}
allowClone={false}
allowDelete={false}
allowEdit={false}
/>
</GraphContainer>
</Card>
</>
);
}
interface ServiceOverviewProps {
selectedTimeStamp: number;
selectedTraceTags: string;
onDragSelect: (start: number, end: number) => void;
handleGraphClick: (type: string) => ClickHandlerType;
tagFilterItems: TagFilterItem[];
topLevelOperationsRoute: string[];
}
export default ServiceOverview;

View File

@ -0,0 +1,65 @@
import { Typography } from 'antd';
import axios from 'axios';
import Spinner from 'components/Spinner';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import Graph from 'container/GridGraphLayout/Graph/';
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
import { Widgets } from 'types/api/dashboard/getAll';
import { ClickHandlerType } from '../Overview';
function TopLevelOperation({
name,
opName,
topLevelOperationsIsError,
topLevelOperationsError,
topLevelOperationsLoading,
onDragSelect,
handleGraphClick,
widget,
yAxisUnit,
}: TopLevelOperationProps): JSX.Element {
return (
<Card>
{topLevelOperationsIsError ? (
<Typography>
{axios.isAxiosError(topLevelOperationsError)
? topLevelOperationsError.response?.data
: SOMETHING_WENT_WRONG}
</Typography>
) : (
<GraphContainer>
{topLevelOperationsLoading && (
<Spinner size="large" tip="Loading..." height="40vh" />
)}
{!topLevelOperationsLoading && (
<Graph
name={name}
widget={widget}
onClickHandler={handleGraphClick(opName)}
yAxisUnit={yAxisUnit}
onDragSelect={onDragSelect}
allowClone={false}
allowDelete={false}
allowEdit={false}
/>
)}
</GraphContainer>
)}
</Card>
);
}
interface TopLevelOperationProps {
name: string;
opName: string;
topLevelOperationsIsError: boolean;
topLevelOperationsError: unknown;
topLevelOperationsLoading: boolean;
onDragSelect: (start: number, end: number) => void;
handleGraphClick: (type: string) => ClickHandlerType;
widget: Widgets;
yAxisUnit: string;
}
export default TopLevelOperation;

View File

@ -0,0 +1,46 @@
import getTopOperations from 'api/metrics/getTopOperations';
import Spinner from 'components/Spinner';
import { Card } from 'container/MetricsApplication/styles';
import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable';
import useResourceAttribute from 'hooks/useResourceAttribute';
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
import { useMemo } from 'react';
import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/metrics/getTopOperations';
import { GlobalReducer } from 'types/reducer/globalTime';
import { Tags } from 'types/reducer/trace';
function TopOperation(): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { servicename } = useParams<{ servicename?: string }>();
const { queries } = useResourceAttribute();
const selectedTags = useMemo(
() => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [],
[queries],
);
const { data, isLoading } = useQuery<PayloadProps>({
queryKey: [minTime, maxTime, servicename, selectedTags],
queryFn: (): Promise<PayloadProps> =>
getTopOperations({
service: servicename || '',
start: minTime,
end: maxTime,
selectedTags,
}),
});
return (
<Card>
{isLoading && <Spinner size="large" tip="Loading..." height="40vh" />}
{!isLoading && <TopOperationsTable data={data || []} />}
</Card>
);
}
export default TopOperation;

View File

@ -1,3 +0,0 @@
export const legend = {
address: '{{address}}',
};

View File

@ -0,0 +1,3 @@
export interface IServiceName {
servicename: string;
}

View File

@ -0,0 +1,60 @@
export const legend = {
address: '{{address}}',
};
export const QUERYNAME_AND_EXPRESSION = ['A', 'B', 'C'];
export const LATENCY_AGGREGATEOPERATOR = ['p50', 'p90', 'p99'];
export const LATENCY_AGGREGATEOPERATOR_SPAN_METRICS = [
'hist_quantile_50',
'hist_quantile_90',
'hist_quantile_99',
];
export const OPERATION_LEGENDS = ['Operations'];
export enum FORMULA {
ERROR_PERCENTAGE = 'A*100/B',
DATABASE_CALLS_AVG_DURATION = 'A/B',
}
export enum GraphTitle {
LATENCY = 'Latency',
RATE_PER_OPS = 'Rate (ops/s)',
ERROR_PERCENTAGE = 'Error Percentage',
DATABASE_CALLS_RPS = 'Database Calls RPS',
DATABASE_CALLS_AVG_DURATION = 'Database Calls Avg Duration',
EXTERNAL_CALL_ERROR_PERCENTAGE = 'External Call Error Percentage',
EXTERNAL_CALL_DURATION = 'External Call duration',
EXTERNAL_CALL_RPS_BY_ADDRESS = 'External Call RPS(by Address)',
EXTERNAL_CALL_DURATION_BY_ADDRESS = 'External Call duration(by Address)',
}
export enum DataType {
STRING = 'string',
FLOAT64 = 'float64',
INT64 = 'int64',
}
export enum MetricsType {
Tag = 'tag',
Resource = 'resource',
}
export enum WidgetKeys {
Name = 'name',
Address = 'address',
DurationNano = 'durationNano',
StatusCode = 'status_code',
Operation = 'operation',
OperationName = 'operationName',
Service_name = 'service_name',
ServiceName = 'serviceName',
SignozLatencyCount = 'signoz_latency_count',
SignozDBLatencyCount = 'signoz_db_latency_count',
DatabaseCallCount = 'signoz_database_call_count',
DatabaseCallLatencySum = 'signoz_database_call_latency_sum',
SignozDbLatencySum = 'signoz_db_latency_sum',
SignozCallsTotal = 'signoz_calls_total',
SignozExternalCallLatencyCount = 'signoz_external_call_latency_count',
SignozExternalCallLatencySum = 'signoz_external_call_latency_sum',
Signoz_latency_bucket = 'signoz_latency_bucket',
}

View File

@ -3,12 +3,12 @@ import getFromLocalstorage from 'api/browser/localstorage/get';
import setToLocalstorage from 'api/browser/localstorage/set'; import setToLocalstorage from 'api/browser/localstorage/set';
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryBuilderKeys } from 'constants/queryBuilder'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import useDebounce from 'hooks/useDebounce'; import useDebounce from 'hooks/useDebounce';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import useUrlQueryData from 'hooks/useUrlQueryData'; import useUrlQueryData from 'hooks/useUrlQueryData';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueries, useQuery } from 'react-query'; import { useQueries } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api'; import { ErrorResponse, SuccessResponse } from 'types/api';
import { import {
BaseAutocompleteData, BaseAutocompleteData,
@ -30,6 +30,7 @@ interface UseOptionsMenuProps {
interface UseOptionsMenu { interface UseOptionsMenu {
options: OptionsQuery; options: OptionsQuery;
config: OptionsMenuConfig; config: OptionsMenuConfig;
handleOptionsChange: (newQueryData: OptionsQuery) => void;
} }
const useOptionsMenu = ({ const useOptionsMenu = ({
@ -115,16 +116,12 @@ const useOptionsMenu = ({
const { const {
data: searchedAttributesData, data: searchedAttributesData,
isFetching: isSearchedAttributesFetching, isFetching: isSearchedAttributesFetching,
} = useQuery( } = useGetAggregateKeys(
[QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedSearchText, isFocused], {
async () =>
getAggregateKeys({
...initialQueryParams, ...initialQueryParams,
searchText: debouncedSearchText, searchText: debouncedSearchText,
}),
{
enabled: isFocused,
}, },
{ queryKey: [debouncedSearchText, isFocused], enabled: isFocused },
); );
const searchedAttributeKeys = useMemo( const searchedAttributeKeys = useMemo(
@ -306,6 +303,7 @@ const useOptionsMenu = ({
return { return {
options: optionsQueryData, options: optionsQueryData,
config: optionsMenuConfig, config: optionsMenuConfig,
handleOptionsChange: handleRedirectWithOptionsData,
}; };
}; };

View File

@ -3,6 +3,8 @@ import { ReactNode } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { OrderByFilterProps } from './filters/OrderByFilter/OrderByFilter.interfaces';
export type QueryBuilderConfig = export type QueryBuilderConfig =
| { | {
queryVariant: 'static'; queryVariant: 'static';
@ -17,4 +19,5 @@ export type QueryBuilderProps = {
filterConfigs?: Partial< filterConfigs?: Partial<
Record<keyof IBuilderQuery, { isHidden: boolean; isDisabled: boolean }> Record<keyof IBuilderQuery, { isHidden: boolean; isDisabled: boolean }>
>; >;
queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode };
}; };

View File

@ -16,6 +16,7 @@ export const QueryBuilder = memo(function QueryBuilder({
panelType: newPanelType, panelType: newPanelType,
actions, actions,
filterConfigs = {}, filterConfigs = {},
queryComponents,
}: QueryBuilderProps): JSX.Element { }: QueryBuilderProps): JSX.Element {
const { const {
currentQuery, currentQuery,
@ -74,6 +75,7 @@ export const QueryBuilder = memo(function QueryBuilder({
queryVariant={config?.queryVariant || 'dropdown'} queryVariant={config?.queryVariant || 'dropdown'}
query={query} query={query}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents}
/> />
</Col> </Col>
))} ))}

View File

@ -6,4 +6,4 @@ export type QueryProps = {
isAvailableToDisable: boolean; isAvailableToDisable: boolean;
query: IBuilderQuery; query: IBuilderQuery;
queryVariant: 'static' | 'dropdown'; queryVariant: 'static' | 'dropdown';
} & Pick<QueryBuilderProps, 'filterConfigs'>; } & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;

View File

@ -36,6 +36,7 @@ export const Query = memo(function Query({
queryVariant, queryVariant,
query, query,
filterConfigs, filterConfigs,
queryComponents,
}: QueryProps): JSX.Element { }: QueryProps): JSX.Element {
const { panelType } = useQueryBuilder(); const { panelType } = useQueryBuilder();
const { const {
@ -110,6 +111,17 @@ export const Query = memo(function Query({
[handleChangeQueryData], [handleChangeQueryData],
); );
const renderOrderByFilter = useCallback((): ReactNode => {
if (queryComponents?.renderOrderBy) {
return queryComponents.renderOrderBy({
query,
onChange: handleChangeOrderByKeys,
});
}
return <OrderByFilter query={query} onChange={handleChangeOrderByKeys} />;
}, [queryComponents, query, handleChangeOrderByKeys]);
const renderAggregateEveryFilter = useCallback( const renderAggregateEveryFilter = useCallback(
(): JSX.Element | null => (): JSX.Element | null =>
!filterConfigs?.stepInterval?.isHidden ? ( !filterConfigs?.stepInterval?.isHidden ? (
@ -167,9 +179,7 @@ export const Query = memo(function Query({
<Col flex="5.93rem"> <Col flex="5.93rem">
<FilterLabel label="Order by" /> <FilterLabel label="Order by" />
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">{renderOrderByFilter()}</Col>
<OrderByFilter query={query} onChange={handleChangeOrderByKeys} />
</Col>
</Row> </Row>
</Col> </Col>
)} )}
@ -225,9 +235,7 @@ export const Query = memo(function Query({
<Col flex="5.93rem"> <Col flex="5.93rem">
<FilterLabel label="Order by" /> <FilterLabel label="Order by" />
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">{renderOrderByFilter()}</Col>
<OrderByFilter query={query} onChange={handleChangeOrderByKeys} />
</Col>
</Row> </Row>
</Col> </Col>
@ -238,11 +246,11 @@ export const Query = memo(function Query({
} }
}, [ }, [
panelType, panelType,
query,
isMetricsDataSource, isMetricsDataSource,
handleChangeHavingFilter, query,
handleChangeLimit, handleChangeLimit,
handleChangeOrderByKeys, handleChangeHavingFilter,
renderOrderByFilter,
renderAggregateEveryFilter, renderAggregateEveryFilter,
]); ]);

View File

@ -7,6 +7,7 @@ import {
selectValueDivider, selectValueDivider,
} from 'constants/queryBuilder'; } from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig'; import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import useDebounce from 'hooks/useDebounce'; import useDebounce from 'hooks/useDebounce';
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue'; import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
// ** Components // ** Components
@ -14,7 +15,7 @@ import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAut
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
import { isEqual, uniqWith } from 'lodash-es'; import { isEqual, uniqWith } from 'lodash-es';
import { memo, useCallback, useEffect, useState } from 'react'; import { memo, useCallback, useEffect, useState } from 'react';
import { useQuery, useQueryClient } from 'react-query'; import { useQueryClient } from 'react-query';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { SelectOption } from 'types/common/select'; import { SelectOption } from 'types/common/select';
@ -38,16 +39,15 @@ export const GroupByFilter = memo(function GroupByFilter({
const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY); const debouncedValue = useDebounce(searchText, DEBOUNCE_DELAY);
const { isFetching } = useQuery( const { isFetching } = useGetAggregateKeys(
[QueryBuilderKeys.GET_AGGREGATE_KEYS, debouncedValue, isFocused], {
async () =>
getAggregateKeys({
aggregateAttribute: query.aggregateAttribute.key, aggregateAttribute: query.aggregateAttribute.key,
dataSource: query.dataSource, dataSource: query.dataSource,
aggregateOperator: query.aggregateOperator, aggregateOperator: query.aggregateOperator,
searchText: debouncedValue, searchText: debouncedValue,
}), },
{ {
queryKey: [debouncedValue, isFocused],
enabled: !disabled && isFocused, enabled: !disabled && isFocused,
onSuccess: (data) => { onSuccess: (data) => {
const keys = query.groupBy.reduce<string[]>((acc, item) => { const keys = query.groupBy.reduce<string[]>((acc, item) => {

View File

@ -1,208 +1,57 @@
import { Select, Spin } from 'antd'; import { Select, Spin } from 'antd';
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys'; import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
import { QueryBuilderKeys } from 'constants/queryBuilder'; import { useMemo } from 'react';
import { IOption } from 'hooks/useResourceAttribute/types';
import { uniqWith } from 'lodash-es';
import * as Papa from 'papaparse';
import { useCallback, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { selectStyle } from '../QueryBuilderSearch/config'; import { selectStyle } from '../QueryBuilderSearch/config';
import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils';
import { FILTERS } from './config';
import { OrderByFilterProps } from './OrderByFilter.interfaces'; import { OrderByFilterProps } from './OrderByFilter.interfaces';
import { import { useOrderByFilter } from './useOrderByFilter';
checkIfKeyPresent,
getLabelFromValue,
mapLabelValuePairs,
orderByValueDelimiter,
splitOrderByFromString,
transformToOrderByStringValues,
} from './utils';
export function OrderByFilter({ export function OrderByFilter({
query, query,
onChange, onChange,
}: OrderByFilterProps): JSX.Element { }: OrderByFilterProps): JSX.Element {
const [searchText, setSearchText] = useState<string>(''); const {
const [selectedValue, setSelectedValue] = useState<IOption[]>( debouncedSearchText,
transformToOrderByStringValues(query.orderBy), selectedValue,
); aggregationOptions,
generateOptions,
createOptions,
handleChange,
handleSearchKeys,
} = useOrderByFilter({ query, onChange });
const { data, isFetching } = useQuery( const { data, isFetching } = useGetAggregateKeys(
[QueryBuilderKeys.GET_AGGREGATE_KEYS, searchText], {
async () =>
getAggregateKeys({
aggregateAttribute: query.aggregateAttribute.key, aggregateAttribute: query.aggregateAttribute.key,
dataSource: query.dataSource, dataSource: query.dataSource,
aggregateOperator: query.aggregateOperator, aggregateOperator: query.aggregateOperator,
searchText, searchText: debouncedSearchText,
}),
{ enabled: !!query.aggregateAttribute.key, keepPreviousData: true },
);
const handleSearchKeys = useCallback(
(searchText: string): void => setSearchText(searchText),
[],
);
const noAggregationOptions = useMemo(
() =>
data?.payload?.attributeKeys
? mapLabelValuePairs(data?.payload?.attributeKeys).flat()
: [],
[data?.payload?.attributeKeys],
);
const aggregationOptions = useMemo(
() =>
mapLabelValuePairs(query.groupBy)
.flat()
.concat([
{
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`,
value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.ASC}`,
}, },
{ {
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`, enabled: !!query.aggregateAttribute.key,
value: `${query.aggregateOperator}(${query.aggregateAttribute.key})${orderByValueDelimiter}${FILTERS.DESC}`, keepPreviousData: true,
}, },
]),
[query.aggregateAttribute.key, query.aggregateOperator, query.groupBy],
); );
const customValue: IOption[] = useMemo(() => {
if (!searchText) return [];
return [
{
label: `${searchText} ${FILTERS.ASC}`,
value: `${searchText}${orderByValueDelimiter}${FILTERS.ASC}`,
},
{
label: `${searchText} ${FILTERS.DESC}`,
value: `${searchText}${orderByValueDelimiter}${FILTERS.DESC}`,
},
];
}, [searchText]);
const optionsData = useMemo(() => { const optionsData = useMemo(() => {
const keyOptions = createOptions(data?.payload?.attributeKeys || []);
const groupByOptions = createOptions(query.groupBy);
const options = const options =
query.aggregateOperator === MetricAggregateOperator.NOOP query.aggregateOperator === MetricAggregateOperator.NOOP
? noAggregationOptions ? keyOptions
: aggregationOptions; : [...groupByOptions, ...aggregationOptions];
const resultOption = [...customValue, ...options]; return generateOptions(options);
return resultOption.filter(
(option) =>
!getLabelFromValue(selectedValue).includes(
getRemoveOrderFromValue(option.value),
),
);
}, [ }, [
aggregationOptions, aggregationOptions,
customValue, createOptions,
noAggregationOptions, data?.payload?.attributeKeys,
generateOptions,
query.aggregateOperator, query.aggregateOperator,
selectedValue, query.groupBy,
]); ]);
const getUniqValues = useCallback((values: IOption[]): IOption[] => {
const modifiedValues = values.map((item) => {
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
if (!match) return { label: item.label, value: item.value };
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
const [_, order] = match.data.flat() as string[];
if (order)
return {
label: item.label,
value: item.value,
};
return {
label: `${item.value} ${FILTERS.ASC}`,
value: `${item.value}${orderByValueDelimiter}${FILTERS.ASC}`,
};
});
return uniqWith(
modifiedValues,
(current, next) =>
getRemoveOrderFromValue(current.value) ===
getRemoveOrderFromValue(next.value),
);
}, []);
const getValidResult = useCallback(
(result: IOption[]): IOption[] =>
result.reduce<IOption[]>((acc, item) => {
if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc;
if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) {
const splittedOrderBy = splitOrderByFromString(item.value);
if (splittedOrderBy) {
acc.push({
label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`,
value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`,
});
return acc;
}
}
acc.push(item);
return acc;
}, []),
[],
);
const handleChange = (values: IOption[]): void => {
const validResult = getValidResult(values);
const result = getUniqValues(validResult);
const orderByValues: OrderByPayload[] = result.map((item) => {
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
if (!match) {
return {
columnName: item.value,
order: 'asc',
};
}
const [columnName, order] = match.data.flat() as string[];
const columnNameValue = checkIfKeyPresent(
columnName,
query.aggregateAttribute.key,
)
? '#SIGNOZ_VALUE'
: columnName;
const orderValue = order ?? 'asc';
return {
columnName: columnNameValue,
order: orderValue,
};
});
const selectedValue: IOption[] = orderByValues.map((item) => ({
label: `${item.columnName} ${item.order}`,
value: `${item.columnName} ${item.order}`,
}));
setSelectedValue(selectedValue);
setSearchText('');
onChange(orderByValues);
};
const isDisabledSelect = useMemo( const isDisabledSelect = useMemo(
() => () =>
!query.aggregateAttribute.key || !query.aggregateAttribute.key ||

View File

@ -0,0 +1 @@
export const SIGNOZ_VALUE = '#SIGNOZ_VALUE';

View File

@ -0,0 +1,199 @@
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import useDebounce from 'hooks/useDebounce';
import { IOption } from 'hooks/useResourceAttribute/types';
import { isEqual, uniqWith } from 'lodash-es';
import * as Papa from 'papaparse';
import { useCallback, useMemo, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData';
import { getRemoveOrderFromValue } from '../QueryBuilderSearch/utils';
import { FILTERS } from './config';
import { SIGNOZ_VALUE } from './constants';
import { OrderByFilterProps } from './OrderByFilter.interfaces';
import {
getLabelFromValue,
mapLabelValuePairs,
orderByValueDelimiter,
splitOrderByFromString,
transformToOrderByStringValues,
} from './utils';
type UseOrderByFilterResult = {
searchText: string;
debouncedSearchText: string;
selectedValue: IOption[];
aggregationOptions: IOption[];
generateOptions: (options: IOption[]) => IOption[];
createOptions: (data: BaseAutocompleteData[]) => IOption[];
handleChange: (values: IOption[]) => void;
handleSearchKeys: (search: string) => void;
};
export const useOrderByFilter = ({
query,
onChange,
}: OrderByFilterProps): UseOrderByFilterResult => {
const [searchText, setSearchText] = useState<string>('');
const debouncedSearchText = useDebounce(searchText, DEBOUNCE_DELAY);
const handleSearchKeys = useCallback(
(searchText: string): void => setSearchText(searchText),
[],
);
const getUniqValues = useCallback((values: IOption[]): IOption[] => {
const modifiedValues = values.map((item) => {
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
if (!match) return { label: item.label, value: item.value };
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars
const [_, order] = match.data.flat() as string[];
if (order)
return {
label: item.label,
value: item.value,
};
return {
label: `${item.value} ${FILTERS.ASC}`,
value: `${item.value}${orderByValueDelimiter}${FILTERS.ASC}`,
};
});
return uniqWith(
modifiedValues,
(current, next) =>
getRemoveOrderFromValue(current.value) ===
getRemoveOrderFromValue(next.value),
);
}, []);
const customValue: IOption[] = useMemo(() => {
if (!searchText) return [];
return [
{
label: `${searchText} ${FILTERS.ASC}`,
value: `${searchText}${orderByValueDelimiter}${FILTERS.ASC}`,
},
{
label: `${searchText} ${FILTERS.DESC}`,
value: `${searchText}${orderByValueDelimiter}${FILTERS.DESC}`,
},
];
}, [searchText]);
const selectedValue = useMemo(() => transformToOrderByStringValues(query), [
query,
]);
const generateOptions = useCallback(
(options: IOption[]): IOption[] => {
const currentCustomValue = options.find(
(keyOption) =>
getRemoveOrderFromValue(keyOption.value) === debouncedSearchText,
)
? []
: customValue;
const result = [...currentCustomValue, ...options];
const uniqResult = uniqWith(result, isEqual);
return uniqResult.filter(
(option) =>
!getLabelFromValue(selectedValue).includes(
getRemoveOrderFromValue(option.value),
),
);
},
[customValue, debouncedSearchText, selectedValue],
);
const getValidResult = useCallback(
(result: IOption[]): IOption[] =>
result.reduce<IOption[]>((acc, item) => {
if (item.value === FILTERS.ASC || item.value === FILTERS.DESC) return acc;
if (item.value.includes(FILTERS.ASC) || item.value.includes(FILTERS.DESC)) {
const splittedOrderBy = splitOrderByFromString(item.value);
if (splittedOrderBy) {
acc.push({
label: `${splittedOrderBy.columnName} ${splittedOrderBy.order}`,
value: `${splittedOrderBy.columnName}${orderByValueDelimiter}${splittedOrderBy.order}`,
});
return acc;
}
}
acc.push(item);
return acc;
}, []),
[],
);
const handleChange = (values: IOption[]): void => {
const validResult = getValidResult(values);
const result = getUniqValues(validResult);
const orderByValues: OrderByPayload[] = result.map((item) => {
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
if (!match) {
return {
columnName: item.value,
order: 'asc',
};
}
const [columnName, order] = match.data.flat() as string[];
const columnNameValue =
columnName === SIGNOZ_VALUE ? SIGNOZ_VALUE : columnName;
const orderValue = order ?? 'asc';
return {
columnName: columnNameValue,
order: orderValue,
};
});
setSearchText('');
onChange(orderByValues);
};
const createOptions = useCallback(
(data: BaseAutocompleteData[]): IOption[] => mapLabelValuePairs(data).flat(),
[],
);
const aggregationOptions = useMemo(
() => [
{
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.ASC}`,
value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.ASC}`,
},
{
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${FILTERS.DESC}`,
value: `${SIGNOZ_VALUE}${orderByValueDelimiter}${FILTERS.DESC}`,
},
],
[query],
);
return {
searchText,
debouncedSearchText,
selectedValue,
aggregationOptions,
createOptions,
handleChange,
handleSearchKeys,
generateOptions,
};
};

View File

@ -1,31 +1,32 @@
import { IOption } from 'hooks/useResourceAttribute/types'; import { IOption } from 'hooks/useResourceAttribute/types';
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
import * as Papa from 'papaparse'; import * as Papa from 'papaparse';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { OrderByPayload } from 'types/api/queryBuilder/queryBuilderData'; import {
IBuilderQuery,
OrderByPayload,
} from 'types/api/queryBuilder/queryBuilderData';
import { FILTERS } from './config'; import { FILTERS } from './config';
import { SIGNOZ_VALUE } from './constants';
export const orderByValueDelimiter = '|'; export const orderByValueDelimiter = '|';
export const transformToOrderByStringValues = ( export const transformToOrderByStringValues = (
orderBy: OrderByPayload[], query: IBuilderQuery,
): IOption[] => { ): IOption[] => {
const prepareSelectedValue: IOption[] = orderBy.reduce<IOption[]>( const prepareSelectedValue: IOption[] = query.orderBy.map((item) => {
(acc, item) => { if (item.columnName === SIGNOZ_VALUE) {
if (item.columnName === '#SIGNOZ_VALUE') return acc; return {
label: `${query.aggregateOperator}(${query.aggregateAttribute.key}) ${item.order}`,
value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
};
}
const option: IOption = { return {
label: `${item.columnName} ${item.order}`, label: `${item.columnName} ${item.order}`,
value: `${item.columnName}${orderByValueDelimiter}${item.order}`, value: `${item.columnName}${orderByValueDelimiter}${item.order}`,
}; };
});
acc.push(option);
return acc;
},
[],
);
return prepareSelectedValue; return prepareSelectedValue;
}; };
@ -34,20 +35,15 @@ export function mapLabelValuePairs(
arr: BaseAutocompleteData[], arr: BaseAutocompleteData[],
): Array<IOption>[] { ): Array<IOption>[] {
return arr.map((item) => { return arr.map((item) => {
const label = transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
});
const value = item.key; const value = item.key;
return [ return [
{ {
label: `${label} asc`, label: `${value} ${FILTERS.ASC}`,
value: `${value}${orderByValueDelimiter}asc`, value: `${value}${orderByValueDelimiter}${FILTERS.ASC}`,
}, },
{ {
label: `${label} desc`, label: `${value} ${FILTERS.DESC}`,
value: `${value}${orderByValueDelimiter}desc`, value: `${value}${orderByValueDelimiter}${FILTERS.DESC}`,
}, },
]; ];
}); });
@ -58,6 +54,7 @@ export function getLabelFromValue(arr: IOption[]): string[] {
const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter }); const match = Papa.parse(item.value, { delimiter: orderByValueDelimiter });
if (match) { if (match) {
const [key] = match.data as string[]; const [key] = match.data as string[];
return key[0]; return key[0];
} }

View File

@ -6,6 +6,8 @@ import { useOptionsMenu } from 'container/OptionsMenu';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Pagination, URL_PAGINATION } from 'hooks/queryPagination'; import { Pagination, URL_PAGINATION } from 'hooks/queryPagination';
import useDragColumns from 'hooks/useDragColumns';
import { getDraggedColumns } from 'hooks/useDragColumns/utils';
import useUrlQueryData from 'hooks/useUrlQueryData'; import useUrlQueryData from 'hooks/useUrlQueryData';
import history from 'lib/history'; import history from 'lib/history';
import { RowData } from 'lib/query/createTableColumnsFromQuery'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
@ -37,6 +39,10 @@ function ListView(): JSX.Element {
}, },
}); });
const { draggedColumns, onDragColumns } = useDragColumns<RowData>(
LOCALSTORAGE.TRACES_LIST_COLUMNS,
);
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>( const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
URL_PAGINATION, URL_PAGINATION,
); );
@ -82,9 +88,10 @@ function ListView(): JSX.Element {
queryTableDataResult, queryTableDataResult,
]); ]);
const columns = useMemo(() => getListColumns(options?.selectColumns || []), [ const columns = useMemo(() => {
options?.selectColumns, const updatedColumns = getListColumns(options?.selectColumns || []);
]); return getDraggedColumns(updatedColumns, draggedColumns);
}, [options?.selectColumns, draggedColumns]);
const transformedQueryTableData = useMemo( const transformedQueryTableData = useMemo(
() => transformDataWithDate(queryTableData) || [], () => transformDataWithDate(queryTableData) || [],
@ -106,6 +113,12 @@ function ListView(): JSX.Element {
[], [],
); );
const handleDragColumn = useCallback(
(fromIndex: number, toIndex: number) =>
onDragColumns(columns, fromIndex, toIndex),
[columns, onDragColumns],
);
return ( return (
<Container> <Container>
<TraceExplorerControls <TraceExplorerControls
@ -127,6 +140,7 @@ function ListView(): JSX.Element {
dataSource={transformedQueryTableData} dataSource={transformedQueryTableData}
columns={columns} columns={columns}
onRow={handleRow} onRow={handleRow}
onDragColumn={handleDragColumn}
/> />
)} )}
</Container> </Container>

View File

@ -1,10 +1,12 @@
import { Button } from 'antd'; import { Button } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder'; import { QueryBuilder } from 'container/QueryBuilder';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { ButtonWrapper, Container } from './styles'; import { ButtonWrapper, Container } from './styles';
@ -22,6 +24,22 @@ function QuerySection(): JSX.Element {
return config; return config;
}, []); }, []);
const renderOrderBy = useCallback(
({ query, onChange }: OrderByFilterProps) => (
<ExplorerOrderBy query={query} onChange={onChange} />
),
[],
);
const queryComponents = useMemo((): QueryBuilderProps['queryComponents'] => {
const shouldRenderCustomOrderBy =
panelTypes === PANEL_TYPES.LIST || panelTypes === PANEL_TYPES.TRACE;
return {
...(shouldRenderCustomOrderBy ? { renderOrderBy } : {}),
};
}, [panelTypes, renderOrderBy]);
return ( return (
<Container> <Container>
<QueryBuilder <QueryBuilder
@ -31,6 +49,7 @@ function QuerySection(): JSX.Element {
initialDataSource: DataSource.TRACES, initialDataSource: DataSource.TRACES,
}} }}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents}
actions={ actions={
<ButtonWrapper> <ButtonWrapper>
<Button onClick={handleRunQuery} type="primary"> <Button onClick={handleRunQuery} type="primary">

View File

@ -0,0 +1,53 @@
import { Space } from 'antd';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { QueryTable } from 'container/QueryTable';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
function TableView(): JSX.Element {
const { stagedQuery, panelType } = useQueryBuilder();
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { data, isLoading } = useGetQueryRange(
{
query: stagedQuery || initialQueriesMap.traces,
graphType: panelType || PANEL_TYPES.TABLE,
selectedTime: 'GLOBAL_TIME',
globalSelectedInterval: globalSelectedTime,
params: {
dataSource: 'traces',
},
},
{
queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE,
globalSelectedTime,
maxTime,
minTime,
stagedQuery,
],
enabled: !!stagedQuery && panelType === PANEL_TYPES.TABLE,
},
);
return (
<Space.Compact block direction="vertical">
<QueryTable
query={stagedQuery || initialQueriesMap.traces}
queryTableData={data?.payload.data.newResult.data.result || []}
loading={isLoading}
/>
</Space.Compact>
);
}
export default memo(TableView);

View File

@ -1,6 +1,4 @@
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
import { getAttributesValues } from 'api/queryBuilder/getAttributesValues'; import { getAttributesValues } from 'api/queryBuilder/getAttributesValues';
import { QueryBuilderKeys } from 'constants/queryBuilder';
import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig'; import { DEBOUNCE_DELAY } from 'constants/queryBuilderFilterConfig';
import { import {
getRemovePrefixFromKey, getRemovePrefixFromKey,
@ -10,12 +8,13 @@ import {
import useDebounceValue from 'hooks/useDebounce'; import useDebounceValue from 'hooks/useDebounce';
import { isEqual, uniqWith } from 'lodash-es'; import { isEqual, uniqWith } from 'lodash-es';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from 'react-query';
import { useDebounce } from 'react-use'; import { useDebounce } from 'react-use';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { useGetAggregateKeys } from './useGetAggregateKeys';
type IuseFetchKeysAndValues = { type IuseFetchKeysAndValues = {
keys: BaseAutocompleteData[]; keys: BaseAutocompleteData[];
results: string[]; results: string[];
@ -71,19 +70,15 @@ export const useFetchKeysAndValues = (
], ],
); );
const { data, isFetching, status } = useQuery( const { data, isFetching, status } = useGetAggregateKeys(
[QueryBuilderKeys.GET_AGGREGATE_KEYS, searchParams], {
async () =>
getAggregateKeys({
searchText: searchKey, searchText: searchKey,
dataSource: query.dataSource, dataSource: query.dataSource,
aggregateOperator: query.aggregateOperator, aggregateOperator: query.aggregateOperator,
aggregateAttribute: query.aggregateAttribute.key, aggregateAttribute: query.aggregateAttribute.key,
tagType: query.aggregateAttribute.type ?? null, tagType: query.aggregateAttribute.type ?? null,
}),
{
enabled: isQueryEnabled,
}, },
{ queryKey: [searchParams], enabled: isQueryEnabled },
); );
/** /**

View File

@ -0,0 +1,34 @@
import { getAggregateKeys } from 'api/queryBuilder/getAttributeKeys';
import { QueryBuilderKeys } from 'constants/queryBuilder';
import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
type UseGetAttributeKeys = (
requestData: IGetAttributeKeysPayload,
options?: UseQueryOptions<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
>,
) => UseQueryResult<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
>;
export const useGetAggregateKeys: UseGetAttributeKeys = (
requestData,
options,
) => {
const queryKey = useMemo(() => {
if (options?.queryKey && Array.isArray(options.queryKey)) {
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, ...options.queryKey];
}
return [QueryBuilderKeys.GET_AGGREGATE_KEYS, requestData];
}, [options?.queryKey, requestData]);
return useQuery<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse>({
queryKey,
queryFn: () => getAggregateKeys(requestData),
...options,
});
};

View File

@ -0,0 +1,7 @@
export const COLUMNS = 'columns';
export const dragColumnParams = {
ignoreSelector: '.react-resizable-handle',
nodeSelector: 'th',
handleSelector: '.dragHandler',
};

View File

@ -0,0 +1,75 @@
import { ColumnsType } from 'antd/es/table';
import getFromLocalstorage from 'api/browser/localstorage/get';
import setToLocalstorage from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { useCallback, useEffect, useMemo } from 'react';
import { COLUMNS } from './configs';
import { UseDragColumns } from './types';
const useDragColumns = <T>(storageKey: LOCALSTORAGE): UseDragColumns<T> => {
const {
query: draggedColumnsQuery,
queryData: draggedColumns,
redirectWithQuery: redirectWithDraggedColumns,
} = useUrlQueryData<ColumnsType<T>>(COLUMNS, []);
const localStorageDraggedColumns = useMemo(
() => getFromLocalstorage(storageKey),
[storageKey],
);
const handleRedirectWithDraggedColumns = useCallback(
(columns: ColumnsType<T>) => {
redirectWithDraggedColumns(columns);
setToLocalstorage(storageKey, JSON.stringify(columns));
},
[storageKey, redirectWithDraggedColumns],
);
const onDragColumns = useCallback(
(columns: ColumnsType<T>, fromIndex: number, toIndex: number): void => {
const columnsData = [...columns];
const item = columnsData.splice(fromIndex, 1)[0];
columnsData.splice(toIndex, 0, item);
handleRedirectWithDraggedColumns(columnsData);
},
[handleRedirectWithDraggedColumns],
);
const redirectWithNewDraggedColumns = useCallback(
async (localStorageColumns: string) => {
let nextDraggedColumns: ColumnsType<T> = [];
try {
const parsedDraggedColumns = await JSON.parse(localStorageColumns);
nextDraggedColumns = parsedDraggedColumns;
} catch (e) {
console.log('error while parsing json');
} finally {
redirectWithDraggedColumns(nextDraggedColumns);
}
},
[redirectWithDraggedColumns],
);
useEffect(() => {
if (draggedColumnsQuery || !localStorageDraggedColumns) return;
redirectWithNewDraggedColumns(localStorageDraggedColumns);
}, [
draggedColumnsQuery,
localStorageDraggedColumns,
redirectWithNewDraggedColumns,
]);
return {
draggedColumns,
onDragColumns,
};
};
export default useDragColumns;

View File

@ -0,0 +1,10 @@
import { ColumnsType } from 'antd/es/table';
export type UseDragColumns<T> = {
draggedColumns: ColumnsType<T>;
onDragColumns: (
columns: ColumnsType<T>,
fromIndex: number,
toIndex: number,
) => void;
};

View File

@ -0,0 +1,37 @@
import { ColumnsType } from 'antd/es/table';
const filterColumns = <T>(
initialColumns: ColumnsType<T>,
findColumns: ColumnsType<T>,
isColumnExist = true,
): ColumnsType<T> =>
initialColumns.filter(({ title: columnTitle }) => {
const column = findColumns.find(({ title }) => title === columnTitle);
return isColumnExist ? !!column : !column;
});
export const getDraggedColumns = <T>(
currentColumns: ColumnsType<T>,
draggedColumns: ColumnsType<T>,
): ColumnsType<T> => {
if (draggedColumns.length) {
const actualDruggedColumns = filterColumns<T>(draggedColumns, currentColumns);
const newColumns = filterColumns<T>(
currentColumns,
actualDruggedColumns,
false,
);
return [...actualDruggedColumns, ...newColumns].reduce((acc, { title }) => {
const column = currentColumns.find(
({ title: columnTitle }) => title === columnTitle,
);
if (column) return [...acc, column];
return acc;
}, [] as ColumnsType<T>);
}
return currentColumns;
};

View File

@ -2,11 +2,16 @@ import { Tabs } from 'antd';
import axios from 'axios'; import axios from 'axios';
import ExplorerCard from 'components/ExplorerCard'; import ExplorerCard from 'components/ExplorerCard';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import {
initialAutocompleteData,
initialQueriesMap,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import ExportPanel from 'container/ExportPanel'; import ExportPanel from 'container/ExportPanel';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants';
import QuerySection from 'container/TracesExplorer/QuerySection'; import QuerySection from 'container/TracesExplorer/QuerySection';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
@ -17,6 +22,7 @@ import history from 'lib/history';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { generatePath } from 'react-router-dom'; import { generatePath } from 'react-router-dom';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { ActionsWrapper, Container } from './styles'; import { ActionsWrapper, Container } from './styles';
@ -29,6 +35,7 @@ function TracesExplorer(): JSX.Element {
currentQuery, currentQuery,
panelType, panelType,
updateAllQueriesOperators, updateAllQueriesOperators,
updateQueriesData,
redirectWithQueryBuilderData, redirectWithQueryBuilderData,
} = useQueryBuilder(); } = useQueryBuilder();
@ -141,26 +148,42 @@ function TracesExplorer(): JSX.Element {
[exportDefaultQuery, notifications, updateDashboard], [exportDefaultQuery, notifications, updateDashboard],
); );
const handleTabChange = useCallback( const getUpdateQuery = useCallback(
(newPanelType: string): void => { (newPanelType: GRAPH_TYPES): Query => {
if (panelType === newPanelType) return; let query = updateAllQueriesOperators(
const query = updateAllQueriesOperators(
currentQuery, currentQuery,
newPanelType as GRAPH_TYPES, newPanelType,
DataSource.TRACES, DataSource.TRACES,
); );
if (
newPanelType === PANEL_TYPES.LIST ||
newPanelType === PANEL_TYPES.TRACE
) {
query = updateQueriesData(query, 'queryData', (item) => ({
...item,
orderBy: item.orderBy.filter((item) => item.columnName !== SIGNOZ_VALUE),
aggregateAttribute: initialAutocompleteData,
}));
}
return query;
},
[currentQuery, updateAllQueriesOperators, updateQueriesData],
);
const handleTabChange = useCallback(
(type: string): void => {
const newPanelType = type as GRAPH_TYPES;
if (panelType === newPanelType) return;
const query = getUpdateQuery(newPanelType);
redirectWithQueryBuilderData(query, { redirectWithQueryBuilderData(query, {
[queryParamNamesMap.panelTypes]: newPanelType, [queryParamNamesMap.panelTypes]: newPanelType,
}); });
}, },
[ [getUpdateQuery, panelType, redirectWithQueryBuilderData],
currentQuery,
panelType,
redirectWithQueryBuilderData,
updateAllQueriesOperators,
],
); );
useShareBuilderUrl(defaultQuery); useShareBuilderUrl(defaultQuery);

View File

@ -3,6 +3,7 @@ import TabLabel from 'components/TabLabel';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import TimeSeriesView from 'container/TimeSeriesView'; import TimeSeriesView from 'container/TimeSeriesView';
import ListView from 'container/TracesExplorer/ListView'; import ListView from 'container/TracesExplorer/ListView';
import TableView from 'container/TracesExplorer/TableView';
import TracesView from 'container/TracesExplorer/TracesView'; import TracesView from 'container/TracesExplorer/TracesView';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@ -42,4 +43,9 @@ export const getTabsItems = ({
key: PANEL_TYPES.TIME_SERIES, key: PANEL_TYPES.TIME_SERIES,
children: <TimeSeriesView dataSource={DataSource.TRACES} />, children: <TimeSeriesView dataSource={DataSource.TRACES} />,
}, },
{
label: 'Table View',
key: PANEL_TYPES.TABLE,
children: <TableView />,
},
]; ];

View File

@ -70,6 +70,7 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
handleRunQuery: () => {}, handleRunQuery: () => {},
resetStagedQuery: () => {}, resetStagedQuery: () => {},
updateAllQueriesOperators: () => initialQueriesMap.metrics, updateAllQueriesOperators: () => initialQueriesMap.metrics,
updateQueriesData: () => initialQueriesMap.metrics,
initQueryBuilderData: () => {}, initQueryBuilderData: () => {},
}); });
@ -222,6 +223,22 @@ export function QueryBuilderProvider({
[getElementWithActualOperator], [getElementWithActualOperator],
); );
const updateQueriesData = useCallback(
<T extends keyof QueryBuilderData>(
query: Query,
type: T,
updateCallback: (
item: QueryBuilderData[T][number],
index: number,
) => QueryBuilderData[T][number],
): Query => {
const result = query.builder[type].map(updateCallback);
return { ...query, builder: { ...query.builder, [type]: result } };
},
[],
);
const removeQueryBuilderEntityByIndex = useCallback( const removeQueryBuilderEntityByIndex = useCallback(
(type: keyof QueryBuilderData, index: number) => { (type: keyof QueryBuilderData, index: number) => {
setCurrentQuery((prevState) => { setCurrentQuery((prevState) => {
@ -567,6 +584,7 @@ export function QueryBuilderProvider({
handleRunQuery, handleRunQuery,
resetStagedQuery, resetStagedQuery,
updateAllQueriesOperators, updateAllQueriesOperators,
updateQueriesData,
initQueryBuilderData, initQueryBuilderData,
}), }),
[ [
@ -588,6 +606,7 @@ export function QueryBuilderProvider({
handleRunQuery, handleRunQuery,
resetStagedQuery, resetStagedQuery,
updateAllQueriesOperators, updateAllQueriesOperators,
updateQueriesData,
initQueryBuilderData, initQueryBuilderData,
], ],
); );

View File

@ -6,6 +6,6 @@ export interface PayloadProps {
} }
export interface Props { export interface Props {
email?: string; email: string;
path?: string; path?: string;
} }

View File

@ -5,10 +5,7 @@ export interface PayloadProps {
result: QueryData[]; result: QueryData[];
} }
export type ListItem = { export type ListItem = { timestamp: string; data: Omit<ILog, 'timestamp'> };
timestamp: string;
data: Omit<ILog, 'timestamp'>;
};
export interface QueryData { export interface QueryData {
metric: { metric: {

View File

@ -192,6 +192,14 @@ export type QueryBuilderContextType = {
panelType: GRAPH_TYPES, panelType: GRAPH_TYPES,
dataSource: DataSource, dataSource: DataSource,
) => Query; ) => Query;
updateQueriesData: <T extends keyof QueryBuilderData>(
query: Query,
type: T,
updateCallback: (
item: QueryBuilderData[T][number],
index: number,
) => QueryBuilderData[T][number],
) => Query;
initQueryBuilderData: (query: Query) => void; initQueryBuilderData: (query: Query) => void;
}; };

View File

@ -0,0 +1,18 @@
import { Dashboard, IDashboardVariable } from 'types/api/dashboard/getAll';
export const getSelectedDashboard = (dashboard: Dashboard[]): Dashboard => {
if (dashboard.length > 0) {
return dashboard[0];
}
return {} as Dashboard;
};
export const getSelectedDashboardVariable = (
dashboard: Dashboard[],
): Record<string, IDashboardVariable> => {
if (dashboard.length > 0) {
const { variables } = dashboard[0].data;
return variables;
}
return {};
};

View File

@ -3719,6 +3719,14 @@ babel-preset-react-app@^10.0.0:
babel-plugin-macros "^3.1.0" babel-plugin-macros "^3.1.0"
babel-plugin-transform-react-remove-prop-types "^0.4.24" babel-plugin-transform-react-remove-prop-types "^0.4.24"
babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@ -4474,6 +4482,11 @@ core-js-compat@^3.25.1:
dependencies: dependencies:
browserslist "^4.21.5" browserslist "^4.21.5"
core-js@^2.4.0:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-util-is@~1.0.0: core-util-is@~1.0.0:
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz"
@ -9999,7 +10012,7 @@ prompts@^2.0.1, prompts@^2.4.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@15, prop-types@15.x, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: prop-types@15, prop-types@15.x, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -10513,6 +10526,14 @@ react-dom@18.2.0:
loose-envify "^1.1.0" loose-envify "^1.1.0"
scheduler "^0.23.0" scheduler "^0.23.0"
react-drag-listview@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-drag-listview/-/react-drag-listview-2.0.0.tgz#b8e7ec5f980ecbbf3abb85f50db0b03cd764edbf"
integrity sha512-7Apx/1Xt4qu+JHHP0rH6aLgZgS7c2MX8ocHVGCi03KfeIWEu0t14MhT3boQKM33l5eJrE/IWfExFTvoYq22fsg==
dependencies:
babel-runtime "^6.26.0"
prop-types "^15.5.8"
react-draggable@^4.0.0, react-draggable@^4.0.3: react-draggable@^4.0.0, react-draggable@^4.0.3:
version "4.4.5" version "4.4.5"
resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz" resolved "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz"
@ -10808,6 +10829,11 @@ regenerator-runtime@0.13.9:
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
regenerator-runtime@^0.13.11: regenerator-runtime@^0.13.11:
version "0.13.11" version "0.13.11"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz"

View File

@ -13,7 +13,7 @@ https://github.com/SigNoz/signoz/blob/main/CONTRIBUTING.md#to-run-clickhouse-set
- Change the alertmanager section in `signoz/deploy/docker/clickhouse-setup/docker-compose.yaml` as follows: - Change the alertmanager section in `signoz/deploy/docker/clickhouse-setup/docker-compose.yaml` as follows:
```console ```console
alertmanager: alertmanager:
image: signoz/alertmanager:0.23.0-0.1 image: signoz/alertmanager:0.23.1
volumes: volumes:
- ./data/alertmanager:/data - ./data/alertmanager:/data
expose: expose:

View File

@ -4161,7 +4161,7 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam
// ("order", "/fetch/{Id}") // ("order", "/fetch/{Id}")
// ("order", "/order") // ("order", "/order")
seriesToPoints := make(map[string][]v3.Point) seriesToPoints := make(map[string][]v3.Point)
var keys []string
// seriesToAttrs is a mapping of key to a map of attribute key to attribute value // seriesToAttrs is a mapping of key to a map of attribute key to attribute value
// for each series. This is used to populate the series' attributes // for each series. This is used to populate the series' attributes
// For instance, for the above example, the seriesToAttrs will be // For instance, for the above example, the seriesToAttrs will be
@ -4182,12 +4182,15 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam
groupBy, groupAttributes, metricPoint := readRow(vars, columnNames) groupBy, groupAttributes, metricPoint := readRow(vars, columnNames)
sort.Strings(groupBy) sort.Strings(groupBy)
key := strings.Join(groupBy, "") key := strings.Join(groupBy, "")
if _, exists := seriesToAttrs[key]; !exists {
keys = append(keys, key)
}
seriesToAttrs[key] = groupAttributes seriesToAttrs[key] = groupAttributes
seriesToPoints[key] = append(seriesToPoints[key], metricPoint) seriesToPoints[key] = append(seriesToPoints[key], metricPoint)
} }
var seriesList []*v3.Series var seriesList []*v3.Series
for key := range seriesToPoints { for _, key := range keys {
points := seriesToPoints[key] points := seriesToPoints[key]
// find the grouping sets point for the series // find the grouping sets point for the series

View File

@ -250,7 +250,7 @@ func (q *querier) runBuilderQueries(ctx context.Context, params *v3.QueryRangePa
} }
if builderQuery.DataSource == v3.DataSourceTraces { if builderQuery.DataSource == v3.DataSourceTraces {
query, err := tracesV3.PrepareTracesQuery(params.Start, params.End, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, keys) query, err := tracesV3.PrepareTracesQuery(params.Start, params.End, params.CompositeQuery.PanelType, builderQuery, keys, "")
if err != nil { if err != nil {
errQueriesByName[queryName] = err.Error() errQueriesByName[queryName] = err.Error()
continue continue

View File

@ -39,7 +39,7 @@ var SupportedFunctions = []string{
var EvalFuncs = map[string]govaluate.ExpressionFunction{} var EvalFuncs = map[string]govaluate.ExpressionFunction{}
type prepareTracesQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, keys map[string]v3.AttributeKey) (string, error) type prepareTracesQueryFunc func(start, end int64, panelType v3.PanelType, bq *v3.BuilderQuery, keys map[string]v3.AttributeKey, graphLimitQtype string) (string, error)
type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, graphLimitQtype string) (string, error) type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, graphLimitQtype string) (string, error)
type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery) (string, error) type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery) (string, error)
@ -147,11 +147,25 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3, args ...in
if len(args) > 0 { if len(args) > 0 {
keys = args[0].(map[string]v3.AttributeKey) keys = args[0].(map[string]v3.AttributeKey)
} }
queryString, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, keys) // for ts query with group by and limit form two queries
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {
limitQuery, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, constants.FirstQueryGraphLimit)
if err != nil {
return nil, err
}
placeholderQuery, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, constants.SecondQueryGraphLimit)
if err != nil {
return nil, err
}
query := fmt.Sprintf(placeholderQuery, limitQuery)
queries[queryName] = query
} else {
queryString, err := qb.options.BuildTraceQuery(params.Start, params.End, compositeQuery.PanelType, query, keys, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
queries[queryName] = queryString queries[queryName] = queryString
}
case v3.DataSourceLogs: case v3.DataSourceLogs:
// for ts query with limit replace it as it is already formed // for ts query with limit replace it as it is already formed
if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 { if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 {

View File

@ -108,6 +108,18 @@ func getSelectLabels(aggregatorOperator v3.AggregateOperator, groupBy []v3.Attri
return selectLabels return selectLabels
} }
func getSelectKeys(aggregatorOperator v3.AggregateOperator, groupBy []v3.AttributeKey) string {
var selectLabels []string
if aggregatorOperator == v3.AggregateOperatorNoOp {
return ""
} else {
for _, tag := range groupBy {
selectLabels = append(selectLabels, fmt.Sprintf("`%s`", tag.Key))
}
}
return strings.Join(selectLabels, ",")
}
func getSelectColumns(sc []v3.AttributeKey, keys map[string]v3.AttributeKey) string { func getSelectColumns(sc []v3.AttributeKey, keys map[string]v3.AttributeKey) string {
var columns []string var columns []string
for _, tag := range sc { for _, tag := range sc {
@ -219,7 +231,7 @@ func handleEmptyValuesInGroupBy(keys map[string]v3.AttributeKey, groupBy []v3.At
return "", nil return "", nil
} }
func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey, panelType v3.PanelType) (string, error) { func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName string, keys map[string]v3.AttributeKey, panelType v3.PanelType, graphLimitQtype string) (string, error) {
filterSubQuery, err := buildTracesFilterQuery(mq.Filters, keys) filterSubQuery, err := buildTracesFilterQuery(mq.Filters, keys)
if err != nil { if err != nil {
@ -236,24 +248,27 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
} }
var queryTmpl string var queryTmpl string
if graphLimitQtype == constants.FirstQueryGraphLimit {
if panelType == v3.PanelTypeTable { queryTmpl = "SELECT"
} else if panelType == v3.PanelTypeTable {
queryTmpl = queryTmpl =
"SELECT now() as ts," + selectLabels + "SELECT now() as ts,"
" %s as value " +
"from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME +
" where " + spanIndexTableTimeFilter + "%s" +
"%s%s" +
"%s"
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { } else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue {
// Select the aggregate value for interval // Select the aggregate value for interval
queryTmpl = queryTmpl =
fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step) + selectLabels + fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d SECOND) AS ts,", step)
}
queryTmpl = queryTmpl + selectLabels +
" %s as value " + " %s as value " +
"from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME + "from " + constants.SIGNOZ_TRACE_DBNAME + "." + constants.SIGNOZ_SPAN_INDEX_TABLENAME +
" where " + spanIndexTableTimeFilter + "%s" + " where " + spanIndexTableTimeFilter + "%s" +
"%s%s" + "%s%s" +
"%s" "%s"
// we don't need value for first query
if graphLimitQtype == constants.FirstQueryGraphLimit {
queryTmpl = "SELECT " + getSelectKeys(mq.AggregateOperator, mq.GroupBy) + " from (" + queryTmpl + ")"
} }
emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(keys, mq.GroupBy) emptyValuesInGroupByFilter, err := handleEmptyValuesInGroupBy(keys, mq.GroupBy)
@ -262,7 +277,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
} }
filterSubQuery += emptyValuesInGroupByFilter filterSubQuery += emptyValuesInGroupByFilter
groupBy := groupByAttributeKeyTags(panelType, mq.GroupBy...) groupBy := groupByAttributeKeyTags(panelType, graphLimitQtype, mq.GroupBy...)
if groupBy != "" { if groupBy != "" {
groupBy = " group by " + groupBy groupBy = " group by " + groupBy
} }
@ -271,6 +286,11 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
if orderBy != "" { if orderBy != "" {
orderBy = " order by " + orderBy orderBy = " order by " + orderBy
} }
if graphLimitQtype == constants.SecondQueryGraphLimit {
filterSubQuery = filterSubQuery + " AND " + fmt.Sprintf("(%s) GLOBAL IN (", getSelectKeys(mq.AggregateOperator, mq.GroupBy)) + "%s)"
}
aggregationKey := "" aggregationKey := ""
if mq.AggregateAttribute.Key != "" { if mq.AggregateAttribute.Key != "" {
aggregationKey = getColumnName(mq.AggregateAttribute, keys) aggregationKey = getColumnName(mq.AggregateAttribute, keys)
@ -326,7 +346,7 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
var query string var query string
if panelType == v3.PanelTypeTrace { if panelType == v3.PanelTypeTrace {
withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter, filterSubQuery) withSubQuery := fmt.Sprintf(constants.TracesExplorerViewSQLSelectWithSubQuery, constants.SIGNOZ_TRACE_DBNAME, constants.SIGNOZ_SPAN_INDEX_TABLENAME, spanIndexTableTimeFilter, filterSubQuery)
withSubQuery = addLimitToQuery(withSubQuery, mq.Limit, panelType) withSubQuery = addLimitToQuery(withSubQuery, mq.Limit)
if mq.Offset != 0 { if mq.Offset != 0 {
withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset) withSubQuery = addOffsetToQuery(withSubQuery, mq.Offset)
} }
@ -367,62 +387,35 @@ func enrichOrderBy(items []v3.OrderBy, keys map[string]v3.AttributeKey) []v3.Ord
// groupBy returns a string of comma separated tags for group by clause // groupBy returns a string of comma separated tags for group by clause
// `ts` is always added to the group by clause // `ts` is always added to the group by clause
func groupBy(panelType v3.PanelType, tags ...string) string { func groupBy(panelType v3.PanelType, graphLimitQtype string, tags ...string) string {
if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { if (graphLimitQtype != constants.FirstQueryGraphLimit) && (panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue) {
tags = append(tags, "ts") tags = append(tags, "ts")
} }
return strings.Join(tags, ",") return strings.Join(tags, ",")
} }
func groupByAttributeKeyTags(panelType v3.PanelType, tags ...v3.AttributeKey) string { func groupByAttributeKeyTags(panelType v3.PanelType, graphLimitQtype string, tags ...v3.AttributeKey) string {
groupTags := []string{} groupTags := []string{}
for _, tag := range tags { for _, tag := range tags {
groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key)) groupTags = append(groupTags, fmt.Sprintf("`%s`", tag.Key))
} }
return groupBy(panelType, groupTags...) return groupBy(panelType, graphLimitQtype, groupTags...)
} }
// orderBy returns a string of comma separated tags for order by clause // orderBy returns a string of comma separated tags for order by clause
// if there are remaining items which are not present in tags they are also added // if there are remaining items which are not present in tags they are also added
// if the order is not specified, it defaults to ASC // if the order is not specified, it defaults to ASC
func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map[string]v3.AttributeKey) []string { func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]struct{}, keys map[string]v3.AttributeKey) []string {
var orderBy []string var orderBy []string
// create a lookup
addedToOrderBy := map[string]bool{}
itemsLookup := map[string]v3.OrderBy{}
for i := 0; i < len(items); i++ {
addedToOrderBy[items[i].ColumnName] = false
itemsLookup[items[i].ColumnName] = items[i]
}
for _, tag := range tags {
if item, ok := itemsLookup[tag]; ok {
orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order))
addedToOrderBy[item.ColumnName] = true
} else {
orderBy = append(orderBy, fmt.Sprintf("`%s` ASC", tag))
}
}
// users might want to order by value of aggregation
for _, item := range items { for _, item := range items {
if item.ColumnName == constants.SigNozOrderByValue { if item.ColumnName == constants.SigNozOrderByValue {
orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order)) orderBy = append(orderBy, fmt.Sprintf("value %s", item.Order))
addedToOrderBy[item.ColumnName] = true } else if _, ok := tagLookup[item.ColumnName]; ok {
} orderBy = append(orderBy, fmt.Sprintf("`%s` %s", item.ColumnName, item.Order))
} } else if panelType == v3.PanelTypeList {
// add the remaining items
if panelType == v3.PanelTypeList {
for _, item := range items {
// since these are not present in tags we will have to select them correctly
// for list view there is no need to check if it was added since they wont be added yet but this is just for safety
if !addedToOrderBy[item.ColumnName] {
attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn}
name := getColumnName(attr, keys) name := getColumnName(attr, keys)
if item.IsColumn { if item.IsColumn {
orderBy = append(orderBy, fmt.Sprintf("`%s` %s", name, item.Order)) orderBy = append(orderBy, fmt.Sprintf("`%s` %s", name, item.Order))
} else { } else {
@ -430,21 +423,24 @@ func orderBy(panelType v3.PanelType, items []v3.OrderBy, tags []string, keys map
} }
} }
} }
}
return orderBy return orderBy
} }
func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey, keys map[string]v3.AttributeKey) string { func orderByAttributeKeyTags(panelType v3.PanelType, items []v3.OrderBy, tags []v3.AttributeKey, keys map[string]v3.AttributeKey) string {
var groupTags []string tagLookup := map[string]struct{}{}
for _, tag := range tags { for _, v := range tags {
groupTags = append(groupTags, tag.Key) tagLookup[v.Key] = struct{}{}
} }
orderByArray := orderBy(panelType, items, groupTags, keys)
if panelType == v3.PanelTypeList && len(orderByArray) == 0 { orderByArray := orderBy(panelType, items, tagLookup, keys)
if len(orderByArray) == 0 {
if panelType == v3.PanelTypeList {
orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC") orderByArray = append(orderByArray, constants.TIMESTAMP+" DESC")
} else if panelType == v3.PanelTypeGraph || panelType == v3.PanelTypeValue { } else if panelType == v3.PanelTypeGraph {
orderByArray = append(orderByArray, "ts") orderByArray = append(orderByArray, "value DESC")
}
} }
str := strings.Join(orderByArray, ",") str := strings.Join(orderByArray, ",")
@ -480,7 +476,7 @@ func reduceToQuery(query string, reduceTo v3.ReduceToOperator, aggregateOperator
return query, nil return query, nil
} }
func addLimitToQuery(query string, limit uint64, panelType v3.PanelType) string { func addLimitToQuery(query string, limit uint64) string {
if limit == 0 { if limit == 0 {
limit = 100 limit = 100
} }
@ -491,16 +487,33 @@ func addOffsetToQuery(query string, offset uint64) string {
return fmt.Sprintf("%s OFFSET %d", query, offset) return fmt.Sprintf("%s OFFSET %d", query, offset)
} }
func PrepareTracesQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, keys map[string]v3.AttributeKey) (string, error) { func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.BuilderQuery, keys map[string]v3.AttributeKey, graphLimitQtype string) (string, error) {
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType) if graphLimitQtype == constants.FirstQueryGraphLimit {
// give me just the group by names
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype)
if err != nil {
return "", err
}
query = addLimitToQuery(query, mq.Limit)
return query, nil
} else if graphLimitQtype == constants.SecondQueryGraphLimit {
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype)
if err != nil {
return "", err
}
return query, nil
}
query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, keys, panelType, graphLimitQtype)
if err != nil { if err != nil {
return "", err return "", err
} }
if panelType == v3.PanelTypeValue { if panelType == v3.PanelTypeValue {
query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator) query, err = reduceToQuery(query, mq.ReduceTo, mq.AggregateOperator)
} }
if panelType == v3.PanelTypeList { if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable {
query = addLimitToQuery(query, mq.Limit, panelType) query = addLimitToQuery(query, mq.Limit)
if mq.Offset != 0 { if mq.Offset != 0 {
query = addOffsetToQuery(query, mq.Offset) query = addOffsetToQuery(query, mq.Offset)

View File

@ -323,8 +323,8 @@ var testOrderBy = []struct {
Name string Name string
PanelType v3.PanelType PanelType v3.PanelType
Items []v3.OrderBy Items []v3.OrderBy
Tags []string Tags []v3.AttributeKey
Result []string Result string
}{ }{
{ {
Name: "Test 1", Name: "Test 1",
@ -339,8 +339,10 @@ var testOrderBy = []struct {
Order: "desc", Order: "desc",
}, },
}, },
Tags: []string{"name"}, Tags: []v3.AttributeKey{
Result: []string{"`name` asc", "value desc"}, {Key: "name"},
},
Result: "`name` asc,value desc",
}, },
{ {
Name: "Test 2", Name: "Test 2",
@ -355,8 +357,11 @@ var testOrderBy = []struct {
Order: "asc", Order: "asc",
}, },
}, },
Tags: []string{"name", "bytes"}, Tags: []v3.AttributeKey{
Result: []string{"`name` asc", "`bytes` asc"}, {Key: "name"},
{Key: "bytes"},
},
Result: "`name` asc,`bytes` asc",
}, },
{ {
Name: "Test 3", Name: "Test 3",
@ -375,8 +380,11 @@ var testOrderBy = []struct {
Order: "asc", Order: "asc",
}, },
}, },
Tags: []string{"name", "bytes"}, Tags: []v3.AttributeKey{
Result: []string{"`name` asc", "`bytes` asc", "value asc"}, {Key: "name"},
{Key: "bytes"},
},
Result: "`name` asc,value asc,`bytes` asc",
}, },
{ {
Name: "Test 4", Name: "Test 4",
@ -398,8 +406,11 @@ var testOrderBy = []struct {
DataType: v3.AttributeKeyDataTypeString, DataType: v3.AttributeKeyDataTypeString,
}, },
}, },
Tags: []string{"name", "bytes"}, Tags: []v3.AttributeKey{
Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, {Key: "name"},
{Key: "bytes"},
},
Result: "`name` asc,`bytes` asc,stringTagMap['response_time'] desc",
}, },
{ {
Name: "Test 5", Name: "Test 5",
@ -426,15 +437,15 @@ var testOrderBy = []struct {
Order: "desc", Order: "desc",
}, },
}, },
Tags: []string{}, Tags: []v3.AttributeKey{},
Result: []string{"`name` asc", "`bytes` asc", "stringTagMap['response_time'] desc"}, Result: "`name` asc,`bytes` asc,stringTagMap['response_time'] desc",
}, },
} }
func TestOrderBy(t *testing.T) { func TestOrderBy(t *testing.T) {
for _, tt := range testOrderBy { for _, tt := range testOrderBy {
Convey("testOrderBy", t, func() { Convey("testOrderBy", t, func() {
res := orderBy(tt.PanelType, tt.Items, tt.Tags, map[string]v3.AttributeKey{ res := orderByAttributeKeyTags(tt.PanelType, tt.Items, tt.Tags, map[string]v3.AttributeKey{
"name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
"bytes": {Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, "bytes": {Key: "bytes", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
"response_time": {Key: "response_time", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: false}, "response_time": {Key: "response_time", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: false},
@ -470,7 +481,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" group by ts order by ts", " group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -486,7 +497,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, count()/60 as value from" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, count()/60 as value from" +
" signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <=" + " signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <=" +
" '1680066458000000000') group by ts order by ts", " '1680066458000000000') group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -505,7 +516,7 @@ var testBuildTracesQueryData = []struct {
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," +
" toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v2" + " toFloat64(count()) as value from signoz_traces.distributed_signoz_index_v2" +
" where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" AND stringTagMap['customer_id'] = '10001' group by ts order by ts", " AND stringTagMap['customer_id'] = '10001' group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -522,7 +533,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" group by ts order by ts", " group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -539,7 +550,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" AND has(stringTagMap, 'user_name') group by ts order by ts", " AND has(stringTagMap, 'user_name') group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -556,7 +567,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" AND name != '' group by ts order by ts", " AND name != '' group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -576,7 +587,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" AND numberTagMap['bytes'] > 100.000000 AND has(stringTagMap, 'user_name') group by ts order by ts", " AND numberTagMap['bytes'] > 100.000000 AND has(stringTagMap, 'user_name') group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -594,7 +605,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(name))) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(name))) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" group by ts order by value ASC,ts", " group by ts order by value ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -611,7 +622,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name'])))" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name'])))" +
" as value from signoz_traces.distributed_signoz_index_v2 where" + " as value from signoz_traces.distributed_signoz_index_v2 where" +
" (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') group by ts order by ts", " (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') group by ts order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -630,7 +641,7 @@ var testBuildTracesQueryData = []struct {
}, },
}, },
GroupBy: []v3.AttributeKey{{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}}, GroupBy: []v3.AttributeKey{{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
OrderBy: []v3.OrderBy{{ColumnName: "http.method", Order: "ASC"}, {ColumnName: "ts", Order: "ASC"}}, OrderBy: []v3.OrderBy{{ColumnName: "http.method", Order: "ASC"}},
}, },
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts," +
@ -639,7 +650,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['http.method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND stringTagMap['http.method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " +
"AND has(stringTagMap, 'http.method') group by `http.method`,ts " + "AND has(stringTagMap, 'http.method') group by `http.method`,ts " +
"order by `http.method` ASC,ts", "order by `http.method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -671,7 +682,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " + "AND stringTagMap['method'] = 'GET' AND resourceTagsMap['x'] != 'abc' " +
"AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by `method`,`x`,ts " + "AND has(stringTagMap, 'method') AND has(resourceTagsMap, 'x') group by `method`,`x`,ts " +
"order by `method` ASC,`x` ASC,ts", "order by `method` ASC,`x` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -699,7 +710,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' " + "AND stringTagMap['method'] = 'GET' " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -727,7 +738,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' " + "AND stringTagMap['method'] = 'GET' " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -755,7 +766,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' " + "AND stringTagMap['method'] = 'GET' " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -783,7 +794,7 @@ var testBuildTracesQueryData = []struct {
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' " + "AND stringTagMap['method'] = 'GET' " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -807,7 +818,7 @@ var testBuildTracesQueryData = []struct {
"from signoz_traces.distributed_signoz_index_v2 " + "from signoz_traces.distributed_signoz_index_v2 " +
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -828,7 +839,7 @@ var testBuildTracesQueryData = []struct {
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, stringTagMap['method'] as `method`" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, stringTagMap['method'] as `method`" +
", sum(bytes)/60 as value from signoz_traces.distributed_signoz_index_v2 " + ", sum(bytes)/60 as value from signoz_traces.distributed_signoz_index_v2 " +
"where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + "where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" AND has(stringTagMap, 'method') group by `method`,ts order by `method` ASC,ts", " AND has(stringTagMap, 'method') group by `method`,ts order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -850,7 +861,7 @@ var testBuildTracesQueryData = []struct {
", count(numberTagMap['bytes'])/60 as value " + ", count(numberTagMap['bytes'])/60 as value " +
"from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -873,7 +884,7 @@ var testBuildTracesQueryData = []struct {
"sum(numberTagMap['bytes'])/60 as value " + "sum(numberTagMap['bytes'])/60 as value " +
"from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND has(stringTagMap, 'method') group by `method`,ts " + "AND has(stringTagMap, 'method') group by `method`,ts " +
"order by `method` ASC,ts", "order by `method` ASC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -897,7 +908,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000')" +
" group by ts having value > 10 order by ts", " group by ts having value > 10 order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -925,7 +936,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value from " + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value from " +
"signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + "signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by ts", "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -953,7 +964,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count(distinct(stringTagMap['name']))) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' group by ts having value > 10 order by ts", "AND stringTagMap['method'] = 'GET' group by ts having value > 10 order by value DESC",
PanelType: v3.PanelTypeGraph, PanelType: v3.PanelTypeGraph,
}, },
{ {
@ -981,7 +992,7 @@ var testBuildTracesQueryData = []struct {
TableName: "signoz_traces.distributed_signoz_index_v2", TableName: "signoz_traces.distributed_signoz_index_v2",
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" + ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, toFloat64(count()) as value" +
" from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " + " from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') " +
"AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10 order by ts", "AND stringTagMap['method'] = 'GET' AND has(stringTagMap, 'name') group by ts having value > 10",
PanelType: v3.PanelTypeValue, PanelType: v3.PanelTypeValue,
}, },
{ {
@ -1108,7 +1119,7 @@ var testBuildTracesQueryData = []struct {
" name FROM signoz_traces.distributed_signoz_index_v2 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND " + " name FROM signoz_traces.distributed_signoz_index_v2 WHERE parentSpanID = '' AND (timestamp >= '1680066360726210000' AND " +
"timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' ORDER BY durationNano DESC LIMIT 100)" + "timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' ORDER BY durationNano DESC LIMIT 100)" +
" SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, traceID" + " SELECT subQuery.serviceName, subQuery.name, count() AS span_count, subQuery.durationNano, traceID" +
" FROM signoz_traces.distributed_signoz_index_v2 INNER JOIN subQuery ON distributed_signoz_index_v2.traceID" + " FROM signoz_traces.distributed_signoz_index_v2 GLOBAL INNER JOIN subQuery ON distributed_signoz_index_v2.traceID" +
" = subQuery.traceID GROUP BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName " + " = subQuery.traceID GROUP BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName " +
"ORDER BY subQuery.durationNano desc;", "ORDER BY subQuery.durationNano desc;",
PanelType: v3.PanelTypeTrace, PanelType: v3.PanelTypeTrace,
@ -1120,7 +1131,218 @@ func TestBuildTracesQuery(t *testing.T) {
Convey("TestBuildTracesQuery", t, func() { Convey("TestBuildTracesQuery", t, func() {
query, err := buildTracesQuery(tt.Start, tt.End, tt.Step, tt.BuilderQuery, tt.TableName, map[string]v3.AttributeKey{ query, err := buildTracesQuery(tt.Start, tt.End, tt.Step, tt.BuilderQuery, tt.TableName, map[string]v3.AttributeKey{
"name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}, "name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
}, tt.PanelType) }, tt.PanelType, "")
So(err, ShouldBeNil)
So(query, ShouldEqual, tt.ExpectedQuery)
})
}
}
var testPrepTracesQueryData = []struct {
Name string
PanelType v3.PanelType
Start int64
End int64
BuilderQuery *v3.BuilderQuery
ExpectedQuery string
Keys map[string]v3.AttributeKey
Type string
}{
{
Name: "Test TS with limit- first",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
},
},
Limit: 10,
StepInterval: 60,
GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
},
ExpectedQuery: "SELECT `method` from (SELECT stringTagMap['method'] as `method`," +
" toFloat64(count(distinct(stringTagMap['name']))) as value from signoz_traces.distributed_signoz_index_v2" +
" where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND" +
" stringTagMap['method'] = 'GET' AND has(stringTagMap, 'method') group by `method` order by value DESC) LIMIT 10",
Keys: map[string]v3.AttributeKey{"name": {Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
Type: constants.FirstQueryGraphLimit,
},
{
Name: "Test TS with limit- first - with order by value",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
},
},
Limit: 10,
StepInterval: 60,
GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "ASC"}},
},
ExpectedQuery: "SELECT `method` from (SELECT stringTagMap['method'] as `method`," +
" toFloat64(count(distinct(stringTagMap['name']))) as value from " +
"signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" +
" has(stringTagMap, 'method') group by `method` order by value ASC) LIMIT 10",
Keys: map[string]v3.AttributeKey{},
Type: constants.FirstQueryGraphLimit,
},
{
Name: "Test TS with limit- first - with order by attribute",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{},
Limit: 10,
StepInterval: 60,
GroupBy: []v3.AttributeKey{{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true}},
OrderBy: []v3.OrderBy{{ColumnName: "serviceName", Order: "ASC"}},
},
ExpectedQuery: "SELECT `serviceName` from (SELECT serviceName as `serviceName`," +
" toFloat64(count(distinct(serviceName))) as value from " +
"signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') " +
"group by `serviceName` order by `serviceName` ASC) LIMIT 10",
Keys: map[string]v3.AttributeKey{},
Type: constants.FirstQueryGraphLimit,
},
{
Name: "Test TS with limit- first - with 2 group by and 2 order by",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{},
Limit: 10,
StepInterval: 60,
GroupBy: []v3.AttributeKey{
{Key: "serviceName", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, IsColumn: true},
{Key: "http.method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
},
OrderBy: []v3.OrderBy{{ColumnName: "serviceName", Order: "ASC"}, {ColumnName: constants.SigNozOrderByValue, Order: "ASC"}},
},
ExpectedQuery: "SELECT `serviceName`,`http.method` from (SELECT serviceName as `serviceName`," +
" stringTagMap['http.method'] as `http.method`," +
" toFloat64(count(distinct(serviceName))) as value from " +
"signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') AND has(stringTagMap, 'http.method') " +
"group by `serviceName`,`http.method` order by `serviceName` ASC,value ASC) LIMIT 10",
Keys: map[string]v3.AttributeKey{},
Type: constants.FirstQueryGraphLimit,
},
{
Name: "Test TS with limit- second",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
},
},
GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
Limit: 2,
StepInterval: 60,
},
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " +
"stringTagMap['method'] as `method`, toFloat64(count(distinct(stringTagMap['name'])))" +
" as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" +
" has(stringTagMap, 'method') AND (`method`) GLOBAL IN (%s) group by `method`,ts order by value DESC",
Keys: map[string]v3.AttributeKey{},
Type: constants.SecondQueryGraphLimit,
},
{
Name: "Test TS with limit- second - with order by",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
},
},
GroupBy: []v3.AttributeKey{{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}},
OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}},
Limit: 2,
StepInterval: 60,
},
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " +
"stringTagMap['method'] as `method`, toFloat64(count(distinct(stringTagMap['name'])))" +
" as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" +
" has(stringTagMap, 'method') AND (`method`) GLOBAL IN (%s) group by `method`,ts order by `method` ASC", Keys: map[string]v3.AttributeKey{},
Type: constants.SecondQueryGraphLimit,
},
{
Name: "Test TS with limit - second - with two group by and two order by",
PanelType: v3.PanelTypeGraph,
Start: 1680066360726210000,
End: 1680066458000000000,
BuilderQuery: &v3.BuilderQuery{
QueryName: "A",
AggregateAttribute: v3.AttributeKey{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
AggregateOperator: v3.AggregateOperatorCountDistinct,
Expression: "A",
Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{
{Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="},
},
},
GroupBy: []v3.AttributeKey{
{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
{Key: "name", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag},
},
OrderBy: []v3.OrderBy{{ColumnName: "method", Order: "ASC"}, {ColumnName: "name", Order: "ASC"}},
Limit: 2,
StepInterval: 60,
},
ExpectedQuery: "SELECT toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, " +
"stringTagMap['method'] as `method`, stringTagMap['name'] as `name`," +
" toFloat64(count(distinct(stringTagMap['name'])))" +
" as value from signoz_traces.distributed_signoz_index_v2 where (timestamp >= '1680066360726210000'" +
" AND timestamp <= '1680066458000000000') AND stringTagMap['method'] = 'GET' AND" +
" has(stringTagMap, 'method') AND has(stringTagMap, 'name') " +
"AND (`method`,`name`) GLOBAL IN (%s) group by `method`,`name`,ts " +
"order by `method` ASC,`name` ASC",
Keys: map[string]v3.AttributeKey{},
Type: constants.SecondQueryGraphLimit,
},
}
func TestPrepareTracesQuery(t *testing.T) {
for _, tt := range testPrepTracesQueryData {
Convey("TestPrepareTracesQuery", t, func() {
query, err := PrepareTracesQuery(tt.Start, tt.End, tt.PanelType, tt.BuilderQuery, tt.Keys, tt.Type)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(query, ShouldEqual, tt.ExpectedQuery) So(query, ShouldEqual, tt.ExpectedQuery)
}) })

View File

@ -246,7 +246,7 @@ const (
TracesExplorerViewSQLSelectWithSubQuery = "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, " + TracesExplorerViewSQLSelectWithSubQuery = "WITH subQuery AS (SELECT distinct on (traceID) traceID, durationNano, " +
"serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s %s ORDER BY durationNano DESC " "serviceName, name FROM %s.%s WHERE parentSpanID = '' AND %s %s ORDER BY durationNano DESC "
TracesExplorerViewSQLSelectQuery = "SELECT subQuery.serviceName, subQuery.name, count() AS " + TracesExplorerViewSQLSelectQuery = "SELECT subQuery.serviceName, subQuery.name, count() AS " +
"span_count, subQuery.durationNano, traceID FROM %s.%s INNER JOIN subQuery ON %s.traceID = subQuery.traceID GROUP " + "span_count, subQuery.durationNano, traceID FROM %s.%s GLOBAL INNER JOIN subQuery ON %s.traceID = subQuery.traceID GROUP " +
"BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY subQuery.durationNano desc;" "BY traceID, subQuery.durationNano, subQuery.name, subQuery.serviceName ORDER BY subQuery.durationNano desc;"
) )

View File

@ -169,7 +169,7 @@ services:
<<: *clickhouse-depends <<: *clickhouse-depends
otel-collector: otel-collector:
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
user: root # required for reading docker container logs user: root # required for reading docker container logs
volumes: volumes:
@ -195,7 +195,7 @@ services:
<<: *clickhouse-depends <<: *clickhouse-depends
otel-collector-metrics: otel-collector-metrics:
image: signoz/signoz-otel-collector:0.79.3 image: signoz/signoz-otel-collector:0.79.4
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"] command: ["--config=/etc/otel-collector-metrics-config.yaml", "--feature-gates=-pkg.translator.prometheus.NormalizeName"]
volumes: volumes:
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml