feat: add share url (#2778)

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-06-01 20:17:09 +03:00 committed by GitHub
parent 7fd27ec09d
commit ad5a9dcd6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 405 additions and 203 deletions

View File

@ -8,6 +8,7 @@ import {
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
Query,
QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
@ -158,6 +159,11 @@ export const initialQuery: QueryState = {
promql: [initialQueryPromQLData],
};
export const initialQueryWithType: Query = {
...initialQuery,
queryType: EQueryType.QUERY_BUILDER,
};
export const operatorsByTypes: Record<LocalDataType, string[]> = {
string: Object.values(StringOperators),
number: Object.values(NumberOperators),

View File

@ -0,0 +1 @@
export const COMPOSITE_QUERY = 'compositeQuery';

View File

@ -1,5 +1,6 @@
import {
initialQueryBuilderFormValues,
initialQueryPromQLData,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@ -31,11 +32,9 @@ export const alertDefaults: AlertDef = {
condition: {
compositeQuery: {
builderQueries: {
A: {
...initialQueryBuilderFormValues,
A: initialQueryBuilderFormValues,
},
},
promQueries: {},
promQueries: { A: initialQueryPromQLData },
chQueries: {
A: {
name: 'A',
@ -69,7 +68,7 @@ export const logAlertDefaults: AlertDef = {
dataSource: DataSource.LOGS,
},
},
promQueries: {},
promQueries: { A: initialQueryPromQLData },
chQueries: {
A: {
name: 'A',
@ -104,7 +103,7 @@ export const traceAlertDefaults: AlertDef = {
dataSource: DataSource.TRACES,
},
},
promQueries: {},
promQueries: { A: initialQueryPromQLData },
chQueries: {
A: {
name: 'A',
@ -139,7 +138,7 @@ export const exceptionAlertDefaults: AlertDef = {
dataSource: DataSource.TRACES,
},
},
promQueries: {},
promQueries: { A: initialQueryPromQLData },
chQueries: {
A: {
name: 'A',
@ -162,3 +161,10 @@ export const exceptionAlertDefaults: AlertDef = {
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,
};
export const ALERTS_VALUES_MAP: Record<AlertTypes, AlertDef> = {
[AlertTypes.METRICS_BASED_ALERT]: alertDefaults,
[AlertTypes.LOGS_BASED_ALERT]: logAlertDefaults,
[AlertTypes.TRACES_BASED_ALERT]: traceAlertDefaults,
[AlertTypes.EXCEPTIONS_BASED_ALERT]: exceptionAlertDefaults,
};

View File

@ -1,10 +1,16 @@
import { Form, Row } from 'antd';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import FormAlertRules from 'container/FormAlertRules';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useUrlQuery from 'hooks/useUrlQuery';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useState } from 'react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
import {
alertDefaults,
ALERTS_VALUES_MAP,
exceptionAlertDefaults,
logAlertDefaults,
traceAlertDefaults,
@ -12,13 +18,18 @@ import {
import SelectAlertType from './SelectAlertType';
function CreateRules(): JSX.Element {
const [initValues, setInitValues] = useState(alertDefaults);
const [step, setStep] = useState(0);
const [initValues, setInitValues] = useState<AlertDef>(alertDefaults);
const [alertType, setAlertType] = useState<AlertTypes>(
AlertTypes.METRICS_BASED_ALERT,
);
const [formInstance] = Form.useForm();
const urlQuery = useUrlQuery();
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
const { redirectWithQueryBuilderData } = useQueryBuilder();
const onSelectType = (typ: AlertTypes): void => {
setAlertType(typ);
switch (typ) {
@ -34,16 +45,22 @@ function CreateRules(): JSX.Element {
default:
setInitValues(alertDefaults);
}
setStep(1);
const value = ALERTS_VALUES_MAP[typ].condition.compositeQuery;
const compositeQuery = mapQueryDataFromApi(value);
redirectWithQueryBuilderData(compositeQuery);
};
if (step === 0) {
if (!compositeQuery) {
return (
<Row wrap={false}>
<SelectAlertType onSelect={onSelectType} />
</Row>
);
}
return (
<FormAlertRules
alertType={alertType}

View File

@ -7,11 +7,13 @@ import ROUTES from 'constants/routes';
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 { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@ -35,7 +37,7 @@ import {
StyledLeftContainer,
} from './styles';
import UserGuide from './UserGuide';
import { prepareStagedQuery, toChartInterval } from './utils';
import { toChartInterval } from './utils';
function FormAlertRules({
alertType,
@ -46,12 +48,7 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const {
currentQuery,
queryType,
handleSetQueryType,
initQueryBuilderData,
} = useQueryBuilder();
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
// use query client
const ruleCache = useQueryClient();
@ -62,7 +59,11 @@ function FormAlertRules({
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
// initQuery contains initial query when component was mounted
const initQuery = initialValue.condition.compositeQuery;
const initQuery = useMemo(() => initialValue.condition.compositeQuery, [
initialValue,
]);
const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]);
// manualStagedQuery requires manual staging of query
// when user clicks run query button. Useful for clickhouse tab where
@ -73,26 +74,26 @@ function FormAlertRules({
// other queries based on server data.
// useful when fetching of initial values (from api)
// is delayed
const { compositeQuery } = useShareBuilderUrl({ defaultValue: sq });
useEffect(() => {
const type = initQuery.queryType;
// prepare staged query
const sq = prepareStagedQuery(
type,
initQuery?.builderQueries,
initQuery?.promQueries,
initQuery?.chQueries,
);
initQueryBuilderData(sq, type);
setManualStagedQuery(sq);
if (compositeQuery && !manualStagedQuery) {
setManualStagedQuery(compositeQuery);
}
setAlertDef(initialValue);
}, [initialValue, initQueryBuilderData, initQuery]);
}, [
initialValue,
initQuery,
redirectWithQueryBuilderData,
currentQuery,
manualStagedQuery,
compositeQuery,
]);
const onRunQuery = (): void => {
setManualStagedQuery({ ...currentQuery, queryType });
setManualStagedQuery(currentQuery);
redirectWithQueryBuilderData(currentQuery);
};
const onCancelHandler = useCallback(() => {
@ -102,7 +103,6 @@ function FormAlertRules({
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
const onQueryCategoryChange = (val: EQueryType): void => {
handleSetQueryType(val);
if (val === EQueryType.PROM) {
setAlertDef({
...alertDef,
@ -113,14 +113,17 @@ function FormAlertRules({
evalWindow: defaultEvalWindow,
});
}
const query: Query = { ...currentQuery, queryType: val };
setManualStagedQuery({ ...currentQuery, queryType: val });
setManualStagedQuery(query);
redirectWithQueryBuilderData(query);
};
const { notifications } = useNotifications();
const validatePromParams = useCallback((): boolean => {
let retval = true;
if (queryType !== EQueryType.PROM) return retval;
if (currentQuery.queryType !== EQueryType.PROM) return retval;
if (!currentQuery.promql || currentQuery.promql.length === 0) {
notifications.error({
@ -141,11 +144,11 @@ function FormAlertRules({
});
return retval;
}, [t, currentQuery, queryType, notifications]);
}, [t, currentQuery, notifications]);
const validateChQueryParams = useCallback((): boolean => {
let retval = true;
if (queryType !== EQueryType.CLICKHOUSE) return retval;
if (currentQuery.queryType !== EQueryType.CLICKHOUSE) return retval;
if (
!currentQuery.clickhouse_sql ||
@ -169,10 +172,10 @@ function FormAlertRules({
});
return retval;
}, [t, queryType, currentQuery, notifications]);
}, [t, currentQuery, notifications]);
const validateQBParams = useCallback((): boolean => {
if (queryType !== EQueryType.QUERY_BUILDER) return true;
if (currentQuery.queryType !== EQueryType.QUERY_BUILDER) return true;
if (
!currentQuery.builder.queryData ||
@ -194,7 +197,7 @@ function FormAlertRules({
}
return true;
}, [t, alertDef, queryType, currentQuery, notifications]);
}, [t, alertDef, currentQuery, notifications]);
const isFormValid = useCallback((): boolean => {
if (!alertDef.alert || alertDef.alert === '') {
@ -228,7 +231,10 @@ function FormAlertRules({
...alertDef,
alertType,
source: window?.location.toString(),
ruleType: queryType === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
ruleType:
currentQuery.queryType === EQueryType.PROM
? 'promql_rule'
: 'threshold_rule',
condition: {
...alertDef.condition,
compositeQuery: {
@ -239,7 +245,7 @@ function FormAlertRules({
},
promQueries: mapQueryDataToApi(currentQuery.promql, 'name').data,
chQueries: mapQueryDataToApi(currentQuery.clickhouse_sql, 'name').data,
queryType,
queryType: currentQuery.queryType,
panelType: initQuery.panelType,
},
},
@ -248,7 +254,6 @@ function FormAlertRules({
};
const memoizedPreparePostData = useCallback(preparePostData, [
queryType,
currentQuery,
alertDef,
alertType,
@ -313,7 +318,8 @@ function FormAlertRules({
const content = (
<Typography.Text>
{' '}
{t('confirm_save_content_part1')} <QueryTypeTag queryType={queryType} />{' '}
{t('confirm_save_content_part1')}{' '}
<QueryTypeTag queryType={currentQuery.queryType} />{' '}
{t('confirm_save_content_part2')}
</Typography.Text>
);
@ -326,7 +332,7 @@ function FormAlertRules({
saveRule();
},
});
}, [t, saveRule, queryType]);
}, [t, saveRule, currentQuery]);
const onTestRuleHandler = useCallback(async () => {
if (!isFormValid()) {
@ -372,7 +378,7 @@ function FormAlertRules({
const renderQBChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryType} />}
headline={<PlotTag queryType={currentQuery.queryType} />}
name=""
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -382,7 +388,7 @@ function FormAlertRules({
const renderPromChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryType} />}
headline={<PlotTag queryType={currentQuery.queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -391,7 +397,7 @@ function FormAlertRules({
const renderChQueryChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryType} />}
headline={<PlotTag queryType={currentQuery.queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -402,7 +408,9 @@ function FormAlertRules({
const isNewRule = ruleId === 0;
const isAlertAvialableToSave =
isAlertAvialable && isNewRule && queryType === EQueryType.QUERY_BUILDER;
isAlertAvialable &&
isNewRule &&
currentQuery.queryType === EQueryType.QUERY_BUILDER;
return (
<>
@ -414,18 +422,20 @@ function FormAlertRules({
layout="vertical"
form={formInstance}
>
{queryType === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
{queryType === EQueryType.PROM && renderPromChartPreview()}
{queryType === EQueryType.CLICKHOUSE && renderChQueryChartPreview()}
{currentQuery.queryType === EQueryType.QUERY_BUILDER &&
renderQBChartPreview()}
{currentQuery.queryType === EQueryType.PROM && renderPromChartPreview()}
{currentQuery.queryType === EQueryType.CLICKHOUSE &&
renderChQueryChartPreview()}
<QuerySection
queryCategory={queryType}
queryCategory={currentQuery.queryType}
setQueryCategory={onQueryCategoryChange}
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
runQuery={onRunQuery}
/>
<RuleOptions
queryCategory={queryType}
queryCategory={currentQuery.queryType}
alertDef={alertDef}
setAlertDef={setAlertDef}
/>
@ -464,7 +474,7 @@ function FormAlertRules({
</MainFormContainer>
</StyledLeftContainer>
<Col flex="1 1 300px">
<UserGuide queryType={queryType} />
<UserGuide queryType={currentQuery.queryType} />
</Col>
</PanelContainer>
</>

View File

@ -1,43 +1,4 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import {
BuilderClickHouseResource,
BuilderPromQLResource,
BuilderQueryDataResourse,
IClickHouseQuery,
IPromQLQuery,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
export const prepareStagedQuery = (
t: EQueryType,
b: BuilderQueryDataResourse,
p: BuilderPromQLResource,
c: BuilderClickHouseResource,
): Query => {
const promList: IPromQLQuery[] = [];
const chQueryList: IClickHouseQuery[] = [];
const builder = mapQueryDataFromApi(b);
if (p) {
Object.keys(p).forEach((key) => {
promList.push({ ...p[key], name: key });
});
}
if (c) {
Object.keys(c).forEach((key) => {
chQueryList.push({ ...c[key], name: key, rawQuery: c[key].query });
});
}
return {
queryType: t,
promql: promList,
builder,
clickhouse_sql: chQueryList,
};
};
// toChartInterval converts eval window to chart selection time interval
export const toChartInterval = (evalWindow: string | undefined): Time => {

View File

@ -8,6 +8,7 @@ import {
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import { MenuItemType } from 'antd/es/menu/hooks/useItems';
import Spinner from 'components/Spinner';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { useCallback, useMemo, useState } from 'react';
@ -58,9 +59,11 @@ function WidgetHeader({
const onEditHandler = useCallback((): void => {
const widgetId = widget.id;
history.push(
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`,
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
widget.panelTypes
}&${COMPOSITE_QUERY}=${JSON.stringify(widget.query)}`,
);
}, [widget.id, widget.panelTypes]);
}, [widget.id, widget.panelTypes, widget.query]);
const keyMethodMapping: {
[K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction };

View File

@ -4,11 +4,13 @@ import { Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { ResizeTable } from 'components/ResizeTable';
import TextToolTip from 'components/TextToolTip';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useInterval from 'hooks/useInterval';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
@ -65,11 +67,19 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
.catch(handleError);
}, [featureResponse, handleError]);
const onEditHandler = (id: string): void => {
const onEditHandler = (record: GettableAlert): void => {
featureResponse
.refetch()
.then(() => {
history.push(`${ROUTES.EDIT_ALERTS}?ruleId=${id}`);
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
history.push(
`${
ROUTES.EDIT_ALERTS
}?ruleId=${record.id.toString()}&${COMPOSITE_QUERY}=${JSON.stringify(
compositeQuery,
)}`,
);
})
.catch(handleError);
};
@ -94,9 +104,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
(a.alert ? a.alert.charCodeAt(0) : 1000) -
(b.alert ? b.alert.charCodeAt(0) : 1000),
render: (value, record): JSX.Element => (
<Typography.Link
onClick={(): void => onEditHandler(record.id ? record.id.toString() : '')}
>
<Typography.Link onClick={(): void => onEditHandler(record)}>
{value}
</Typography.Link>
),
@ -154,10 +162,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
<>
<ToggleAlertState disabled={record.disabled} setData={setData} id={id} />
<ColumnButton
onClick={(): void => onEditHandler(id.toString())}
type="link"
>
<ColumnButton onClick={(): void => onEditHandler(record)} type="link">
Edit
</ColumnButton>

View File

@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { initialQueryWithType } from 'constants/queryBuilder';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
@ -43,7 +45,9 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element {
toggleAddWidget(false);
history.push(
`${history.location.pathname}/new?graphType=${name}&widgetId=${emptyLayout.i}`,
`${history.location.pathname}/new?graphType=${name}&widgetId=${
emptyLayout.i
}&${COMPOSITE_QUERY}=${JSON.stringify(initialQueryWithType)}`,
);
} catch (error) {
notifications.error({

View File

@ -3,9 +3,10 @@ import TextToolTip from 'components/TextToolTip';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { QueryBuilder } from 'container/QueryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useCallback, useEffect, useMemo } from 'react';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useState } from 'react';
import { connect, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import {
@ -15,6 +16,7 @@ import {
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { Widgets } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import DashboardReducer from 'types/reducer/dashboards';
@ -22,12 +24,10 @@ import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
const {
currentQuery,
queryType,
handleSetQueryType,
initQueryBuilderData,
} = useQueryBuilder();
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery();
const [isInit, setIsInit] = useState<boolean>(false);
const { dashboards, isLoadingQueryResult } = useSelector<
AppState,
@ -35,11 +35,8 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
>((state) => state.dashboards);
const [selectedDashboards] = dashboards;
const { search } = useLocation();
const { widgets } = selectedDashboards.data;
const urlQuery = useMemo(() => new URLSearchParams(search), [search]);
const getWidget = useCallback(() => {
const widgetId = urlQuery.get('widgetId');
return widgets?.find((e) => e.id === widgetId);
@ -47,32 +44,43 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
const selectedWidget = getWidget() as Widgets;
const { query } = selectedWidget || {};
const { query } = selectedWidget;
const { compositeQuery } = useShareBuilderUrl({ defaultValue: query });
useEffect(() => {
initQueryBuilderData(query, selectedWidget.query.queryType);
}, [query, initQueryBuilderData, selectedWidget]);
const handleStageQuery = (): void => {
if (!isInit && compositeQuery) {
setIsInit(true);
updateQuery({
updatedQuery: {
...currentQuery,
queryType,
},
updatedQuery: compositeQuery,
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
});
};
}
}, [isInit, compositeQuery, selectedWidget, urlQuery, updateQuery]);
const handleStageQuery = useCallback(
(updatedQuery: Query): void => {
updateQuery({
updatedQuery,
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
});
redirectWithQueryBuilderData(updatedQuery);
},
[urlQuery, selectedWidget, updateQuery, redirectWithQueryBuilderData],
);
const handleQueryCategoryChange = (qCategory: string): void => {
const currentQueryType = qCategory as EQueryType;
handleSetQueryType(currentQueryType);
updateQuery({
updatedQuery: { ...currentQuery, queryType: currentQueryType },
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
});
handleStageQuery({ ...currentQuery, queryType: currentQueryType });
};
const handleRunQuery = (): void => {
handleStageQuery(currentQuery);
};
const items = [
@ -100,8 +108,8 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
<Tabs
type="card"
style={{ width: '100%' }}
defaultActiveKey={queryType}
activeKey={queryType}
defaultActiveKey={currentQuery.queryType}
activeKey={currentQuery.queryType}
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
@ -109,7 +117,7 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
<Button
loading={isLoadingQueryResult}
type="primary"
onClick={handleStageQuery}
onClick={handleRunQuery}
>
Stage & Run Query
</Button>

View File

@ -1,11 +1,13 @@
import { LockFilled } from '@ant-design/icons';
import { Button, Modal, Tooltip, Typography } from 'antd';
import { FeatureKeys } from 'constants/features';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import history from 'lib/history';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
@ -47,6 +49,7 @@ function NewWidget({
saveSettingOfPanel,
getQueryResults,
}: Props): JSX.Element {
const urlQuery = useUrlQuery();
const dispatch = useDispatch();
const { dashboards } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards,
@ -159,9 +162,10 @@ function NewWidget({
}, [dashboardId, dispatch]);
const getQueryResult = useCallback(() => {
if (selectedWidget?.id.length !== 0 && selectedWidget?.query) {
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
if ((selectedWidget?.id.length !== 0 && compositeQuery) || compositeQuery) {
getQueryResults({
query: selectedWidget?.query,
query: JSON.parse(compositeQuery),
selectedTime: selectedTime.enum,
widgetId: selectedWidget?.id || '',
graphType,
@ -170,12 +174,12 @@ function NewWidget({
});
}
}, [
selectedWidget?.query,
selectedTime.enum,
selectedWidget?.id,
getQueryResults,
globalSelectedInterval,
graphType,
urlQuery,
]);
const setGraphHandler = (type: ITEMS): void => {

View File

@ -0,0 +1,33 @@
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import useUrlQuery from 'hooks/useUrlQuery';
import { useEffect, useMemo } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { useQueryBuilder } from './useQueryBuilder';
type UseShareBuilderUrlParams = { defaultValue: Query };
type UseShareBuilderUrlReturnType = { compositeQuery: Query | null };
export const useShareBuilderUrl = ({
defaultValue,
}: UseShareBuilderUrlParams): UseShareBuilderUrlReturnType => {
const { redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery();
const compositeQuery: Query | null = useMemo(() => {
const query = urlQuery.get(COMPOSITE_QUERY);
if (query) {
return JSON.parse(query);
}
return null;
}, [urlQuery]);
useEffect(() => {
if (!compositeQuery) {
redirectWithQueryBuilderData(defaultValue);
}
}, [defaultValue, urlQuery, redirectWithQueryBuilderData, compositeQuery]);
return { compositeQuery };
};

View File

@ -1,27 +1,35 @@
import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
import { FORMULA_REGEXP } from 'constants/regExp';
import {
BuilderQueryDataResourse,
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
import { initialQuery } from 'constants/queryBuilder';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { transformQueryBuilderDataModel } from '../transformQueryBuilderDataModel';
export const mapQueryDataFromApi = (
data: BuilderQueryDataResourse,
): QueryBuilderData => {
const queryData: QueryBuilderData['queryData'] = [];
const queryFormulas: QueryBuilderData['queryFormulas'] = [];
compositeQuery: ICompositeMetricQuery,
): Query => {
const builder = compositeQuery.builderQueries
? transformQueryBuilderDataModel(compositeQuery.builderQueries)
: initialQuery.builder;
Object.entries(data).forEach(([, value]) => {
if (FORMULA_REGEXP.test(value.queryName)) {
const formula = value as IBuilderFormula;
queryFormulas.push(formula);
} else {
const query = value as IBuilderQuery;
queryData.push({ ...initialQueryBuilderFormValues, ...query });
}
});
const promql = compositeQuery.promQueries
? Object.keys(compositeQuery.promQueries).map((key) => ({
...compositeQuery.promQueries[key],
name: key,
}))
: initialQuery.promql;
return { queryData, queryFormulas };
const clickhouseSql = compositeQuery.chQueries
? Object.keys(compositeQuery.chQueries).map((key) => ({
...compositeQuery.chQueries[key],
name: key,
rawQuery: compositeQuery.chQueries[key].query,
}))
: initialQuery.clickhouse_sql;
return {
builder,
promql,
clickhouse_sql: clickhouseSql,
queryType: compositeQuery.queryType,
};
};

View File

@ -0,0 +1,30 @@
import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
} from 'constants/queryBuilder';
import { FORMULA_REGEXP } from 'constants/regExp';
import {
BuilderQueryDataResourse,
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { QueryBuilderData } from 'types/common/queryBuilder';
export const transformQueryBuilderDataModel = (
data: BuilderQueryDataResourse,
): QueryBuilderData => {
const queryData: QueryBuilderData['queryData'] = [];
const queryFormulas: QueryBuilderData['queryFormulas'] = [];
Object.entries(data).forEach(([, value]) => {
if (FORMULA_REGEXP.test(value.queryName)) {
const formula = value as IBuilderFormula;
queryFormulas.push({ ...initialFormulaBuilderFormValues, ...formula });
} else {
const query = value as IBuilderQuery;
queryData.push({ ...initialQueryBuilderFormValues, ...query });
}
});
return { queryData, queryFormulas };
};

View File

@ -0,0 +1,28 @@
export function replaceIncorrectObjectFields<
// eslint-disable-next-line @typescript-eslint/ban-types
TargetValue extends object,
// eslint-disable-next-line @typescript-eslint/ban-types
ResultValue extends object
>(
targetObject: TargetValue,
defaultObject: ResultValue,
): { isValid: boolean; validData: ResultValue } {
const targetObjectKeys = Object.keys(targetObject);
const defaultObjectKeys = Object.keys(defaultObject);
let isValid = true;
const result: ResultValue = { ...defaultObject };
defaultObjectKeys.forEach((key) => {
if (targetObjectKeys.includes(key)) {
result[key as keyof ResultValue] = (targetObject[
key as keyof TargetValue
] as unknown) as ResultValue[keyof ResultValue];
} else {
isValid = false;
}
});
return { isValid, validData: result };
}

View File

@ -1,30 +1,39 @@
import {
alphabet,
formulasNames,
initialClickHouseData,
initialFormulaBuilderFormValues,
initialQuery,
initialQueryBuilderFormValues,
initialQueryPromQLData,
initialQueryWithType,
initialSingleQueryMap,
MAX_FORMULAS,
MAX_QUERIES,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import useUrlQuery from 'hooks/useUrlQuery';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
import { replaceIncorrectObjectFields } from 'lib/replaceIncorrectObjectFields';
import {
createContext,
PropsWithChildren,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useHistory, useLocation } from 'react-router-dom';
// ** Types
import {
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
Query,
QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
@ -35,8 +44,7 @@ import {
} from 'types/common/queryBuilder';
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
currentQuery: initialQuery,
queryType: EQueryType.QUERY_BUILDER,
currentQuery: initialQueryWithType,
initialDataSource: null,
panelType: PANEL_TYPES.TIME_SERIES,
resetQueryBuilderData: () => {},
@ -53,11 +61,16 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
addNewBuilderQuery: () => {},
addNewFormula: () => {},
addNewQueryItem: () => {},
redirectWithQueryBuilderData: () => {},
});
export function QueryBuilderProvider({
children,
}: PropsWithChildren): JSX.Element {
const urlQuery = useUrlQuery();
const history = useHistory();
const location = useLocation();
const [initialDataSource, setInitialDataSource] = useState<DataSource | null>(
null,
);
@ -85,13 +98,41 @@ export function QueryBuilderProvider({
setCurrentQuery(initialQuery);
}, []);
const initQueryBuilderData = useCallback(
(query: QueryState, queryType: EQueryType): void => {
setCurrentQuery(query);
setQueryType(queryType);
},
[],
);
const initQueryBuilderData = useCallback((query: Partial<Query>): void => {
const { queryType, ...queryState } = query;
const builder: QueryBuilderData = {
queryData: queryState.builder
? queryState.builder.queryData.map((item) => ({
...initialQueryBuilderFormValues,
...item,
}))
: initialQuery.builder.queryData,
queryFormulas: queryState.builder
? queryState.builder.queryFormulas.map((item) => ({
...initialFormulaBuilderFormValues,
...item,
}))
: initialQuery.builder.queryFormulas,
};
const promql: IPromQLQuery[] = queryState.promql
? queryState.promql.map((item) => ({
...initialQueryPromQLData,
...item,
}))
: initialQuery.promql;
const clickHouse: IClickHouseQuery[] = queryState.clickhouse_sql
? queryState.clickhouse_sql.map((item) => ({
...initialClickHouseData,
...item,
}))
: initialQuery.clickhouse_sql;
setCurrentQuery({ builder, clickhouse_sql: clickHouse, promql });
setQueryType(queryType || EQueryType.QUERY_BUILDER);
}, []);
const removeQueryBuilderEntityByIndex = useCallback(
(type: keyof QueryBuilderData, index: number) => {
@ -316,10 +357,62 @@ export function QueryBuilderProvider({
setPanelType(newPanelType);
}, []);
const redirectWithQueryBuilderData = useCallback(
(query: Partial<Query>) => {
const currentGeneratedQuery: Query = {
queryType:
!query.queryType || !Object.values(EQueryType).includes(query.queryType)
? EQueryType.QUERY_BUILDER
: query.queryType,
builder:
!query.builder || query.builder.queryData.length === 0
? initialQuery.builder
: query.builder,
promql:
!query.promql || query.promql.length === 0
? initialQuery.promql
: query.promql,
clickhouse_sql:
!query.clickhouse_sql || query.clickhouse_sql.length === 0
? initialQuery.clickhouse_sql
: query.clickhouse_sql,
};
urlQuery.set(COMPOSITE_QUERY, JSON.stringify(currentGeneratedQuery));
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
history.push(generatedUrl);
},
[history, location, urlQuery],
);
useEffect(() => {
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
if (!compositeQuery) return;
const newQuery: Query = JSON.parse(compositeQuery);
const { isValid, validData } = replaceIncorrectObjectFields(
newQuery,
initialQueryWithType,
);
if (!isValid) {
redirectWithQueryBuilderData(validData);
} else {
initQueryBuilderData(newQuery);
}
}, [initQueryBuilderData, redirectWithQueryBuilderData, urlQuery]);
const query: Query = useMemo(() => ({ ...currentQuery, queryType }), [
currentQuery,
queryType,
]);
const contextValues: QueryBuilderContextType = useMemo(
() => ({
currentQuery,
queryType,
currentQuery: query,
initialDataSource,
panelType,
resetQueryBuilderData,
@ -336,12 +429,12 @@ export function QueryBuilderProvider({
addNewBuilderQuery,
addNewFormula,
addNewQueryItem,
redirectWithQueryBuilderData,
}),
[
currentQuery,
query,
initialDataSource,
panelType,
queryType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
@ -356,6 +449,7 @@ export function QueryBuilderProvider({
addNewBuilderQuery,
addNewFormula,
addNewQueryItem,
redirectWithQueryBuilderData,
],
);

View File

@ -1,15 +1,9 @@
import getDashboard from 'api/dashboard/get';
import {
initialClickHouseData,
initialQueryBuilderFormValues,
initialQueryPromQLData,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { initialQueryWithType, PANEL_TYPES } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import { Props } from 'types/api/dashboard/get';
import { EQueryType } from 'types/common/dashboard';
export const GetDashboard = ({
uuid,
@ -55,15 +49,7 @@ export const GetDashboard = ({
errorMessage: '',
loading: false,
},
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData],
builder: {
queryFormulas: [],
queryData: [initialQueryBuilderFormValues],
},
},
query: initialQueryWithType,
},
});
}

View File

@ -84,8 +84,6 @@ export interface Query {
export type QueryState = Omit<Query, 'queryType'>;
export type BuilderQueryResource = Record<string, IBuilderQuery>;
export type BuilderFormulaResource = Record<string, IBuilderFormula>;
export type BuilderClickHouseResource = Record<string, IClickHouseQuery>;
export type BuilderPromQLResource = Record<string, IPromQLQuery>;
export type BuilderQueryDataResourse = Record<

View File

@ -4,7 +4,7 @@ import {
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
QueryState,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from './dashboard';
@ -153,8 +153,7 @@ export type QueryBuilderData = {
};
export type QueryBuilderContextType = {
currentQuery: QueryState;
queryType: EQueryType;
currentQuery: Query;
initialDataSource: DataSource | null;
panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void;
@ -168,7 +167,7 @@ export type QueryBuilderContextType = {
) => void;
handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
handleSetQueryType: (newQueryType: EQueryType) => void;
initQueryBuilderData: (query: QueryState, queryType: EQueryType) => void;
initQueryBuilderData: (query: Partial<Query>) => void;
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
removeQueryBuilderEntityByIndex: (
type: keyof QueryBuilderData,
@ -181,6 +180,7 @@ export type QueryBuilderContextType = {
addNewBuilderQuery: () => void;
addNewFormula: () => void;
addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void;
redirectWithQueryBuilderData: (query: Query) => void;
};
export type QueryAdditionalFilter = {