Feat/extend query builder state (#2755)

* feat: extends query builder state

* fix: correct import path for query type
This commit is contained in:
Yevhen Shevchenko 2023-05-23 16:47:52 +03:00 committed by GitHub
parent 1ded475b37
commit 818a984af3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 578 additions and 741 deletions

View File

@ -1,11 +0,0 @@
export const PromQLQueryTemplate = {
query: '',
legend: '',
disabled: false,
};
export const ClickHouseQueryTemplate = {
rawQuery: '',
legend: '',
disabled: false,
};

View File

@ -6,7 +6,11 @@ import {
HavingForm,
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import {
BoolOperators,
DataSource,
@ -14,6 +18,7 @@ import {
NumberOperators,
PanelTypeKeys,
QueryAdditionalFilter,
QueryBuilderData,
ReduceOperators,
StringOperators,
} from 'types/common/queryBuilder';
@ -122,6 +127,37 @@ export const initialFormulaBuilderFormValues: IBuilderFormula = {
legend: '',
};
export const initialQueryPromQLData: IPromQLQuery = {
name: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
query: '',
legend: '',
disabled: false,
};
export const initialClickHouseData: IClickHouseQuery = {
name: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
rawQuery: '',
legend: '',
disabled: false,
query: '',
};
export const initialQueryBuilderData: QueryBuilderData = {
queryData: [initialQueryBuilderFormValues],
queryFormulas: [],
};
export const initialSingleQueryMap: Record<
EQueryType.PROM | EQueryType.CLICKHOUSE,
IClickHouseQuery | IPromQLQuery
> = { clickhouse_sql: initialClickHouseData, promql: initialQueryPromQLData };
export const initialQuery: QueryState = {
builder: initialQueryBuilderData,
clickhouse_sql: [initialClickHouseData],
promql: [initialQueryPromQLData],
};
export const operatorsByTypes: Record<LocalDataType, string[]> = {
string: Object.values(StringOperators),
number: Object.values(NumberOperators),

View File

@ -1,45 +1,17 @@
import ClickHouseQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/query';
import { IClickHouseQueryHandleChange } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/clickHouse/types';
import { IChQueries } from 'types/api/alerts/compositeQuery';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { rawQueryToIChQuery, toIClickHouseQuery } from './transform';
function ChQuerySection({
chQueries,
setChQueries,
}: ChQuerySectionProps): JSX.Element {
const handleChQueryChange = ({
rawQuery,
legend,
toggleDelete,
}: IClickHouseQueryHandleChange): void => {
const chQuery = rawQueryToIChQuery(
chQueries.A,
rawQuery,
legend,
toggleDelete,
);
setChQueries({
A: {
...chQuery,
},
});
};
function ChQuerySection(): JSX.Element {
const { currentQuery } = useQueryBuilder();
return (
<ClickHouseQueryBuilder
key="A"
queryIndex="A"
queryData={toIClickHouseQuery(chQueries?.A)}
handleQueryChange={handleChQueryChange}
queryIndex={0}
queryData={currentQuery.clickhouse_sql[0]}
deletable={false}
/>
);
}
interface ChQuerySectionProps {
chQueries: IChQueries;
setChQueries: (q: IChQueries) => void;
}
export default ChQuerySection;

View File

@ -1,39 +0,0 @@
import { IChQuery } from 'types/api/alerts/compositeQuery';
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
// @description rawQueryToIChQuery transforms raw query (from ClickHouseQueryBuilder)
// to alert specific IChQuery format
export const rawQueryToIChQuery = (
src: IChQuery,
rawQuery: string | undefined,
legend: string | undefined,
toggleDelete: boolean | undefined,
): IChQuery => {
if (toggleDelete) {
return {
rawQuery: '',
legend: '',
name: 'A',
disabled: false,
query: '',
};
}
return {
rawQuery: rawQuery !== undefined ? rawQuery : src.query,
query: rawQuery !== undefined ? rawQuery : src.query,
legend: legend !== undefined ? legend : src.legend,
name: 'A',
disabled: false,
};
};
// @description toIClickHouseQuery transforms IChQuery (alert specific) to
// ClickHouseQueryBuilder format. The main difference is
// use of rawQuery (in ClickHouseQueryBuilder)
// and query (in alert builder)
export const toIClickHouseQuery = (src: IChQuery): IClickHouseQuery => ({
...src,
name: 'A',
rawQuery: src.query,
});

View File

@ -11,7 +11,7 @@ import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { GetMetricQueryRange } from 'store/actions/dashboard/getQueryResults';
import { Query } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { ChartContainer, FailedMessageContainer } from './styles';

View File

@ -1,48 +1,17 @@
import PromQLQueryBuilder from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/query';
import { IPromQLQueryHandleChange } from 'container/NewWidget/LeftContainer/QuerySection/QueryBuilder/promQL/types';
import { IPromQueries } from 'types/api/alerts/compositeQuery';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
function PromqlSection({
promQueries,
setPromQueries,
}: PromqlSectionProps): JSX.Element {
const handlePromQLQueryChange = ({
query,
legend,
toggleDelete,
}: IPromQLQueryHandleChange): void => {
let promQuery = promQueries.A;
function PromqlSection(): JSX.Element {
const { currentQuery } = useQueryBuilder();
// todo(amol): how to remove query, make it null?
if (query) promQuery.query = query;
if (legend) promQuery.legend = legend;
if (toggleDelete) {
promQuery = {
query: '',
legend: '',
name: 'A',
disabled: false,
};
}
setPromQueries({
A: {
...promQuery,
},
});
};
return (
<PromQLQueryBuilder
key="A"
queryIndex="A"
queryData={{ ...promQueries?.A, name: 'A' }}
handleQueryChange={handlePromQLQueryChange}
queryIndex={0}
queryData={currentQuery.promql[0]}
deletable={false}
/>
);
}
interface PromqlSectionProps {
promQueries: IPromQueries;
setPromQueries: (p: IPromQueries) => void;
}
export default PromqlSection;

View File

@ -5,7 +5,6 @@ import { QueryBuilder } from 'container/QueryBuilder';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
import { EQueryType } from 'types/common/dashboard';
import ChQuerySection from './ChQuerySection';
@ -15,10 +14,6 @@ import { FormContainer, StepHeading } from './styles';
function QuerySection({
queryCategory,
setQueryCategory,
promQueries,
setPromQueries,
chQueries,
setChQueries,
alertType,
runQuery,
}: QuerySectionProps): JSX.Element {
@ -26,45 +21,12 @@ function QuerySection({
const { t } = useTranslation('alerts');
const handleQueryCategoryChange = (queryType: string): void => {
if (
queryType === EQueryType.PROM &&
(!promQueries || Object.keys(promQueries).length === 0)
) {
setPromQueries({
A: {
query: '',
stats: '',
name: 'A',
legend: '',
disabled: false,
},
});
}
if (
queryType === EQueryType.CLICKHOUSE &&
(!chQueries || Object.keys(chQueries).length === 0)
) {
setChQueries({
A: {
rawQuery: '',
name: 'A',
query: '',
legend: '',
disabled: false,
},
});
}
setQueryCategory(queryType as EQueryType);
};
const renderPromqlUI = (): JSX.Element => (
<PromqlSection promQueries={promQueries} setPromQueries={setPromQueries} />
);
const renderPromqlUI = (): JSX.Element => <PromqlSection />;
const renderChQueryUI = (): JSX.Element => (
<ChQuerySection chQueries={chQueries} setChQueries={setChQueries} />
);
const renderChQueryUI = (): JSX.Element => <ChQuerySection />;
const renderMetricUI = (): JSX.Element => (
<QueryBuilder
@ -169,10 +131,6 @@ function QuerySection({
interface QuerySectionProps {
queryCategory: EQueryType;
setQueryCategory: (n: EQueryType) => void;
promQueries: IPromQueries;
setPromQueries: (p: IPromQueries) => void;
chQueries: IChQueries;
setChQueries: (q: IChQueries) => void;
alertType: AlertTypes;
runQuery: () => void;
}

View File

@ -10,19 +10,17 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
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 { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { IChQueries, IPromQueries } from 'types/api/alerts/compositeQuery';
import {
AlertDef,
defaultEvalWindow,
defaultMatchType,
} from 'types/api/alerts/def';
import { Query as StagedQuery } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import BasicInfo from './BasicInfo';
@ -48,7 +46,12 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { queryBuilderData, initQueryBuilderData } = useQueryBuilder();
const {
currentQuery,
queryType,
handleSetQueryType,
initQueryBuilderData,
} = useQueryBuilder();
// use query client
const ruleCache = useQueryClient();
@ -61,83 +64,35 @@ function FormAlertRules({
// initQuery contains initial query when component was mounted
const initQuery = initialValue.condition.compositeQuery;
const [queryCategory, setQueryCategory] = useState<EQueryType>(
initQuery.queryType,
);
// local state to handle promql queries
const [promQueries, setPromQueries] = useState<IPromQueries>({
...initQuery?.promQueries,
});
// local state to handle promql queries
const [chQueries, setChQueries] = useState<IChQueries>({
...initQuery?.chQueries,
});
// staged query is used to display chart preview. the query gets
// auto refreshed when any of the params in query section change.
// though this is the source of chart data, the final query used
// by chart will be either debouncedStagedQuery or manualStagedQuery
// depending on the run option (auto-run or use of run query button)
const [stagedQuery, setStagedQuery] = useState<StagedQuery>();
// manualStagedQuery requires manual staging of query
// when user clicks run query button. Useful for clickhouse tab where
// run query button is provided.
const [manualStagedQuery, setManualStagedQuery] = useState<StagedQuery>();
const [manualStagedQuery, setManualStagedQuery] = useState<Query>();
// this use effect initiates staged query and
// other queries based on server data.
// useful when fetching of initial values (from api)
// is delayed
useEffect(() => {
const initQuery = initialValue?.condition?.compositeQuery;
const type = initQuery.queryType;
const builderData = mapQueryDataFromApi(
initialValue?.condition?.compositeQuery?.builderQueries || {},
);
// prepare staged query
const sq = prepareStagedQuery(
type,
builderData.queryData,
builderData.queryFormulas,
initQuery?.builderQueries,
initQuery?.promQueries,
initQuery?.chQueries,
);
const pq = initQuery?.promQueries;
const chq = initQuery?.chQueries;
setQueryCategory(type);
initQueryBuilderData(builderData);
setPromQueries(pq);
setStagedQuery(sq);
initQueryBuilderData(sq, type);
// also set manually staged query
setManualStagedQuery(sq);
setChQueries(chq);
setAlertDef(initialValue);
}, [initialValue, initQueryBuilderData]);
// this useEffect updates staging query when
// any of its sub-parameters changes
useEffect(() => {
// prepare staged query
const sq: StagedQuery = prepareStagedQuery(
queryCategory,
queryBuilderData.queryData,
queryBuilderData.queryFormulas,
promQueries,
chQueries,
);
setStagedQuery(sq);
}, [queryCategory, chQueries, queryBuilderData, promQueries]);
}, [initialValue, initQueryBuilderData, initQuery]);
const onRunQuery = (): void => {
setManualStagedQuery(stagedQuery);
setManualStagedQuery({ ...currentQuery, queryType });
};
const onCancelHandler = useCallback(() => {
@ -147,8 +102,7 @@ function FormAlertRules({
// onQueryCategoryChange handles changes to query category
// in state as well as sets additional defaults
const onQueryCategoryChange = (val: EQueryType): void => {
console.log('onQueryCategoryChange', val);
setQueryCategory(val);
handleSetQueryType(val);
if (val === EQueryType.PROM) {
setAlertDef({
...alertDef,
@ -160,22 +114,15 @@ function FormAlertRules({
});
}
const sq: StagedQuery = prepareStagedQuery(
val,
queryBuilderData.queryData,
queryBuilderData.queryFormulas,
promQueries,
chQueries,
);
setManualStagedQuery(sq);
setManualStagedQuery({ ...currentQuery, queryType: val });
};
const { notifications } = useNotifications();
const validatePromParams = useCallback((): boolean => {
let retval = true;
if (queryCategory !== EQueryType.PROM) return retval;
if (queryType !== EQueryType.PROM) return retval;
if (!promQueries || Object.keys(promQueries).length === 0) {
if (!currentQuery.promql || currentQuery.promql.length === 0) {
notifications.error({
message: 'Error',
description: t('promql_required'),
@ -183,8 +130,8 @@ function FormAlertRules({
return false;
}
Object.keys(promQueries).forEach((key) => {
if (promQueries[key].query === '') {
currentQuery.promql.forEach((item) => {
if (item.query === '') {
notifications.error({
message: 'Error',
description: t('promql_required'),
@ -194,13 +141,16 @@ function FormAlertRules({
});
return retval;
}, [t, promQueries, queryCategory, notifications]);
}, [t, currentQuery, queryType, notifications]);
const validateChQueryParams = useCallback((): boolean => {
let retval = true;
if (queryCategory !== EQueryType.CLICKHOUSE) return retval;
if (queryType !== EQueryType.CLICKHOUSE) return retval;
if (!chQueries || Object.keys(chQueries).length === 0) {
if (
!currentQuery.clickhouse_sql ||
currentQuery.clickhouse_sql.length === 0
) {
notifications.error({
message: 'Error',
description: t('chquery_required'),
@ -208,8 +158,8 @@ function FormAlertRules({
return false;
}
Object.keys(chQueries).forEach((key) => {
if (chQueries[key].rawQuery === '') {
currentQuery.clickhouse_sql.forEach((item) => {
if (item.rawQuery === '') {
notifications.error({
message: 'Error',
description: t('chquery_required'),
@ -219,12 +169,15 @@ function FormAlertRules({
});
return retval;
}, [t, chQueries, queryCategory, notifications]);
}, [t, queryType, currentQuery, notifications]);
const validateQBParams = useCallback((): boolean => {
if (queryCategory !== EQueryType.QUERY_BUILDER) return true;
if (queryType !== EQueryType.QUERY_BUILDER) return true;
if (!queryBuilderData.queryData || queryBuilderData.queryData.length === 0) {
if (
!currentQuery.builder.queryData ||
currentQuery.builder.queryData.length === 0
) {
notifications.error({
message: 'Error',
description: t('condition_required'),
@ -241,7 +194,7 @@ function FormAlertRules({
}
return true;
}, [t, alertDef, queryCategory, queryBuilderData, notifications]);
}, [t, alertDef, queryType, currentQuery, notifications]);
const isFormValid = useCallback((): boolean => {
if (!alertDef.alert || alertDef.alert === '') {
@ -275,15 +228,18 @@ function FormAlertRules({
...alertDef,
alertType,
source: window?.location.toString(),
ruleType:
queryCategory === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
ruleType: queryType === EQueryType.PROM ? 'promql_rule' : 'threshold_rule',
condition: {
...alertDef.condition,
compositeQuery: {
builderQueries: mapQueryDataToApi(queryBuilderData).data,
promQueries,
chQueries,
queryType: queryCategory,
builderQueries: {
...mapQueryDataToApi(currentQuery.builder.queryData, 'queryName').data,
...mapQueryDataToApi(currentQuery.builder.queryFormulas, 'queryName')
.data,
},
promQueries: mapQueryDataToApi(currentQuery.promql, 'name').data,
chQueries: mapQueryDataToApi(currentQuery.clickhouse_sql, 'name').data,
queryType,
panelType: initQuery.panelType,
},
},
@ -292,11 +248,9 @@ function FormAlertRules({
};
const memoizedPreparePostData = useCallback(preparePostData, [
queryCategory,
queryType,
currentQuery,
alertDef,
queryBuilderData,
promQueries,
chQueries,
alertType,
initQuery,
]);
@ -359,7 +313,7 @@ function FormAlertRules({
const content = (
<Typography.Text>
{' '}
{t('confirm_save_content_part1')} <QueryTypeTag queryType={queryCategory} />{' '}
{t('confirm_save_content_part1')} <QueryTypeTag queryType={queryType} />{' '}
{t('confirm_save_content_part2')}
</Typography.Text>
);
@ -372,7 +326,7 @@ function FormAlertRules({
saveRule();
},
});
}, [t, saveRule, queryCategory]);
}, [t, saveRule, queryType]);
const onTestRuleHandler = useCallback(async () => {
if (!isFormValid()) {
@ -418,7 +372,7 @@ function FormAlertRules({
const renderQBChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryCategory} />}
headline={<PlotTag queryType={queryType} />}
name=""
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -428,7 +382,7 @@ function FormAlertRules({
const renderPromChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryCategory} />}
headline={<PlotTag queryType={queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -437,7 +391,7 @@ function FormAlertRules({
const renderChQueryChartPreview = (): JSX.Element => (
<ChartPreview
headline={<PlotTag queryType={queryCategory} />}
headline={<PlotTag queryType={queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
@ -448,7 +402,7 @@ function FormAlertRules({
const isNewRule = ruleId === 0;
const isAlertAvialableToSave =
isAlertAvialable && isNewRule && queryCategory === EQueryType.QUERY_BUILDER;
isAlertAvialable && isNewRule && queryType === EQueryType.QUERY_BUILDER;
return (
<>
@ -460,22 +414,18 @@ function FormAlertRules({
layout="vertical"
form={formInstance}
>
{queryCategory === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
{queryCategory === EQueryType.PROM && renderPromChartPreview()}
{queryCategory === EQueryType.CLICKHOUSE && renderChQueryChartPreview()}
{queryType === EQueryType.QUERY_BUILDER && renderQBChartPreview()}
{queryType === EQueryType.PROM && renderPromChartPreview()}
{queryType === EQueryType.CLICKHOUSE && renderChQueryChartPreview()}
<QuerySection
queryCategory={queryCategory}
queryCategory={queryType}
setQueryCategory={onQueryCategoryChange}
promQueries={promQueries}
setPromQueries={setPromQueries}
chQueries={chQueries}
setChQueries={setChQueries}
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
runQuery={onRunQuery}
/>
<RuleOptions
queryCategory={queryCategory}
queryCategory={queryType}
alertDef={alertDef}
setAlertDef={setAlertDef}
/>
@ -514,7 +464,7 @@ function FormAlertRules({
</MainFormContainer>
</StyledLeftContainer>
<Col flex="1 1 300px">
<UserGuide queryType={queryCategory} />
<UserGuide queryType={queryType} />
</Col>
</PanelContainer>
</>

View File

@ -1,34 +1,30 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import {
IChQueries,
IChQuery,
IPromQueries,
IPromQuery,
} from 'types/api/alerts/compositeQuery';
import { Query as IStagedQuery } from 'types/api/dashboard/getAll';
import {
IBuilderFormula,
IBuilderQuery,
BuilderClickHouseResource,
BuilderPromQLResource,
BuilderQueryDataResourse,
IClickHouseQuery,
IPromQLQuery,
Query,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
export const prepareStagedQuery = (
t: EQueryType,
m: IBuilderQuery[],
f: IBuilderFormula[],
p: IPromQueries,
c: IChQueries,
): IStagedQuery => {
const promList: IPromQuery[] = [];
const chQueryList: IChQuery[] = [];
b: BuilderQueryDataResourse,
p: BuilderPromQLResource,
c: BuilderClickHouseResource,
): Query => {
const promList: IPromQLQuery[] = [];
const chQueryList: IClickHouseQuery[] = [];
// convert map[string]IPromQuery to IPromQuery[]
const builder = mapQueryDataFromApi(b);
if (p) {
Object.keys(p).forEach((key) => {
promList.push({ ...p[key], name: key });
});
}
// convert map[string]IChQuery to IChQuery[]
if (c) {
Object.keys(c).forEach((key) => {
chQueryList.push({ ...c[key], name: key, rawQuery: c[key].query });
@ -38,10 +34,7 @@ export const prepareStagedQuery = (
return {
queryType: t,
promql: promList,
builder: {
queryFormulas: f,
queryData: m,
},
builder,
clickhouse_sql: chQueryList,
};
};

View File

@ -1,12 +1,11 @@
import { NotificationInstance } from 'antd/es/notification/interface';
import updateDashboardApi from 'api/dashboard/update';
import {
ClickHouseQueryTemplate,
PromQLQueryTemplate,
} from 'constants/dashboard';
import { initialQueryBuilderFormValues } from 'constants/queryBuilder';
initialClickHouseData,
initialQueryBuilderFormValues,
initialQueryPromQLData,
} from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import GetQueryName from 'lib/query/GetQueryName';
import { Layout } from 'react-grid-layout';
import store from 'store';
import { Dashboard } from 'types/api/dashboard/getAll';
@ -42,18 +41,8 @@ export const UpdateDashboard = async (
panelTypes: graphType,
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [
{
name: GetQueryName([]) || '',
...PromQLQueryTemplate,
},
],
clickhouse_sql: [
{
name: GetQueryName([]) || '',
...ClickHouseQueryTemplate,
},
],
promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData],
builder: {
queryFormulas: [],
queryData: [initialQueryBuilderFormValues],

View File

@ -14,6 +14,7 @@ interface IQueryHeaderProps {
disabled: boolean;
onDisable: VoidFunction;
name: string;
deletable: boolean;
onDelete: VoidFunction;
children: ReactNode;
}
@ -23,6 +24,7 @@ function QueryHeader({
onDisable,
name,
onDelete,
deletable,
children,
}: IQueryHeaderProps): JSX.Element {
const [collapse, setCollapse] = useState(false);
@ -44,7 +46,9 @@ function QueryHeader({
/>
</Row>
{deletable && (
<Button type="ghost" danger icon={<DeleteOutlined />} onClick={onDelete} />
)}
</Row>
{!collapse && children}
</QueryWrapper>

View File

@ -1,79 +1,24 @@
import { PlusOutlined } from '@ant-design/icons';
import { ClickHouseQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
import { Query } from 'types/api/dashboard/getAll';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { EQueryType } from 'types/common/dashboard';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import ClickHouseQueryBuilder from './query';
import { IClickHouseQueryHandleChange } from './types';
interface IClickHouseQueryContainerProps {
queryData: Query;
updateQueryData: (args: IHandleUpdatedQuery) => void;
clickHouseQueries: Query['clickhouse_sql'];
}
function ClickHouseQueryContainer({
queryData,
updateQueryData,
clickHouseQueries,
}: IClickHouseQueryContainerProps): JSX.Element | null {
const handleClickHouseQueryChange = ({
queryIndex,
rawQuery,
legend,
toggleDisable,
toggleDelete,
}: IClickHouseQueryHandleChange): void => {
// we must check if queryIndex is number type. because -
// ClickHouseQueryBuilder.handleQueryChange has a queryIndex
// parameter which supports both number and string formats.
// it is because, the dashboard side of query builder has queryIndex as number
// while the alert builder uses string format for query index (similar to backend)
// hence, this method is only applies when queryIndex is in number format.
if (typeof queryIndex === 'number') {
const allQueries = queryData[EQueryType.CLICKHOUSE];
const currentIndexQuery = allQueries[queryIndex];
if (rawQuery !== undefined) {
currentIndexQuery.rawQuery = rawQuery;
}
if (legend !== undefined) {
currentIndexQuery.legend = legend;
}
if (toggleDisable) {
currentIndexQuery.disabled = !currentIndexQuery.disabled;
}
if (toggleDelete) {
allQueries.splice(queryIndex, 1);
}
updateQueryData({ updatedQuery: { ...queryData } });
}
};
function ClickHouseQueryContainer(): JSX.Element | null {
const { currentQuery, addNewQueryItem } = useQueryBuilder();
const addQueryHandler = (): void => {
queryData[EQueryType.CLICKHOUSE].push({
name: GetQueryName(queryData[EQueryType.CLICKHOUSE]) || '',
...ClickHouseQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
addNewQueryItem(EQueryType.CLICKHOUSE);
};
if (!clickHouseQueries) {
return null;
}
return (
<>
{clickHouseQueries.map((q, idx) => (
{currentQuery.clickhouse_sql.map((q, idx) => (
<ClickHouseQueryBuilder
key={q.name}
queryIndex={idx}
deletable={currentQuery.clickhouse_sql.length > 1}
queryData={q}
handleQueryChange={handleClickHouseQueryChange}
/>
))}
<QueryButton onClick={addQueryHandler} icon={<PlusOutlined />}>

View File

@ -1,42 +1,80 @@
import { Input } from 'antd';
import MonacoEditor from 'components/Editor';
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ChangeEvent, useCallback } from 'react';
import { IClickHouseQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import QueryHeader from '../QueryHeader';
import { IClickHouseQueryHandleChange } from './types';
interface IClickHouseQueryBuilderProps {
queryData: IClickHouseQuery;
queryIndex: number | string;
handleQueryChange: (args: IClickHouseQueryHandleChange) => void;
queryIndex: number;
deletable: boolean;
}
function ClickHouseQueryBuilder({
queryData,
queryIndex,
handleQueryChange,
deletable,
}: IClickHouseQueryBuilderProps): JSX.Element | null {
if (queryData === undefined) {
return null;
}
const {
handleSetQueryItemData,
removeQueryTypeItemByIndex,
} = useQueryBuilder();
const handleRemoveQuery = useCallback(() => {
removeQueryTypeItemByIndex(EQueryType.CLICKHOUSE, queryIndex);
}, [queryIndex, removeQueryTypeItemByIndex]);
const handleUpdateQuery = useCallback(
<Field extends keyof IClickHouseQuery, Value extends IClickHouseQuery[Field]>(
field: keyof IClickHouseQuery,
value: Value,
) => {
const newQuery: IClickHouseQuery = { ...queryData, [field]: value };
handleSetQueryItemData(queryIndex, EQueryType.CLICKHOUSE, newQuery);
},
[handleSetQueryItemData, queryIndex, queryData],
);
const handleDisable = useCallback(() => {
const newQuery: IClickHouseQuery = {
...queryData,
disabled: !queryData.disabled,
};
handleSetQueryItemData(queryIndex, EQueryType.CLICKHOUSE, newQuery);
}, [handleSetQueryItemData, queryData, queryIndex]);
const handleUpdateEditor = useCallback(
(value: string) => {
handleUpdateQuery('rawQuery', value);
},
[handleUpdateQuery],
);
const handleUpdateInput = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
handleUpdateQuery(name as keyof IClickHouseQuery, value);
},
[handleUpdateQuery],
);
return (
<QueryHeader
name={queryData.name}
disabled={queryData.disabled}
onDisable={(): void =>
handleQueryChange({ queryIndex, toggleDisable: true })
}
onDelete={(): void => {
handleQueryChange({ queryIndex, toggleDelete: true });
}}
onDisable={handleDisable}
onDelete={handleRemoveQuery}
deletable={deletable}
>
<MonacoEditor
language="sql"
height="200px"
onChange={(value): void =>
handleQueryChange({ queryIndex, rawQuery: value })
}
onChange={handleUpdateEditor}
value={queryData.rawQuery}
options={{
scrollbar: {
@ -48,9 +86,8 @@ function ClickHouseQueryBuilder({
}}
/>
<Input
onChange={(event): void =>
handleQueryChange({ queryIndex, legend: event.target.value })
}
onChange={handleUpdateInput}
name="legend"
size="middle"
defaultValue={queryData.legend}
value={queryData.legend}

View File

@ -1,4 +1,4 @@
import { IClickHouseQuery } from 'types/api/dashboard/getAll';
import { IClickHouseQuery } from 'types/api/queryBuilder/queryBuilderData';
export interface IClickHouseQueryHandleChange {
queryIndex: number | string;

View File

@ -1,65 +1,27 @@
import { PlusOutlined } from '@ant-design/icons';
import { PromQLQueryTemplate } from 'constants/dashboard';
import GetQueryName from 'lib/query/GetQueryName';
import { IPromQLQuery, Query } from 'types/api/dashboard/getAll';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { IPromQLQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { QueryButton } from '../../styles';
import { IHandleUpdatedQuery } from '../../types';
import PromQLQueryBuilder from './query';
import { IPromQLQueryHandleChange } from './types';
interface IPromQLQueryContainerProps {
queryData: Query;
updateQueryData: (args: IHandleUpdatedQuery) => void;
promQLQueries: IPromQLQuery[];
}
function PromQLQueryContainer(): JSX.Element | null {
const { addNewQueryItem, currentQuery } = useQueryBuilder();
function PromQLQueryContainer({
queryData,
updateQueryData,
promQLQueries,
}: IPromQLQueryContainerProps): JSX.Element | null {
const handlePromQLQueryChange = ({
queryIndex,
query,
legend,
toggleDisable,
toggleDelete,
}: IPromQLQueryHandleChange): void => {
const allQueries = queryData[EQueryType.PROM];
const currentIndexQuery = allQueries[queryIndex as number];
if (query !== undefined) currentIndexQuery.query = query;
if (legend !== undefined) currentIndexQuery.legend = legend;
if (toggleDisable) {
currentIndexQuery.disabled = !currentIndexQuery.disabled;
}
if (toggleDelete) {
allQueries.splice(queryIndex as number, 1);
}
updateQueryData({ updatedQuery: { ...queryData } });
};
const addQueryHandler = (): void => {
queryData[EQueryType.PROM].push({
name: GetQueryName(queryData[EQueryType.PROM]) || '',
...PromQLQueryTemplate,
});
updateQueryData({ updatedQuery: { ...queryData } });
addNewQueryItem(EQueryType.PROM);
};
if (!promQLQueries) {
return null;
}
return (
<>
{promQLQueries.map(
{currentQuery.promql.map(
(q: IPromQLQuery, idx: number): JSX.Element => (
<PromQLQueryBuilder
key={q.name}
deletable={currentQuery.promql.length > 1}
queryIndex={idx}
queryData={q}
handleQueryChange={handlePromQLQueryChange}
/>
),
)}

View File

@ -1,46 +1,71 @@
import { Input } from 'antd';
import { IPromQLQuery } from 'types/api/dashboard/getAll';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { ChangeEvent, useCallback } from 'react';
import { IPromQLQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import QueryHeader from '../QueryHeader';
import { IPromQLQueryHandleChange } from './types';
interface IPromQLQueryBuilderProps {
queryData: IPromQLQuery;
queryIndex: number | string;
handleQueryChange: (args: IPromQLQueryHandleChange) => void;
queryIndex: number;
deletable: boolean;
}
function PromQLQueryBuilder({
queryData,
queryIndex,
handleQueryChange,
deletable,
}: IPromQLQueryBuilderProps): JSX.Element {
const {
handleSetQueryItemData,
removeQueryTypeItemByIndex,
} = useQueryBuilder();
const handleRemoveQuery = useCallback(() => {
removeQueryTypeItemByIndex(EQueryType.PROM, queryIndex);
}, [queryIndex, removeQueryTypeItemByIndex]);
const handleUpdateQuery = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
const newQuery: IPromQLQuery = { ...queryData, [name]: value };
handleSetQueryItemData(queryIndex, EQueryType.PROM, newQuery);
},
[handleSetQueryItemData, queryIndex, queryData],
);
const handleDisable = useCallback(() => {
const newQuery: IPromQLQuery = {
...queryData,
disabled: !queryData.disabled,
};
handleSetQueryItemData(queryIndex, EQueryType.PROM, newQuery);
}, [handleSetQueryItemData, queryData, queryIndex]);
return (
<QueryHeader
name={queryData.name}
disabled={queryData.disabled}
onDisable={(): void =>
handleQueryChange({ queryIndex, toggleDisable: true })
}
onDelete={(): void => {
handleQueryChange({ queryIndex, toggleDelete: true });
}}
onDisable={handleDisable}
onDelete={handleRemoveQuery}
deletable={deletable}
>
<Input
onChange={(event): void =>
handleQueryChange({ queryIndex, query: event.target.value })
}
onChange={handleUpdateQuery}
size="middle"
name="query"
defaultValue={queryData.query}
addonBefore="PromQL Query"
style={{ marginBottom: '0.5rem' }}
/>
<Input
onChange={(event): void =>
handleQueryChange({ queryIndex, legend: event.target.value })
}
onChange={handleUpdateQuery}
size="middle"
name="legend"
defaultValue={queryData.legend}
addonBefore="Legend Format"
style={{ marginBottom: '0.5rem' }}

View File

@ -1,9 +1,6 @@
import { IPromQLQuery } from 'types/api/dashboard/getAll';
import { IPromQLQuery } from 'types/api/queryBuilder/queryBuilderData';
export interface IPromQLQueryHandleChange {
queryIndex: number | string;
query?: IPromQLQuery['query'];
legend?: IPromQLQuery['legend'];
toggleDisable?: IPromQLQuery['disabled'];
toggleDelete?: boolean;
query: IPromQLQuery;
}

View File

@ -3,8 +3,7 @@ import TextToolTip from 'components/TextToolTip';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { QueryBuilder } from 'container/QueryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { cloneDeep } from 'lodash-es';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { connect, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { bindActionCreators, Dispatch } from 'redux';
@ -15,25 +14,20 @@ import {
} from 'store/actions/dashboard/updateQuery';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import { Query, Widgets } from 'types/api/dashboard/getAll';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import DashboardReducer from 'types/reducer/dashboards';
import { v4 as uuid } from 'uuid';
import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
import { IHandleUpdatedQuery } from './types';
function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
const { queryBuilderData, initQueryBuilderData } = useQueryBuilder();
const [localQueryChanges, setLocalQueryChanges] = useState<Query>({} as Query);
const [rctTabKey, setRctTabKey] = useState<
Record<keyof typeof EQueryType, string>
>({
QUERY_BUILDER: uuid(),
CLICKHOUSE: uuid(),
PROM: uuid(),
});
const {
currentQuery,
queryType,
handleSetQueryType,
initQueryBuilderData,
} = useQueryBuilder();
const { dashboards, isLoadingQueryResult } = useSelector<
AppState,
@ -52,33 +46,18 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
}, [widgets, urlQuery]);
const selectedWidget = getWidget() as Widgets;
const [queryCategory, setQueryCategory] = useState<EQueryType>(
selectedWidget.query.queryType,
);
const { query } = selectedWidget || {};
useEffect(() => {
initQueryBuilderData(query.builder);
setLocalQueryChanges(cloneDeep(query) as Query);
}, [query, initQueryBuilderData]);
const regenRctKeys = (): void => {
setRctTabKey((prevState) => {
const newState = prevState;
Object.keys(newState).forEach((key) => {
newState[key as keyof typeof EQueryType] = uuid();
});
return cloneDeep(newState);
});
};
initQueryBuilderData(query, selectedWidget.query.queryType);
}, [query, initQueryBuilderData, selectedWidget]);
const handleStageQuery = (): void => {
updateQuery({
updatedQuery: {
...localQueryChanges,
builder: queryBuilderData,
...currentQuery,
queryType,
},
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
@ -86,26 +65,16 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
};
const handleQueryCategoryChange = (qCategory: string): void => {
setQueryCategory(qCategory as EQueryType);
const newLocalQuery = {
...cloneDeep(query),
queryType: qCategory as EQueryType,
};
setLocalQueryChanges(newLocalQuery);
regenRctKeys();
const currentQueryType = qCategory as EQueryType;
handleSetQueryType(currentQueryType);
updateQuery({
updatedQuery: newLocalQuery,
updatedQuery: { ...currentQuery, queryType: currentQueryType },
widgetId: urlQuery.get('widgetId') || '',
yAxisUnit: selectedWidget.yAxisUnit,
});
};
const handleLocalQueryUpdate = ({
updatedQuery,
}: IHandleUpdatedQuery): void => {
setLocalQueryChanges(cloneDeep(updatedQuery));
};
const items = [
{
key: EQueryType.QUERY_BUILDER,
@ -117,31 +86,13 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
key: EQueryType.CLICKHOUSE,
label: 'ClickHouse Query',
tab: <Typography>ClickHouse Query</Typography>,
children: (
<ClickHouseQueryContainer
key={rctTabKey.CLICKHOUSE}
queryData={localQueryChanges}
updateQueryData={({ updatedQuery }: IHandleUpdatedQuery): void => {
handleLocalQueryUpdate({ updatedQuery });
}}
clickHouseQueries={localQueryChanges[EQueryType.CLICKHOUSE]}
/>
),
children: <ClickHouseQueryContainer />,
},
{
key: EQueryType.PROM,
label: 'PromQL',
tab: <Typography>PromQL</Typography>,
children: (
<PromQLQueryContainer
key={rctTabKey.PROM}
queryData={localQueryChanges}
updateQueryData={({ updatedQuery }: IHandleUpdatedQuery): void => {
handleLocalQueryUpdate({ updatedQuery });
}}
promQLQueries={localQueryChanges[EQueryType.PROM]}
/>
),
children: <PromQLQueryContainer />,
},
];
@ -149,8 +100,8 @@ function QuerySection({ updateQuery, selectedGraph }: QueryProps): JSX.Element {
<Tabs
type="card"
style={{ width: '100%' }}
defaultActiveKey={queryCategory}
activeKey={queryCategory}
defaultActiveKey={queryType}
activeKey={queryType}
onChange={handleQueryCategoryChange}
tabBarExtraContent={
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>

View File

@ -1,5 +0,0 @@
import { Query } from 'types/api/dashboard/getAll';
export interface IHandleUpdatedQuery {
updatedQuery: Query;
}

View File

@ -17,10 +17,10 @@ export const QueryBuilder = memo(function QueryBuilder({
panelType,
}: QueryBuilderProps): JSX.Element {
const {
queryBuilderData,
currentQuery,
setupInitialDataSource,
resetQueryBuilderInfo,
addNewQuery,
addNewBuilderQuery,
addNewFormula,
handleSetPanelType,
} = useQueryBuilder();
@ -43,27 +43,27 @@ export const QueryBuilder = memo(function QueryBuilder({
);
const isDisabledQueryButton = useMemo(
() => queryBuilderData.queryData.length >= MAX_QUERIES,
[queryBuilderData],
() => currentQuery.builder.queryData.length >= MAX_QUERIES,
[currentQuery],
);
const isDisabledFormulaButton = useMemo(
() => queryBuilderData.queryFormulas.length >= MAX_FORMULAS,
[queryBuilderData],
() => currentQuery.builder.queryFormulas.length >= MAX_FORMULAS,
[currentQuery],
);
const isAvailableToDisableQuery = useMemo(
() =>
queryBuilderData.queryData.length > 1 ||
queryBuilderData.queryFormulas.length > 0,
[queryBuilderData],
currentQuery.builder.queryData.length > 1 ||
currentQuery.builder.queryFormulas.length > 0,
[currentQuery],
);
return (
<Row gutter={[0, 20]} justify="start">
<Col span={24}>
<Row gutter={[0, 50]}>
{queryBuilderData.queryData.map((query, index) => (
{currentQuery.builder.queryData.map((query, index) => (
<Col key={query.queryName} span={24}>
<Query
index={index}
@ -73,7 +73,7 @@ export const QueryBuilder = memo(function QueryBuilder({
/>
</Col>
))}
{queryBuilderData.queryFormulas.map((formula, index) => (
{currentQuery.builder.queryFormulas.map((formula, index) => (
<Col key={formula.queryName} span={24}>
<Formula formula={formula} index={index} />
</Col>
@ -87,7 +87,7 @@ export const QueryBuilder = memo(function QueryBuilder({
disabled={isDisabledQueryButton}
type="primary"
icon={<PlusOutlined />}
onClick={addNewQuery}
onClick={addNewBuilderQuery}
>
Query
</Button>

View File

@ -12,11 +12,14 @@ import { FormulaProps } from './Formula.interfaces';
const { TextArea } = Input;
export function Formula({ index, formula }: FormulaProps): JSX.Element {
const { removeEntityByIndex, handleSetFormulaData } = useQueryBuilder();
const {
removeQueryBuilderEntityByIndex,
handleSetFormulaData,
} = useQueryBuilder();
const handleDelete = useCallback(() => {
removeEntityByIndex('queryFormulas', index);
}, [index, removeEntityByIndex]);
removeQueryBuilderEntityByIndex('queryFormulas', index);
}, [index, removeQueryBuilderEntityByIndex]);
const handleToggleDisableFormula = useCallback((): void => {
const newFormula: IBuilderFormula = {

View File

@ -19,7 +19,7 @@ import { SelectOption } from 'types/common/select';
export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
const {
handleSetQueryData,
removeEntityByIndex,
removeQueryBuilderEntityByIndex,
panelType,
} = useQueryBuilder();
const [operators, setOperators] = useState<SelectOption<string, string>[]>([]);
@ -101,8 +101,8 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
);
const handleDeleteQuery = useCallback(() => {
removeEntityByIndex('queryData', index);
}, [removeEntityByIndex, index]);
removeQueryBuilderEntityByIndex('queryData', index);
}, [removeQueryBuilderEntityByIndex, index]);
const handleChangeQueryData: HandleChangeQueryData = useCallback(
(key, value) => {

View File

@ -1,14 +1,14 @@
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 { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export const mapQueryDataFromApi = (
data: QueryDataResourse,
data: BuilderQueryDataResourse,
): QueryBuilderData => {
const queryData: QueryBuilderData['queryData'] = [];
const queryFormulas: QueryBuilderData['queryFormulas'] = [];

View File

@ -1,49 +1,29 @@
import { QueryBuilderData } from 'types/common/queryBuilder';
import {
MapFormula,
MapQuery,
MapData,
MapQueryDataToApiResult,
} from 'types/common/queryBuilderMappers.types';
} from 'types/api/queryBuilder/queryBuilderData';
export const mapQueryDataToApi = (
data: QueryBuilderData,
): MapQueryDataToApiResult => {
export const mapQueryDataToApi = <Data extends MapData, Key extends keyof Data>(
data: Data[],
nameField: Key,
): MapQueryDataToApiResult<Record<string, Data>> => {
const newLegendMap: Record<string, string> = {};
const preparedQueryData: MapQuery = data.queryData.reduce<MapQuery>(
(acc, query) => {
const newResult: MapQuery = {
const preparedResult = data.reduce<Record<string, Data>>((acc, query) => {
const newResult: Record<string, Data> = {
...acc,
[query.queryName]: {
[query[nameField] as string]: {
...query,
},
};
newLegendMap[query.queryName] = query.legend;
newLegendMap[query[nameField] as string] = query.legend;
return newResult;
},
{},
);
const preparedFormulaData: MapFormula = data.queryFormulas.reduce<MapFormula>(
(acc, formula) => {
const newResult: MapFormula = {
...acc,
[formula.queryName]: {
...formula,
},
};
newLegendMap[formula.queryName] = formula.legend;
return newResult;
},
{},
);
}, {} as Record<string, Data>);
return {
data: { ...preparedQueryData, ...preparedFormulaData },
data: preparedResult,
newLegendMap,
};
};

View File

@ -2,7 +2,9 @@ import {
alphabet,
formulasNames,
initialFormulaBuilderFormValues,
initialQuery,
initialQueryBuilderFormValues,
initialSingleQueryMap,
MAX_FORMULAS,
MAX_QUERIES,
PANEL_TYPES,
@ -21,7 +23,11 @@ import {
import {
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import {
DataSource,
QueryBuilderContextType,
@ -29,26 +35,26 @@ import {
} from 'types/common/queryBuilder';
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
queryBuilderData: { queryData: [], queryFormulas: [] },
currentQuery: initialQuery,
queryType: EQueryType.QUERY_BUILDER,
initialDataSource: null,
panelType: PANEL_TYPES.TIME_SERIES,
resetQueryBuilderData: () => {},
resetQueryBuilderInfo: () => {},
handleSetQueryData: () => {},
handleSetFormulaData: () => {},
handleSetQueryItemData: () => {},
handleSetPanelType: () => {},
handleSetQueryType: () => {},
initQueryBuilderData: () => {},
setupInitialDataSource: () => {},
removeEntityByIndex: () => {},
addNewQuery: () => {},
removeQueryBuilderEntityByIndex: () => {},
removeQueryTypeItemByIndex: () => {},
addNewBuilderQuery: () => {},
addNewFormula: () => {},
addNewQueryItem: () => {},
});
const initialQueryBuilderData: QueryBuilderData = {
queryData: [],
queryFormulas: [],
};
export function QueryBuilderProvider({
children,
}: PropsWithChildren): JSX.Element {
@ -60,10 +66,15 @@ export function QueryBuilderProvider({
PANEL_TYPES.TIME_SERIES,
);
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
queryData: [],
queryFormulas: [],
});
const [currentQuery, setCurrentQuery] = useState<QueryState>(initialQuery);
const [queryType, setQueryType] = useState<EQueryType>(
EQueryType.QUERY_BUILDER,
);
const handleSetQueryType = useCallback((newQueryType: EQueryType) => {
setQueryType(newQueryType);
}, []);
const resetQueryBuilderInfo = useCallback((): void => {
setInitialDataSource(null);
@ -71,30 +82,48 @@ export function QueryBuilderProvider({
}, []);
const resetQueryBuilderData = useCallback(() => {
setQueryBuilderData(initialQueryBuilderData);
setCurrentQuery(initialQuery);
}, []);
const initQueryBuilderData = useCallback(
(queryBuilderData: QueryBuilderData): void => {
setQueryBuilderData(queryBuilderData);
(query: QueryState, queryType: EQueryType): void => {
setCurrentQuery(query);
setQueryType(queryType);
},
[],
);
const removeEntityByIndex = useCallback(
const removeQueryBuilderEntityByIndex = useCallback(
(type: keyof QueryBuilderData, index: number) => {
setQueryBuilderData((prevState) => {
const currentArray: (IBuilderQuery | IBuilderFormula)[] = prevState[type];
setCurrentQuery((prevState) => {
const currentArray: (IBuilderQuery | IBuilderFormula)[] =
prevState.builder[type];
return {
...prevState,
[type]: currentArray.filter((item, i) => index !== i),
builder: {
...prevState.builder,
[type]: currentArray.filter((_, i) => index !== i),
},
};
});
},
[],
);
const createNewQuery = useCallback(
const removeQueryTypeItemByIndex = useCallback(
(type: EQueryType.PROM | EQueryType.CLICKHOUSE, index: number) => {
setCurrentQuery((prevState) => {
const targetArray: (IPromQLQuery | IClickHouseQuery)[] = prevState[type];
return {
...prevState,
[type]: targetArray.filter((_, i) => index !== i),
};
});
},
[],
);
const createNewBuilderQuery = useCallback(
(queries: IBuilderQuery[]): IBuilderQuery => {
const existNames = queries.map((item) => item.queryName);
@ -121,7 +150,7 @@ export function QueryBuilderProvider({
[initialDataSource, panelType],
);
const createNewFormula = useCallback((formulas: IBuilderFormula[]) => {
const createNewBuilderFormula = useCallback((formulas: IBuilderFormula[]) => {
const existNames = formulas.map((item) => item.queryName);
const newFormula: IBuilderFormula = {
@ -135,28 +164,73 @@ export function QueryBuilderProvider({
return newFormula;
}, []);
const addNewQuery = useCallback(() => {
setQueryBuilderData((prevState) => {
if (prevState.queryData.length >= MAX_QUERIES) return prevState;
const createNewQueryTypeItem = useCallback(
(
itemArray: QueryState['clickhouse_sql'] | QueryState['promql'],
type: EQueryType.CLICKHOUSE | EQueryType.PROM,
): IPromQLQuery | IClickHouseQuery => {
const existNames = itemArray.map((item) => item.name);
const newQuery = createNewQuery(prevState.queryData);
const newItem: IPromQLQuery | IClickHouseQuery = {
...initialSingleQueryMap[type],
name: createNewBuilderItemName({
existNames,
sourceNames: alphabet,
}),
};
return { ...prevState, queryData: [...prevState.queryData, newQuery] };
});
}, [createNewQuery]);
return newItem;
},
[],
);
const addNewFormula = useCallback(() => {
setQueryBuilderData((prevState) => {
if (prevState.queryFormulas.length >= MAX_FORMULAS) return prevState;
const addNewQueryItem = useCallback(
(type: EQueryType.CLICKHOUSE | EQueryType.PROM) => {
setCurrentQuery((prevState) => {
if (prevState[type].length >= MAX_QUERIES) return prevState;
const newFormula = createNewFormula(prevState.queryFormulas);
const newQuery = createNewQueryTypeItem(prevState[type], type);
return {
...prevState,
queryFormulas: [...prevState.queryFormulas, newFormula],
[type]: [...prevState[type], newQuery],
};
});
}, [createNewFormula]);
},
[createNewQueryTypeItem],
);
const addNewBuilderQuery = useCallback(() => {
setCurrentQuery((prevState) => {
if (prevState.builder.queryData.length >= MAX_QUERIES) return prevState;
const newQuery = createNewBuilderQuery(prevState.builder.queryData);
return {
...prevState,
builder: {
...prevState.builder,
queryData: [...prevState.builder.queryData, newQuery],
},
};
});
}, [createNewBuilderQuery]);
const addNewFormula = useCallback(() => {
setCurrentQuery((prevState) => {
if (prevState.builder.queryFormulas.length >= MAX_FORMULAS) return prevState;
const newFormula = createNewBuilderFormula(prevState.builder.queryFormulas);
return {
...prevState,
builder: {
...prevState.builder,
queryFormulas: [...prevState.builder.queryFormulas, newFormula],
},
};
});
}, [createNewBuilderFormula]);
const setupInitialDataSource = useCallback(
(newInitialDataSource: DataSource | null) =>
@ -164,30 +238,54 @@ export function QueryBuilderProvider({
[],
);
const updateQueryBuilderData = useCallback(
(queries: IBuilderQuery[], index: number, newQueryData: IBuilderQuery) =>
queries.map((item, idx) => (index === idx ? newQueryData : item)),
const updateQueryBuilderData: <T>(
arr: T[],
index: number,
newQueryItem: T,
) => T[] = useCallback(
(arr, index, newQueryItem) =>
arr.map((item, idx) => (index === idx ? newQueryItem : item)),
[],
);
const updateFormulaBuilderData = useCallback(
(formulas: IBuilderFormula[], index: number, newFormula: IBuilderFormula) =>
formulas.map((item, idx) => (index === idx ? newFormula : item)),
[],
);
const handleSetQueryData = useCallback(
(index: number, newQueryData: IBuilderQuery): void => {
setQueryBuilderData((prevState) => {
const handleSetQueryItemData = useCallback(
(
index: number,
type: EQueryType.PROM | EQueryType.CLICKHOUSE,
newQueryData: IPromQLQuery | IClickHouseQuery,
) => {
setCurrentQuery((prevState) => {
const updatedQueryBuilderData = updateQueryBuilderData(
prevState.queryData,
prevState[type],
index,
newQueryData,
);
return {
...prevState,
[type]: updatedQueryBuilderData,
};
});
},
[updateQueryBuilderData],
);
const handleSetQueryData = useCallback(
(index: number, newQueryData: IBuilderQuery): void => {
setCurrentQuery((prevState) => {
const updatedQueryBuilderData = updateQueryBuilderData(
prevState.builder.queryData,
index,
newQueryData,
);
return {
...prevState,
builder: {
...prevState.builder,
queryData: updatedQueryBuilderData,
},
};
});
},
@ -195,20 +293,23 @@ export function QueryBuilderProvider({
);
const handleSetFormulaData = useCallback(
(index: number, formulaData: IBuilderFormula): void => {
setQueryBuilderData((prevState) => {
const updatedFormulasBuilderData = updateFormulaBuilderData(
prevState.queryFormulas,
setCurrentQuery((prevState) => {
const updatedFormulasBuilderData = updateQueryBuilderData(
prevState.builder.queryFormulas,
index,
formulaData,
);
return {
...prevState,
builder: {
...prevState.builder,
queryFormulas: updatedFormulasBuilderData,
},
};
});
},
[updateFormulaBuilderData],
[updateQueryBuilderData],
);
const handleSetPanelType = useCallback((newPanelType: GRAPH_TYPES) => {
@ -217,34 +318,44 @@ export function QueryBuilderProvider({
const contextValues: QueryBuilderContextType = useMemo(
() => ({
queryBuilderData,
currentQuery,
queryType,
initialDataSource,
panelType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetQueryItemData,
handleSetPanelType,
handleSetQueryType,
initQueryBuilderData,
setupInitialDataSource,
removeEntityByIndex,
addNewQuery,
removeQueryBuilderEntityByIndex,
removeQueryTypeItemByIndex,
addNewBuilderQuery,
addNewFormula,
addNewQueryItem,
}),
[
queryBuilderData,
currentQuery,
initialDataSource,
panelType,
queryType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetQueryItemData,
handleSetPanelType,
handleSetQueryType,
initQueryBuilderData,
setupInitialDataSource,
removeEntityByIndex,
addNewQuery,
removeQueryBuilderEntityByIndex,
removeQueryTypeItemByIndex,
addNewBuilderQuery,
addNewFormula,
addNewQueryItem,
],
);

View File

@ -1,14 +1,11 @@
import getDashboard from 'api/dashboard/get';
import {
ClickHouseQueryTemplate,
PromQLQueryTemplate,
} from 'constants/dashboard';
import {
initialClickHouseData,
initialQueryBuilderFormValues,
initialQueryPromQLData,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import GetQueryName from 'lib/query/GetQueryName';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import { Props } from 'types/api/dashboard/get';
@ -60,18 +57,8 @@ export const GetDashboard = ({
},
query: {
queryType: EQueryType.QUERY_BUILDER,
promql: [
{
name: GetQueryName([]) as string,
...PromQLQueryTemplate,
},
],
clickhouse_sql: [
{
name: GetQueryName([]) as string,
...ClickHouseQueryTemplate,
},
],
promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData],
builder: {
queryFormulas: [],
queryData: [initialQueryBuilderFormValues],

View File

@ -18,7 +18,7 @@ import { Dispatch } from 'redux';
import store from 'store';
import AppActions from 'types/actions';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Query } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { EQueryType } from 'types/common/dashboard';
import { GlobalReducer } from 'types/reducer/globalTime';
@ -50,13 +50,18 @@ export async function GetMetricQueryRange({
switch (query.queryType) {
case EQueryType.QUERY_BUILDER: {
const { queryData: data, queryFormulas } = query.builder;
const builderQueries = mapQueryDataToApi({
queryData: data,
queryFormulas,
});
legendMap = builderQueries.newLegendMap;
const currentQueryData = mapQueryDataToApi(data, 'queryName');
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
const builderQueries = {
...currentQueryData.data,
...currentFormulas.data,
};
legendMap = {
...currentQueryData.newLegendMap,
...currentFormulas.newLegendMap,
};
QueryPayload.compositeQuery.builderQueries = builderQueries.data;
QueryPayload.compositeQuery.builderQueries = builderQueries;
break;
}
case EQueryType.CLICKHOUSE: {

View File

@ -1,6 +1,6 @@
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
import { Query } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
export const UpdateQuery = (
props: UpdateQueryProps,
@ -18,9 +18,6 @@ export const UpdateQuery = (
};
export interface UpdateQueryProps {
// query: string;
// legend: string;
// currentIndex: number;
updatedQuery: Query;
widgetId: string;
yAxisUnit: string | undefined;

View File

@ -3,9 +3,9 @@ import { ApplySettingsToPanelProps } from 'store/actions/dashboard/applySettings
import {
Dashboard,
IDashboardVariable,
Query,
Widgets,
} from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery';
export const GET_DASHBOARD = 'GET_DASHBOARD';

View File

@ -1,28 +1,15 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { IClickHouseQuery, IPromQLQuery } from 'types/api/dashboard/getAll';
import {
BuilderClickHouseResource,
BuilderPromQLResource,
BuilderQueryDataResourse,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { QueryDataResourse } from 'types/common/queryBuilderMappers.types';
export interface ICompositeMetricQuery {
builderQueries: QueryDataResourse;
promQueries: IPromQueries;
chQueries: IChQueries;
builderQueries: BuilderQueryDataResourse;
promQueries: BuilderPromQLResource;
chQueries: BuilderClickHouseResource;
queryType: EQueryType;
panelType: GRAPH_TYPES;
}
export interface IChQueries {
[key: string]: IChQuery;
}
export interface IChQuery extends IClickHouseQuery {
query: string;
}
export interface IPromQuery extends IPromQLQuery {
stats?: '';
}
export interface IPromQueries {
[key: string]: IPromQuery;
}

View File

@ -1,8 +1,7 @@
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
import { Layout } from 'react-grid-layout';
import { EQueryType } from 'types/common/dashboard';
import { QueryBuilderData } from 'types/common/queryBuilder';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from '../widgets/getQuery';
@ -86,25 +85,6 @@ export interface Widgets extends IBaseWidget {
export interface PromQLWidgets extends IBaseWidget {
query: { query: string; legend: string }[];
}
export interface Query {
queryType: EQueryType;
promql: IPromQLQuery[];
builder: QueryBuilderData;
clickhouse_sql: IClickHouseQuery[];
}
export interface IClickHouseQuery {
name: string;
rawQuery: string;
legend: string;
disabled: boolean;
}
export interface IPromQLQuery {
query: string;
legend: string;
disabled: boolean;
name: string;
}
export interface IQueryBuilderTagFilterItems {
id: string;

View File

@ -1,4 +1,9 @@
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { EQueryType } from 'types/common/dashboard';
import {
DataSource,
QueryBuilderData,
ReduceOperators,
} from 'types/common/queryBuilder';
import { BaseAutocompleteData } from './queryAutocompleteResponse';
@ -55,3 +60,46 @@ export type IBuilderQuery = {
reduceTo: ReduceOperators;
legend: string;
};
export interface IClickHouseQuery {
name: string;
rawQuery: string;
legend: string;
disabled: boolean;
query: string;
}
export interface IPromQLQuery {
query: string;
legend: string;
disabled: boolean;
name: string;
}
export interface Query {
queryType: EQueryType;
promql: IPromQLQuery[];
builder: QueryBuilderData;
clickhouse_sql: IClickHouseQuery[];
}
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<
string,
IBuilderQuery | IBuilderFormula
>;
export type MapData =
| IBuilderQuery
| IBuilderFormula
| IClickHouseQuery
| IPromQLQuery;
export type MapQueryDataToApiResult<T> = {
data: T;
newLegendMap: Record<string, string>;
};

View File

@ -2,8 +2,13 @@ import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import {
IBuilderFormula,
IBuilderQuery,
IClickHouseQuery,
IPromQLQuery,
QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from './dashboard';
export enum DataSource {
METRICS = 'metrics',
TRACES = 'traces',
@ -148,19 +153,34 @@ export type QueryBuilderData = {
};
export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData;
currentQuery: QueryState;
queryType: EQueryType;
initialDataSource: DataSource | null;
panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void;
resetQueryBuilderInfo: () => void;
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
handleSetQueryItemData: (
index: number,
type: EQueryType.PROM | EQueryType.CLICKHOUSE,
newQueryData: IPromQLQuery | IClickHouseQuery,
) => void;
handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
handleSetQueryType: (newQueryType: EQueryType) => void;
initQueryBuilderData: (query: QueryState, queryType: EQueryType) => void;
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
removeEntityByIndex: (type: keyof QueryBuilderData, index: number) => void;
addNewQuery: () => void;
removeQueryBuilderEntityByIndex: (
type: keyof QueryBuilderData,
index: number,
) => void;
removeQueryTypeItemByIndex: (
type: EQueryType.PROM | EQueryType.CLICKHOUSE,
index: number,
) => void;
addNewBuilderQuery: () => void;
addNewFormula: () => void;
addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void;
};
export type QueryAdditionalFilter = {

View File

@ -1,14 +0,0 @@
import {
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
export type MapQuery = Record<string, IBuilderQuery>;
export type MapFormula = Record<string, IBuilderFormula>;
export type QueryDataResourse = Record<string, IBuilderQuery | IBuilderFormula>;
export type MapQueryDataToApiResult = {
data: QueryDataResourse;
newLegendMap: Record<string, string>;
};