Compare commits

..

17 Commits

Author SHA1 Message Date
Rajat-Dabade
ced74603c0 chore: updated test script 2023-12-15 13:46:16 +05:30
Rajat-Dabade
f59fb81109 refactor: updated test directory 2023-12-15 13:46:16 +05:30
Rajat-Dabade
507e68a0c1 refactor: reverted back as working directory is specified as frontend 2023-12-15 13:46:16 +05:30
Rajat-Dabade
4ad8a1f3ad refactor: shifted i18n to original location 2023-12-15 13:46:16 +05:30
Rajat-Dabade
19faf6a584 chore: updates 2023-12-15 13:46:16 +05:30
Rajat-Dabade
3978ada811 refactor: for push and pull request 2023-12-15 13:46:16 +05:30
Rajat-Dabade
0a04fc04a5 refactor: generate code coverage report on every push 2023-12-15 13:46:16 +05:30
Rajat-Dabade
7c9e333b84 refactor: added package-manager 2023-12-15 13:46:16 +05:30
Rajat-Dabade
dd78afb20f refactor: updated the working directory 2023-12-15 13:46:16 +05:30
Rajat-Dabade
237d765376 refactor: updated github flow 2023-12-15 13:46:16 +05:30
Rajat-Dabade
85e865fb1b refactor: updated token 2023-12-15 13:46:16 +05:30
Rajat-Dabade
975e5daf03 refactor: updated test case 2023-12-15 13:46:16 +05:30
Rajat-Dabade
8a532cca17 refactor: updated jest running command 2023-12-15 13:46:16 +05:30
Rajat-Dabade
b9c908719f refactor: updated the command for jest 2023-12-15 13:46:16 +05:30
Rajat-Dabade
63c7b5e9e1 chore: minor changes 2023-12-15 13:46:16 +05:30
Rajat-Dabade
32eeb3d106 refactor: done some changes 2023-12-15 13:46:16 +05:30
Rajat-Dabade
1a4ec2bf00 feat: jest code coverage report 2023-12-15 13:46:16 +05:30
19 changed files with 92 additions and 287 deletions

View File

@@ -0,0 +1,32 @@
name: Code Coverage
on:
push:
branches:
- develop
- main
- release/v*
pull_request:
branches:
- develop
- main
- release/v*
jobs:
coverage:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
contents: write
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- uses: ArtiomTr/jest-coverage-report-action@v2
with:
package-manager: yarn
working-directory: frontend
test-script: yarn jest:coverage
github-token: ${{ secrets.GITHUB_TOKEN }}
output: comment
prnumber: ${{ steps.findPr.outputs.number }}

View File

@@ -1,10 +1,9 @@
import cacheBursting from 'i18n-translations-hash.json';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
import cacheBursting from '../../i18n-translations-hash.json';
i18n
// load translation using http -> see /public/locales
.use(Backend)

View File

@@ -1,15 +0,0 @@
import { ApiV3Instance as axios } from 'api';
import { ApiResponse } from 'types/api';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { QueryRangePayload } from 'types/api/metrics/getQueryRange';
interface IQueryRangeFormat {
compositeQuery: ICompositeMetricQuery;
}
export const getQueryRangeFormat = (
props?: Partial<QueryRangePayload>,
): Promise<IQueryRangeFormat> =>
axios
.post<ApiResponse<IQueryRangeFormat>>('/query_range/format', props)
.then((res) => res.data.data);

View File

