fix: step size is made dynamic (#2903)

* fix: step size is made dynamic

* test: get step test is added

* chore: alerts step is updated

* chore: query is updated

* chore: provider query is updated

* fix: user input is being take care of

* chore: query builder step interval is updated

* test: lib/getStep is updated

* test: test for getStep is updated

* fix: step interval is taken care when we change from top nav

* chore: while saving the dashboard query is updated

* chore: updated when selected widget is present

* chore: getStep is now multiple of 60 and test is updated accordingly

* chore: user input is overriden from global step

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
Palash Gupta 2023-06-28 15:19:52 +05:30 committed by GitHub
parent 56402b0d40
commit 9b8f7a091c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 338 additions and 105 deletions

View File

@ -125,7 +125,7 @@ const initialQueryBuilderFormValues: IBuilderQuery = {
}),
disabled: false,
having: [],
stepInterval: 30,
stepInterval: 60,
limit: null,
orderBy: [],
groupBy: [],

View File

@ -8,6 +8,7 @@ import QueryTypeTag from 'container/NewWidget/LeftContainer/QueryTypeTag';
import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
@ -16,6 +17,8 @@ import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQu
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
AlertDef,
@ -24,6 +27,7 @@ import {
} from 'types/api/alerts/def';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
import BasicInfo from './BasicInfo';
import ChartPreview from './ChartPreview';
@ -48,6 +52,10 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const {
currentQuery,
stagedQuery,
@ -76,10 +84,6 @@ function FormAlertRules({
setAlertDef(initialValue);
}, [initialValue]);
const onRunQuery = (): void => {
handleRunQuery();
};
const onCancelHandler = useCallback(() => {
history.replace(ROUTES.LIST_ALL_ALERT);
}, []);
@ -99,7 +103,7 @@ function FormAlertRules({
}
const query: Query = { ...currentQuery, queryType: val };
redirectWithQueryBuilderData(query);
redirectWithQueryBuilderData(updateStepInterval(query, maxTime, minTime));
};
const { notifications } = useNotifications();
@ -402,7 +406,7 @@ function FormAlertRules({
queryCategory={currentQuery.queryType}
setQueryCategory={onQueryCategoryChange}
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
runQuery={onRunQuery}
runQuery={handleRunQuery}
/>
<RuleOptions

View File

@ -8,6 +8,7 @@ import {
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';
@ -48,11 +49,13 @@ function FullView({
[selectedTime, globalSelectedTime, widget],
);
const updatedQuery = useStepInterval(widget?.query);
const response = useGetQueryRange(
{
selectedTime: selectedTime.enum,
graphType: widget.panelTypes,
query: widget.query,
query: updatedQuery,
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(),
},
@ -84,10 +87,8 @@ function FullView({
{fullViewOptions && (
<TimeContainer>
<TimePreference
{...{
selectedTime,
setSelectedTime,
}}
selectedTime={selectedTime}
setSelectedTime={setSelectedTime}
/>
<Button
onClick={(): void => {

View File

@ -4,6 +4,7 @@ import Spinner from 'components/Spinner';
import GridGraphComponent from 'container/GridGraphComponent';
import { UpdateDashboard } from 'container/GridGraphLayout/utils';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useNotifications } from 'hooks/useNotifications';
import usePreviousValue from 'hooks/usePreviousValue';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
@ -80,11 +81,13 @@ function GridCardGraph({
const selectedData = selectedDashboard?.data;
const { variables } = selectedData;
const updatedQuery = useStepInterval(widget?.query);
const queryResponse = useGetQueryRange(
{
selectedTime: widget?.timePreferance,
graphType: widget?.panelTypes,
query: widget?.query,
query: updatedQuery,
globalSelectedInterval,
variables: getDashboardVariables(),
},

View File

@ -2,6 +2,8 @@ import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder';
import getStep from 'lib/getStep';
import store from 'store';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import {
@ -24,6 +26,11 @@ export const getQueryBuilderQueries = ({
groupBy,
aggregateAttribute: metricName,
legend,
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
reduceTo: 'sum',
filters: {
items: itemsA,
@ -64,6 +71,11 @@ export const getQueryBuilderQuerieswithFormula = ({
items: additionalItemsA,
op: 'AND',
},
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
},
{
...initialQueryBuilderFormValuesMap.metrics,
@ -79,6 +91,11 @@ export const getQueryBuilderQuerieswithFormula = ({
items: additionalItemsB,
op: 'AND',
},
stepInterval: getStep({
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
start: store.getState().globalTime.minTime,
}),
},
],
});

View File

@ -11,11 +11,11 @@ import {
} from 'hooks/useResourceAttribute/utils';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
import {
@ -25,7 +25,7 @@ import {
onViewTracePopupClick,
} from './util';
function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
function DBCall(): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { queries } = useResourceAttribute();
@ -59,7 +59,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
const databaseCallsAverageDurationWidget = useMemo(
() =>
@ -73,7 +73,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
return (
@ -151,8 +151,4 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
);
}
interface DBCallProps {
getWidgetQueryBuilder: (query: Widgets['query']) => Widgets;
}
export default DBCall;

View File

@ -13,10 +13,10 @@ import {
} from 'hooks/useResourceAttribute/utils';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
import { Button } from './styles';
@ -26,7 +26,7 @@ import {
onViewTracePopupClick,
} from './util';
function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
function External(): JSX.Element {
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { servicename } = useParams<{ servicename?: string }>();
@ -51,7 +51,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
const selectedTraceTags = useMemo(
@ -71,7 +71,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
const externalCallRPSWidget = useMemo(
@ -87,7 +87,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
const externalCallDurationAddressWidget = useMemo(
@ -103,7 +103,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
[servicename, tagFilterItems],
);
return (
@ -261,8 +261,4 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
);
}
interface ExternalProps {
getWidgetQueryBuilder: (query: Widgets['query']) => Widgets;
}
export default External;

View File

@ -18,11 +18,11 @@ import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useParams } from 'react-router-dom';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import MetricReducer from 'types/reducer/metrics';
import { v4 as uuid } from 'uuid';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import {
errorPercentage,
operationPerSec,
@ -36,7 +36,7 @@ import {
onViewTracePopupClick,
} from './util';
function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
function Application(): JSX.Element {
const { servicename } = useParams<{ servicename?: string }>();
const [selectedTimeStamp, setSelectedTimeStamp] = useState<number>(0);
const { search } = useLocation();
@ -94,7 +94,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, topLevelOperations, tagFilterItems],
[servicename, topLevelOperations, tagFilterItems],
);
const errorPercentageWidget = useMemo(
@ -110,7 +110,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
clickhouse_sql: [],
id: uuid(),
}),
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
[servicename, topLevelOperations, tagFilterItems],
);
const onDragSelect = useCallback(
@ -289,10 +289,6 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
);
}
interface DashboardProps {
getWidgetQueryBuilder: (query: Widgets['query']) => Widgets;
}
type ClickHandlerType = (
ChartEvent: ChartEvent,
activeElements: ActiveElement[],

View File

@ -6,21 +6,20 @@ import { memo, useMemo } from 'react';
import { generatePath, useParams } from 'react-router-dom';
import { useLocation } from 'react-use';
import { getWidgetQueryBuilder } from './MetricsApplication.factory';
import DBCall from './Tabs/DBCall';
import External from './Tabs/External';
import Overview from './Tabs/Overview';
function OverViewTab(): JSX.Element {
return <Overview getWidgetQueryBuilder={getWidgetQueryBuilder} />;
return <Overview />;
}
function DbCallTab(): JSX.Element {
return <DBCall getWidgetQueryBuilder={getWidgetQueryBuilder} />;
return <DBCall />;
}
function ExternalTab(): JSX.Element {
return <External getWidgetQueryBuilder={getWidgetQueryBuilder} />;
return <External />;
}
function ServiceMetrics(): JSX.Element {

View File

@ -6,6 +6,7 @@ import { QueryBuilder } from 'container/QueryBuilder';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback } from 'react';
import { connect, useSelector } from 'react-redux';
@ -22,6 +23,7 @@ import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards';
import { GlobalReducer } from 'types/reducer/globalTime';
import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
@ -33,6 +35,11 @@ function QuerySection({
}: QueryProps): JSX.Element {
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery();
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const { featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
@ -67,10 +74,19 @@ function QuerySection({
yAxisUnit: selectedWidget.yAxisUnit,
});
redirectWithQueryBuilderData(updatedQuery);
redirectWithQueryBuilderData(
updateStepInterval(updatedQuery, maxTime, minTime),
);
},
[urlQuery, selectedWidget, updateQuery, redirectWithQueryBuilderData],
[
updateQuery,
urlQuery,
selectedWidget.yAxisUnit,
redirectWithQueryBuilderData,
maxTime,
minTime,
],
);
const handleQueryCategoryChange = (qCategory: string): void => {

View File

@ -1,11 +1,7 @@
import { Input } from 'antd';
import getStep from 'lib/getStep';
import { InputNumber, InputNumberProps } from 'antd';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { selectStyle } from '../QueryBuilderSearch/config';
@ -13,49 +9,27 @@ function AggregateEveryFilter({
onChange,
query,
}: AggregateEveryFilterProps): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const stepInterval = useMemo(
() =>
getStep({
start: minTime,
end: maxTime,
inputFormat: 'ns',
}),
[maxTime, minTime],
);
const handleKeyDown = (event: {
keyCode: number;
which: number;
preventDefault: () => void;
}): void => {
const keyCode = event.keyCode || event.which;
const isBackspace = keyCode === 8;
const isNumeric =
(keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105);
if (!isNumeric && !isBackspace) {
event.preventDefault();
}
};
const isMetricsDataSource = useMemo(
() => query.dataSource === DataSource.METRICS,
[query.dataSource],
);
const onChangeHandler: InputNumberProps<number>['onChange'] = (event) => {
if (event && event >= 0) {
onChange(event);
}
};
const isDisabled = isMetricsDataSource && !query.aggregateAttribute.key;
return (
<Input
type="text"
<InputNumber
placeholder="Enter in seconds"
disabled={isMetricsDataSource && !query.aggregateAttribute.key}
disabled={isDisabled}
style={selectStyle}
defaultValue={query.stepInterval ?? stepInterval}
onChange={(event): void => onChange(Number(event.target.value))}
onKeyDown={handleKeyDown}
value={query.stepInterval}
onChange={onChangeHandler}
min={0}
/>
);
}

View File

@ -4,6 +4,9 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
import { LOCALSTORAGE } from 'constants/localStorage';
import dayjs, { Dayjs } from 'dayjs';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import GetMinMax from 'lib/getMinMax';
import getTimeString from 'lib/getTimeString';
import { useCallback, useEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux';
@ -66,6 +69,8 @@ function DateTimeSelection({
false,
);
const { stagedQuery, initQueryBuilderData } = useQueryBuilder();
const { maxTime, minTime, selectedTime } = useSelector<
AppState,
GlobalReducer
@ -174,6 +179,14 @@ function DateTimeSelection({
setRefreshButtonHidden(true);
setCustomDTPickerVisible(true);
}
if (!stagedQuery) {
return;
}
const { maxTime, minTime } = GetMinMax(value, getTime());
initQueryBuilderData(updateStepInterval(stagedQuery, maxTime, minTime));
};
const onRefreshHandler = (): void => {

View File

@ -9,8 +9,8 @@ import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { GlobalReducer } from 'types/reducer/globalTime';
import { useGetCompositeQueryParam } from './useGetCompositeQueryParam';
import { useGetQueryRange } from './useGetQueryRange';
import { useQueryBuilder } from './useQueryBuilder';
export const useGetWidgetQueryRange = (
{
@ -24,24 +24,24 @@ export const useGetWidgetQueryRange = (
GlobalReducer
>((state) => state.globalTime);
const compositeQuery = useGetCompositeQueryParam();
const { stagedQuery } = useQueryBuilder();
return useGetQueryRange(
{
graphType,
selectedTime,
globalSelectedInterval,
query: compositeQuery || initialQueriesMap.metrics,
query: stagedQuery || initialQueriesMap.metrics,
variables: getDashboardVariables(),
},
{
enabled: !!compositeQuery,
enabled: !!stagedQuery,
retry: false,
queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE,
selectedTime,
globalSelectedInterval,
compositeQuery,
stagedQuery,
],
...options,
},

View File

@ -0,0 +1,37 @@
import getStep from 'lib/getStep';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { GlobalReducer } from 'types/reducer/globalTime';
export const updateStepInterval = (
query: Widgets['query'],
maxTime: number,
minTime: number,
): Widgets['query'] => {
const stepInterval = getStep({
start: minTime,
end: maxTime,
inputFormat: 'ns',
});
return {
...query,
builder: {
...query?.builder,
queryData:
query?.builder?.queryData?.map((item) => ({
...item,
stepInterval,
})) || [],
},
};
};
export const useStepInterval = (query: Widgets['query']): Widgets['query'] => {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
return updateStepInterval(query, maxTime, minTime);
};

View File

@ -39,7 +39,12 @@ describe('lib/getStep', () => {
const startUnix = start.valueOf();
const endUnix = end.valueOf();
const expectedStepSize = Math.floor(end.diff(start, 's') / MaxDataPoints);
let expectedStepSize = Math.max(
Math.floor(end.diff(start, 's') / MaxDataPoints),
DefaultStepSize,
);
expectedStepSize -= expectedStepSize % 60;
expect(
getStep({

View File

@ -7,7 +7,7 @@ export const getDashboardVariables = (): Record<string, unknown> => {
globalTime,
dashboards: { dashboards },
} = store.getState();
const [selectedDashboard] = dashboards;
const [selectedDashboard] = dashboards || [];
const {
data: { variables = {} },
} = selectedDashboard;

View File

@ -0,0 +1,137 @@
import dayjs from 'dayjs';
import getStep, { DefaultStepSize, MaxDataPoints } from './getStep';
describe('get dynamic step size', () => {
test('should return default step size if diffSec is less than MaxDataPoints', () => {
const start = dayjs().subtract(1, 'minute').valueOf();
const end = dayjs().valueOf();
const step = getStep({
start,
end,
inputFormat: 'ms',
});
expect(step).toBe(DefaultStepSize);
});
test('should return appropriate step size if diffSec is more than MaxDataPoints', () => {
const start = dayjs().subtract(4, 'hour').valueOf();
const end = dayjs().valueOf();
const step = getStep({
start,
end,
inputFormat: 'ms',
});
// the expected step size should be no less than DefaultStepSize
const diffSec = Math.abs(dayjs(end).diff(dayjs(start), 's'));
const expectedStep = Math.max(
Math.floor(diffSec / MaxDataPoints),
DefaultStepSize,
);
expect(step).toBe(expectedStep);
});
test('should correctly handle different input formats', () => {
const endSec = dayjs().unix();
const startSec = endSec - 4 * 3600; // 4 hours earlier
const stepSec = getStep({
start: startSec,
end: endSec,
inputFormat: 's',
});
const diffSec = Math.abs(dayjs.unix(endSec).diff(dayjs.unix(startSec), 's'));
const expectedStep = Math.max(
Math.floor(diffSec / MaxDataPoints),
DefaultStepSize,
);
expect(stepSec).toBe(expectedStep);
const startNs = startSec * 1e9; // convert to nanoseconds
const endNs = endSec * 1e9; // convert to nanoseconds
const stepNs = getStep({
start: startNs,
end: endNs,
inputFormat: 'ns',
});
expect(stepNs).toBe(expectedStep); // Expect the same result as 's' inputFormat
});
test('should throw an error for invalid input format', () => {
const start = dayjs().valueOf();
const end = dayjs().valueOf();
expect(() => {
getStep({
start,
end,
inputFormat: 'invalid' as never,
});
}).toThrow('invalid format');
});
test('should return DefaultStepSize when start and end are the same', () => {
const start = dayjs().valueOf();
const end = start; // same as start
const step = getStep({
start,
end,
inputFormat: 'ms',
});
expect(step).toBe(DefaultStepSize);
});
test('should return DefaultStepSize if diffSec is exactly MaxDataPoints', () => {
const endMs = dayjs().valueOf();
const startMs = endMs - MaxDataPoints * 1000; // exactly MaxDataPoints seconds earlier
const step = getStep({
start: startMs,
end: endMs,
inputFormat: 'ms',
});
expect(step).toBe(DefaultStepSize); // since calculated step size is less than DefaultStepSize, it should return DefaultStepSize
});
test('should return DefaultStepSize for future dates less than (MaxDataPoints * DefaultStepSize) seconds ahead', () => {
const start = dayjs().valueOf();
const end = start + MaxDataPoints * DefaultStepSize * 1000 - 1; // just one millisecond less than (MaxDataPoints * DefaultStepSize) seconds ahead
const step = getStep({
start,
end,
inputFormat: 'ms',
});
expect(step).toBe(DefaultStepSize);
});
test('should handle string inputs correctly for a time range greater than (MaxDataPoints * DefaultStepSize) seconds', () => {
const endMs = dayjs().valueOf();
const startMs = endMs - (MaxDataPoints * DefaultStepSize * 1000 + 1); // one millisecond more than (MaxDataPoints * DefaultStepSize) seconds earlier
const step = getStep({
start: startMs.toString(),
end: endMs.toString(),
inputFormat: 'ms',
});
const diffSec = Math.abs(
dayjs(Number(endMs)).diff(dayjs(Number(startMs)), 's'),
);
expect(step).toBe(Math.floor(diffSec / MaxDataPoints));
});
});

View File

@ -30,7 +30,7 @@ const convertToMs = (
};
export const DefaultStepSize = 60;
export const MaxDataPoints = 200;
export const MaxDataPoints = 300;
/**
* Returns relevant step size based on given start and end date.
@ -40,7 +40,13 @@ const getStep = ({ start, end, inputFormat = 'ms' }: GetStepInput): number => {
const endDate = dayjs(convertToMs(Number(end), inputFormat));
const diffSec = Math.abs(endDate.diff(startDate, 's'));
return Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize);
let result =
Math.max(Math.floor(diffSec / MaxDataPoints), DefaultStepSize) ||
DefaultStepSize;
result -= result % 60;
return result;
};
export default getStep;

View File

@ -16,6 +16,7 @@ import {
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import useUrlQuery from 'hooks/useUrlQuery';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
@ -29,7 +30,9 @@ import {
useMemo,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { useHistory, useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
// ** Types
import {
IBuilderFormula,
@ -45,6 +48,7 @@ import {
QueryBuilderContextType,
QueryBuilderData,
} from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuid } from 'uuid';
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
@ -66,6 +70,7 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
handleRunQuery: () => {},
resetStagedQuery: () => {},
updateAllQueriesOperators: () => initialQueriesMap.metrics,
initQueryBuilderData: () => {},
});
export function QueryBuilderProvider({
@ -74,6 +79,9 @@ export function QueryBuilderProvider({
const urlQuery = useUrlQuery();
const history = useHistory();
const location = useLocation();
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const compositeQueryParam = useGetCompositeQueryParam();
const { queryType: queryTypeParam, ...queryState } =
@ -356,7 +364,6 @@ export function QueryBuilderProvider({
) => T[] = useCallback(
(arr, index, newQueryItem) =>
arr.map((item, idx) => (index === idx ? newQueryItem : item)),
[],
);
@ -465,7 +472,7 @@ export function QueryBuilderProvider({
history.push(generatedUrl);
},
[history, location, urlQuery],
[history, location.pathname, urlQuery],
);
const handleSetConfig = useCallback(
@ -477,8 +484,24 @@ export function QueryBuilderProvider({
);
const handleRunQuery = useCallback(() => {
redirectWithQueryBuilderData({ ...currentQuery, queryType });
}, [redirectWithQueryBuilderData, currentQuery, queryType]);
redirectWithQueryBuilderData({
...{
...currentQuery,
...updateStepInterval(
{
builder: currentQuery.builder,
clickhouse_sql: currentQuery.clickhouse_sql,
promql: currentQuery.promql,
id: currentQuery.id,
queryType,
},
maxTime,
minTime,
),
},
queryType,
});
}, [currentQuery, queryType, maxTime, minTime, redirectWithQueryBuilderData]);
const resetStagedQuery = useCallback(() => {
setStagedQuery(null);
@ -541,6 +564,7 @@ export function QueryBuilderProvider({
handleRunQuery,
resetStagedQuery,
updateAllQueriesOperators,
initQueryBuilderData,
}),
[
query,
@ -561,6 +585,7 @@ export function QueryBuilderProvider({
handleRunQuery,
resetStagedQuery,
updateAllQueriesOperators,
initQueryBuilderData,
],
);

View File

@ -3,18 +3,19 @@
// @ts-nocheck
import { getMetricsQueryRange } from 'api/metrics/getQueryRange';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Time } from 'container/TopNav/DateTimeSelection/config';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import getStep from 'lib/getStep';
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import store from 'store';
import { SuccessResponse } from 'types/api';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { convertNewDataToOld } from 'lib/newQueryBuilder/convertNewDataToOld';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
export async function GetMetricQueryRange({
query,
@ -89,7 +90,11 @@ export async function GetMetricQueryRange({
const response = await getMetricsQueryRange({
start: parseInt(start, 10) * 1e3,
end: parseInt(end, 10) * 1e3,
step: getStep({ start, end, inputFormat: 'ms' }),
step: getStep({
start: store.getState().globalTime.minTime,
end: store.getState().globalTime.maxTime,
inputFormat: 'ns',
}),
variables,
...QueryPayload,
...params,

View File

@ -4,6 +4,7 @@ import { AxiosError } from 'axios';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import history from 'lib/history';
import { Layout } from 'react-grid-layout';
import { generatePath } from 'react-router-dom';
@ -88,9 +89,10 @@ export const SaveDashboard = ({
const allLayout = getAllLayout();
const params = new URLSearchParams(window.location.search);
const compositeQuery = params.get(COMPOSITE_QUERY);
const { maxTime, minTime } = store.getState().globalTime;
const query = compositeQuery
? JSON.parse(compositeQuery)
: selectedWidget.query;
? updateStepInterval(JSON.parse(compositeQuery), maxTime, minTime)
: updateStepInterval(selectedWidget.query, maxTime, minTime);
const response = await updateDashboardApi({
data: {

View File

@ -192,6 +192,7 @@ export type QueryBuilderContextType = {
panelType: GRAPH_TYPES,
dataSource: DataSource,
) => Query;
initQueryBuilderData: (query: Query) => void;
};
export type QueryAdditionalFilter = {