mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 01:36:10 +08:00
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:
parent
56402b0d40
commit
9b8f7a091c
@ -125,7 +125,7 @@ const initialQueryBuilderFormValues: IBuilderQuery = {
|
||||
}),
|
||||
disabled: false,
|
||||
having: [],
|
||||
stepInterval: 30,
|
||||
stepInterval: 60,
|
||||
limit: null,
|
||||
orderBy: [],
|
||||
groupBy: [],
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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(),
|
||||
},
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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[],
|
||||
|
@ -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 {
|
||||
|
@ -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 => {
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -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 => {
|
||||
|
@ -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,
|
||||
},
|
||||
|
37
frontend/src/hooks/queryBuilder/useStepInterval.ts
Normal file
37
frontend/src/hooks/queryBuilder/useStepInterval.ts
Normal 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);
|
||||
};
|
@ -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({
|
||||
|
@ -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;
|
||||
|
137
frontend/src/lib/getStep.test.ts
Normal file
137
frontend/src/lib/getStep.test.ts
Normal 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));
|
||||
});
|
||||
});
|
@ -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;
|
||||
|
@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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: {
|
||||
|
@ -192,6 +192,7 @@ export type QueryBuilderContextType = {
|
||||
panelType: GRAPH_TYPES,
|
||||
dataSource: DataSource,
|
||||
) => Query;
|
||||
initQueryBuilderData: (query: Query) => void;
|
||||
};
|
||||
|
||||
export type QueryAdditionalFilter = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user