@@ -27,7 +27,6 @@ function DynamicColumnTable({
);
useEffect(() => {
setColumnsData(columns);
const visibleColumns = getVisibleColumns({
tablesource,
columnsData: columns,
@@ -43,7 +42,7 @@ function DynamicColumnTable({
: undefined,
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns]);
}, []);
const onToggleHandler = (index: number) => (
checked: boolean,

View File

@@ -14,7 +14,7 @@ import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { ReactNode, useCallback, useMemo } from 'react';
@@ -71,7 +71,13 @@ function WidgetHeader({
);
}, [widget.id, widget.panelTypes, widget.query]);
const onCreateAlertsHandler = useCreateAlerts(widget);
const onCreateAlertsHandler = useCallback(() => {
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(widget.query),
)}`,
);
}, [widget]);
const keyMethodMapping = useMemo(
() => ({

View File

@@ -1,7 +1,7 @@
/* eslint-disable react/display-name */
import { PlusOutlined } from '@ant-design/icons';
import { Input, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import { Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import saveAlertApi from 'api/alerts/save';
import DropDown from 'components/DropDown/DropDown';
import {
@@ -14,12 +14,9 @@ import LabelColumn from 'components/TableRenderer/LabelColumn';
import TextToolTip from 'components/TextToolTip';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import useSortableTable from 'hooks/ResizeTable/useSortableTable';
import useComponentPermission from 'hooks/useComponentPermission';
import useDebouncedFn from 'hooks/useDebouncedFunction';
import useInterval from 'hooks/useInterval';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useCallback, useState } from 'react';
@@ -32,19 +29,12 @@ import { GettableAlert } from 'types/api/alerts/get';
import AppReducer from 'types/reducer/app';
import DeleteAlert from './DeleteAlert';
import {
Button,
ButtonContainer,
ColumnButton,
SearchContainer,
} from './styles';
import { Button, ButtonContainer, ColumnButton } from './styles';
import Status from './TableComponents/Status';
import ToggleAlertState from './ToggleAlertState';
import { filterAlerts } from './utils';
const { Search } = Input;
function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
const [data, setData] = useState<GettableAlert[]>(allAlertRules || []);
const { t } = useTranslation('common');
const { role, featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
@@ -54,39 +44,13 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
role,
);
const params = useUrlQuery();
const orderColumnParam = params.get('columnKey');
const orderQueryParam = params.get('order');
const paginationParam = params.get('page');
const searchParams = params.get('search');
const [searchString, setSearchString] = useState<string>(searchParams || '');
const [data, setData] = useState<GettableAlert[]>(() => {
const value = searchString.toLowerCase();
const filteredData = filterAlerts(allAlertRules, value);
return filteredData || [];
});
// Type asuring
const sortingOrder: 'ascend' | 'descend' | null =
orderQueryParam === 'ascend' || orderQueryParam === 'descend'
? orderQueryParam
: null;
const { sortedInfo, handleChange } = useSortableTable<GettableAlert>(
sortingOrder,
orderColumnParam || '',
searchString,
);
const { notifications: notificationsApi } = useNotifications();
useInterval(() => {
(async (): Promise<void> => {
const { data: refetchData, status } = await refetch();
if (status === 'success') {
const value = searchString.toLowerCase();
const filteredData = filterAlerts(refetchData.payload || [], value);
setData(filteredData || []);
setData(refetchData?.payload || []);
}
if (status === 'error') {
notificationsApi.error({
@@ -164,13 +128,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
}
};
const handleSearch = useDebouncedFn((e: unknown) => {
const value = (e as React.BaseSyntheticEvent).target.value.toLowerCase();
setSearchString(value);
const filteredData = filterAlerts(allAlertRules, value);
setData(filteredData);
});
const dynamicColumns: ColumnsType<GettableAlert> = [
{
title: 'Created At',
@@ -185,10 +142,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return prev - next;
},
render: DateComponent,
sortOrder:
sortedInfo.columnKey === DynamicColumnsKey.CreatedAt
? sortedInfo.order
: null,
},
{
title: 'Created By',
@@ -210,10 +163,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return prev - next;
},
render: DateComponent,
sortOrder:
sortedInfo.columnKey === DynamicColumnsKey.UpdatedAt
? sortedInfo.order
: null,
},
{
title: 'Updated By',
@@ -234,7 +183,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
(b.state ? b.state.charCodeAt(0) : 1000) -
(a.state ? a.state.charCodeAt(0) : 1000),
render: (value): JSX.Element => <Status status={value} />,
sortOrder: sortedInfo.columnKey === 'state' ? sortedInfo.order : null,
},
{
title: 'Alert Name',
@@ -250,7 +198,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
render: (value, record): JSX.Element => (
<Typography.Link onClick={onEditHandler(record)}>{value}</Typography.Link>
),
sortOrder: sortedInfo.columnKey === 'name' ? sortedInfo.order : null,
},
{
title: 'Severity',
@@ -267,7 +214,6 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return <Typography>{severityValue}</Typography>;
},
sortOrder: sortedInfo.columnKey === 'severity' ? sortedInfo.order : null,
},
{
title: 'Labels',
@@ -325,37 +271,26 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
return (
<>
<SearchContainer>
<Search
placeholder="Search by Alert Name, Severity and Labels"
onChange={handleSearch}
defaultValue={searchString}
<ButtonContainer>
<TextToolTip
{...{
text: `More details on how to create alerts`,
url: 'https://signoz.io/docs/userguide/alerts-management/',
}}
/>
<ButtonContainer>
<TextToolTip
{...{
text: `More details on how to create alerts`,
url: 'https://signoz.io/docs/userguide/alerts-management/',
}}
/>
{addNewAlert && (
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
New Alert
</Button>
)}
</ButtonContainer>
</SearchContainer>
{addNewAlert && (
<Button onClick={onClickNewAlertHandler} icon={<PlusOutlined />}>
New Alert
</Button>
)}
</ButtonContainer>
<DynamicColumnTable
tablesource={TableDataSource.Alert}
columns={columns}
rowKey="id"
dataSource={data}
dynamicColumns={dynamicColumns}
onChange={handleChange}
pagination={{
defaultCurrent: Number(paginationParam) || 1,
}}
/>
</>
);

View File

@@ -1,17 +1,11 @@
import { Button as ButtonComponent } from 'antd';
import styled from 'styled-components';
export const SearchContainer = styled.div`
&&& {
display: flex;
margin-bottom: 2rem;
align-items: center;
gap: 2rem;
}
`;
export const ButtonContainer = styled.div`
&&& {
display: flex;
justify-content: flex-end;
margin-bottom: 2rem;
align-items: center;
}
`;

View File

@@ -1,25 +0,0 @@
import { GettableAlert } from 'types/api/alerts/get';
export const filterAlerts = (
allAlertRules: GettableAlert[],
filter: string,
): GettableAlert[] => {
const value = filter.toLowerCase();
return allAlertRules.filter((alert) => {
const alertName = alert.alert.toLowerCase();
const severity = alert.labels?.severity.toLowerCase();
const labels = Object.keys(alert.labels || {})
.filter((e) => e !== 'severity')
.join(' ')
.toLowerCase();
const labelValue = Object.values(alert.labels || {});
return (
alertName.includes(value) ||
severity?.includes(value) ||
labels.includes(value) ||
labelValue.includes(value)
);
});
};

View File

@@ -10,9 +10,11 @@ import {
} from 'antd';
import InputComponent from 'components/Input';
import TimePreference from 'components/TimePreferenceDropDown';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems';
import useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
import history from 'lib/history';
import { Dispatch, SetStateAction, useCallback } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -53,7 +55,15 @@ function RightContainer({
const selectedGraphType =
GraphTypes.find((e) => e.name === selectedGraph)?.display || '';
const onCreateAlertsHandler = useCreateAlerts(selectedWidget);
const onCreateAlertsHandler = useCallback(() => {
if (!selectedWidget) return;
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(selectedWidget?.query),
)}`,
);
}, [selectedWidget]);
const allowThreshold = panelTypeVsThreshold[selectedGraph];

View File

@@ -292,9 +292,6 @@ function DateTimeSelection({
selectedTime,
)}
data-testid="dropDown"
style={{
minWidth: 120,
}}
>
{options.map(({ value, label }) => (
<Option key={value + label} value={value}>

View File

@@ -1,44 +0,0 @@
import { TableProps } from 'antd';
import { SorterResult } from 'antd/es/table/interface';
import { useEffect, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
const useSortableTable = <T>(
initialOrder: 'ascend' | 'descend' | null,
initialColumnKey: string,
searchString: string,
): {
sortedInfo: SorterResult<T>;
handleChange: TableProps<T>['onChange'];
} => {
const history = useHistory();
const { search } = useLocation();
useEffect(() => {
const searchParams = new URLSearchParams(search);
searchParams.set('search', searchString);
history.replace({ search: searchParams.toString() });
}, [history, search, searchString]);
const [sortedInfo, setSortedInfo] = useState<SorterResult<T>>({
order: initialOrder,
columnKey: initialColumnKey,
});
const handleChange: TableProps<T>['onChange'] = (pagination, __, sorter) => {
if (Array.isArray(sorter)) return;
const searchParams = new URLSearchParams(search);
setSortedInfo(sorter as SorterResult<T>);
searchParams.set('columnKey', sorter.columnKey as string);
searchParams.set('order', sorter.order as string);
searchParams.set(
'page',
pagination.current ? pagination.current.toString() : '1',
);
history.replace({ search: searchParams.toString() });
};
return { sortedInfo, handleChange };
};
export default useSortableTable;

View File

@@ -1,65 +0,0 @@
import { getQueryRangeFormat } from 'api/dashboard/queryRangeFormat';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback } from 'react';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
const useCreateAlerts = (widget?: Widgets): VoidFunction => {
const queryRangeMutation = useMutation(getQueryRangeFormat);
const { selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { notifications } = useNotifications();
const { selectedDashboard } = useDashboard();
return useCallback(() => {
if (!widget) return;
const { queryPayload } = prepareQueryRangePayload({
query: widget.query,
globalSelectedInterval,
graphType: widget.panelTypes,
selectedTime: widget.timePreferance,
variables: getDashboardVariables(selectedDashboard?.data.variables),
});
queryRangeMutation.mutate(queryPayload, {
onSuccess: (data) => {
const updatedQuery = mapQueryDataFromApi(data.compositeQuery);
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(updatedQuery),
)}`,
);
},
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
}, [
globalSelectedInterval,
notifications,
queryRangeMutation,
selectedDashboard?.data.variables,
widget,
]);
};
export default useCreateAlerts;

View File

@@ -0,0 +1 @@
{"/en-GB/alerts":"37ea40b758e14f100b970178809147d7","/en-GB/channels":"b855a58fce92ff62a0ce50cc40d8da0b","/en-GB/common":"d918932fcd1d34b2d84cb463812bd157","/en-GB/dashboard":"9ec66badfc02995263cf108615f6380c","/en-GB/errorDetails":"a1a1ea54ed8adc720e7942c42ce4be0f","/en-GB/explorer":"98106bbc79e701d81f5731dd53a158f0","/en-GB/generalSettings":"65fca62d2f109d73fa4bdc447c353857","/en-GB/licenses":"dc2fea934c67b5b3bf8c940019d820cd","/en-GB/login":"c9d63ef04a9af5ae6aed12b4b725add5","/en-GB/logs":"de363f7feee26d9fc72eccdf69988f09","/en-GB/organizationsettings":"e24624bba7bdd7bf071873940742b1a8","/en-GB/routes":"08585a25257ed898131ba43e4c927d7e","/en-GB/rules":"f134663a0943cdb8cd2a2c169f27ba90","/en-GB/settings":"e2c4003664cc9ba476b658f1e6304fe5","/en-GB/signup":"59c64809b8b4c7b1b8902da5aa2315f0","/en-GB/titles":"9e0515203efab287fdd50afb96bde8c8","/en-GB/trace":"fcf3fda7bee8b609b5a3ab0f749f2594","/en-GB/traceDetails":"f91795f15c286f15bf630a454febb015","/en-GB/translation":"532cce878c691d9ff3c689a73279fd2e","/en/alerts":"37ea40b758e14f100b970178809147d7","/en/channels":"c9bfbd14bb4d3f38e2a669f5fbfadc17","/en/common":"d918932fcd1d34b2d84cb463812bd157","/en/dashboard":"6de8356a6ed53c109746c0f7ef37ffcf","/en/errorDetails":"eb83b35f49830420547f30c08bd88c4e","/en/explorer":"98106bbc79e701d81f5731dd53a158f0","/en/generalSettings":"65fca62d2f109d73fa4bdc447c353857","/en/licenses":"dc2fea934c67b5b3bf8c940019d820cd","/en/login":"c9d63ef04a9af5ae6aed12b4b725add5","/en/logs":"de363f7feee26d9fc72eccdf69988f09","/en/organizationsettings":"e24624bba7bdd7bf071873940742b1a8","/en/pipeline":"9f75c31214b2ae9d362bb6e5985c2e1f","/en/routes":"08585a25257ed898131ba43e4c927d7e","/en/rules":"f134663a0943cdb8cd2a2c169f27ba90","/en/settings":"ffabe7ca89d7992d9639695b4df4d6e9","/en/signup":"59c64809b8b4c7b1b8902da5aa2315f0","/en/titles":"49d542f8f3ca9291777b9042ed8faf1a","/en/trace":"fcf3fda7bee8b609b5a3ab0f749f2594","/en/traceDetails":"f91795f15c286f15bf630a454febb015","/en/translation":"921a0256c8d4d3522754557b41e24362","/en/valueGraph":"cc57d9b83919574016dab2fc9e5adedf"}

View File

@@ -36,18 +36,5 @@ export const getXAxisScale = (
maxTime = fallbackMax;
}
// As API response is adjusted to return values for T - 1 min (end timestamp) due to which graph would not have the current timestamp data, we see the empty space in the graph for smaller timeframe. To accommodate this, we will be plotting the graph from (startTime -> endTime - 1 min).
const oneMinuteAgoTimestamp = (maxTime - 60) * 1000;
const currentDate = new Date(oneMinuteAgoTimestamp);
// Set seconds and milliseconds to zero
currentDate.setSeconds(0);
currentDate.setMilliseconds(0);
// Get the Unix timestamp in seconds
const unixTimestampSeconds = Math.floor(currentDate.getTime() / 1000);
maxTime = unixTimestampSeconds;
return { time: true, auto: false, range: [minTime, maxTime] };
};

View File

@@ -3,8 +3,8 @@ import { convertValue } from 'lib/getConvertedValue';
import { QueryDataV3 } from 'types/api/widgets/getQuery';
function findMinMaxValues(data: QueryDataV3[]): [number, number] {
let min = Number.MAX_SAFE_INTEGER;
let max = Number.MIN_SAFE_INTEGER;
let min = 0;
let max = 0;
data?.forEach((entry) => {
entry.series?.forEach((series) => {
series.values.forEach((valueObj) => {
@@ -23,18 +23,20 @@ function findMinMaxThresholdValues(
thresholds: ThresholdProps[],
yAxisUnit?: string,
): [number, number] {
let minThresholdValue =
thresholds[0].thresholdValue || Number.MAX_SAFE_INTEGER;
let maxThresholdValue =
thresholds[0].thresholdValue || Number.MIN_SAFE_INTEGER;
let minThresholdValue = 0;
let maxThresholdValue = 0;
thresholds.forEach((entry) => {
const { thresholdValue, thresholdUnit } = entry;
if (thresholdValue === undefined) return;
const compareValue = convertValue(thresholdValue, thresholdUnit, yAxisUnit);
if (compareValue === null) return;
minThresholdValue = Math.min(minThresholdValue, compareValue);
maxThresholdValue = Math.max(maxThresholdValue, compareValue);
minThresholdValue = Math.min(
minThresholdValue,
convertValue(thresholdValue, thresholdUnit, yAxisUnit) || 0,
);
maxThresholdValue = Math.max(
maxThresholdValue,
convertValue(thresholdValue, thresholdUnit, yAxisUnit) || 0,
);
});
return [minThresholdValue, maxThresholdValue];
@@ -77,7 +79,7 @@ export const getYAxisScale = (
auto: boolean;
range?: [number, number];
} => {
if (!thresholds || !series || thresholds.length === 0) return { auto: true };
if (!thresholds || !series) return { auto: true };
if (areAllSeriesEmpty(series)) return { auto: true };

View File

@@ -328,11 +328,7 @@ func buildMetricQuery(start, end, step int64, mq *v3.BuilderQuery, tableName str
// `ts` is always added to the group by clause
func groupingSets(tags ...string) string {
withTs := append(tags, "ts")
if len(withTs) > 1 {
return fmt.Sprintf(`GROUPING SETS ( (%s), (%s) )`, strings.Join(withTs, ", "), strings.Join(tags, ", "))
} else {
return strings.Join(withTs, ", ")
}
return fmt.Sprintf(`GROUPING SETS ( (%s), (%s) )`, strings.Join(withTs, ", "), strings.Join(tags, ", "))
}
// groupBy returns a string of comma separated tags for group by clause

View File

@@ -245,7 +245,7 @@ func TestBuildQueryOperators(t *testing.T) {
func TestBuildQueryXRate(t *testing.T) {
t.Run("TestBuildQueryXRate", func(t *testing.T) {
tmpl := `SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991980000 AND timestamp_ms <= 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts`
tmpl := `SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991980000 AND timestamp_ms <= 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY GROUPING SETS ( (ts), () ) ORDER BY ts`
cases := []struct {
aggregateOperator v3.AggregateOperator
@@ -298,7 +298,7 @@ func TestBuildQueryXRate(t *testing.T) {
func TestBuildQueryRPM(t *testing.T) {
t.Run("TestBuildQueryXRate", func(t *testing.T) {
tmpl := `SELECT ts, ceil(value * 60) as value FROM (SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991980000 AND timestamp_ms <= 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY ts ORDER BY ts)`
tmpl := `SELECT ts, ceil(value * 60) as value FROM (SELECT ts, %s(rate_value) as value FROM (SELECT ts, If((value - lagInFrame(value, 1, 0) OVER rate_window) < 0, nan, If((ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window) >= 86400, nan, (value - lagInFrame(value, 1, 0) OVER rate_window) / (ts - lagInFrame(ts, 1, toDate('1970-01-01')) OVER rate_window))) as rate_value FROM(SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, max(value) as value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'name' AND temporality IN ['Cumulative', 'Unspecified']) as filtered_time_series USING fingerprint WHERE metric_name = 'name' AND timestamp_ms >= 1650991980000 AND timestamp_ms <= 1651078380000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WINDOW rate_window as (PARTITION BY fingerprint ORDER BY fingerprint, ts) ) WHERE isNaN(rate_value) = 0 GROUP BY GROUPING SETS ( (ts), () ) ORDER BY ts)`
cases := []struct {
aggregateOperator v3.AggregateOperator

View File

@@ -11,11 +11,7 @@ import (
// `ts` is always added to the group by clause
func groupingSets(tags ...string) string {
withTs := append(tags, "ts")
if len(withTs) > 1 {
return fmt.Sprintf(`GROUPING SETS ( (%s), (%s) )`, strings.Join(withTs, ", "), strings.Join(tags, ", "))
} else {
return strings.Join(withTs, ", ")
}
return fmt.Sprintf(`GROUPING SETS ( (%s), (%s) )`, strings.Join(withTs, ", "), strings.Join(tags, ", "))
}
// groupingSetsByAttributeKeyTags returns a string of comma separated tags for group by clause

View File

@@ -168,7 +168,7 @@ func TestPrepareTimeseriesQuery(t *testing.T) {
},
start: 1701794980000,
end: 1701796780000,
expectedQueryContains: "SELECT ts, sum(per_series_value) as value FROM (SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, avg(value) as per_series_value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'system_memory_usage' AND temporality = 'Unspecified' AND JSONExtractString(labels, 'state') != 'idle') as filtered_time_series USING fingerprint WHERE metric_name = 'system_memory_usage' AND timestamp_ms >= 1701794980000 AND timestamp_ms <= 1701796780000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WHERE isNaN(per_series_value) = 0 GROUP BY ts ORDER BY ts ASC",
expectedQueryContains: "SELECT ts, sum(per_series_value) as value FROM (SELECT fingerprint, toStartOfInterval(toDateTime(intDiv(timestamp_ms, 1000)), INTERVAL 60 SECOND) as ts, avg(value) as per_series_value FROM signoz_metrics.distributed_samples_v2 INNER JOIN (SELECT DISTINCT fingerprint FROM signoz_metrics.time_series_v2 WHERE metric_name = 'system_memory_usage' AND temporality = 'Unspecified' AND JSONExtractString(labels, 'state') != 'idle') as filtered_time_series USING fingerprint WHERE metric_name = 'system_memory_usage' AND timestamp_ms >= 1701794980000 AND timestamp_ms <= 1701796780000 GROUP BY fingerprint, ts ORDER BY fingerprint, ts) WHERE isNaN(per_series_value) = 0 GROUP BY GROUPING SETS ( (ts), () ) ORDER BY ts ASC",
},
{
name: "test time aggregation = rate, space aggregation = sum, temporality = cumulative",