Feat/logs explorer (#2905)

* feat: add query builder and graph

* feat: add graph

* fix: id in the another places

* fix: multiple queries for explorer logs

* chore: chunkName is updated

---------

Co-authored-by: Chintan Sudani <46838508+techchintan@users.noreply.github.com>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Yevhen Shevchenko 2023-06-16 13:38:39 +03:00 committed by GitHub
parent 82936f73a3
commit 5bdb0e84d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 644 additions and 287 deletions

View File

@ -101,6 +101,10 @@ export const Logs = Loadable(
() => import(/* webpackChunkName: "Logs" */ 'pages/Logs'),
);
export const LogsExplorer = Loadable(
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'),
);
export const Login = Loadable(
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
);

View File

@ -16,6 +16,7 @@ import {
ListAllALertsPage,
Login,
Logs,
LogsExplorer,
MySettings,
NewDashboardPage,
OrganizationSettings,
@ -209,6 +210,13 @@ const routes: AppRoutes[] = [
key: 'LOGS',
isPrivate: true,
},
{
path: ROUTES.LOGS_EXPLORER,
exact: true,
component: LogsExplorer,
key: 'LOGS_EXPLORER',
isPrivate: true,
},
{
path: ROUTES.LOGIN,
exact: true,

View File

@ -1,5 +1,6 @@
// ** Helpers
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import {
BaseAutocompleteData,
@ -18,6 +19,7 @@ import { EQueryType } from 'types/common/dashboard';
import {
BoolOperators,
DataSource,
LogsAggregatorOperator,
MetricAggregateOperator,
NumberOperators,
PanelTypeKeys,
@ -25,6 +27,7 @@ import {
QueryBuilderData,
ReduceOperators,
StringOperators,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
import { v4 as uuid } from 'uuid';
@ -100,14 +103,17 @@ export const initialHavingValues: HavingForm = {
};
export const initialAutocompleteData: BaseAutocompleteData = {
id: uuid(),
id: createIdFromObjectFields(
{ dataType: null, key: '', isColumn: null, type: null },
baseAutoCompleteIdKeysOrder,
),
dataType: null,
key: '',
isColumn: null,
type: null,
};
export const initialQueryBuilderFormValues: IBuilderQuery = {
const initialQueryBuilderFormValues: IBuilderQuery = {
dataSource: DataSource.METRICS,
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: MetricAggregateOperator.NOOP,
@ -127,6 +133,27 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
reduceTo: 'sum',
};
const initialQueryBuilderFormLogsValues: IBuilderQuery = {
...initialQueryBuilderFormValues,
aggregateOperator: LogsAggregatorOperator.COUNT,
dataSource: DataSource.LOGS,
};
const initialQueryBuilderFormTracesValues: IBuilderQuery = {
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
dataSource: DataSource.TRACES,
};
export const initialQueryBuilderFormValuesMap: Record<
DataSource,
IBuilderQuery
> = {
metrics: initialQueryBuilderFormValues,
logs: initialQueryBuilderFormLogsValues,
traces: initialQueryBuilderFormTracesValues,
};
export const initialFormulaBuilderFormValues: IBuilderFormula = {
queryName: createNewBuilderItemName({
existNames: [],
@ -161,17 +188,39 @@ export const initialSingleQueryMap: Record<
IClickHouseQuery | IPromQLQuery
> = { clickhouse_sql: initialClickHouseData, promql: initialQueryPromQLData };
export const initialQuery: QueryState = {
export const initialQueryState: QueryState = {
id: uuid(),
builder: initialQueryBuilderData,
clickhouse_sql: [initialClickHouseData],
promql: [initialQueryPromQLData],
};
export const initialQueryWithType: Query = {
...initialQuery,
const initialQueryWithType: Query = {
...initialQueryState,
queryType: EQueryType.QUERY_BUILDER,
};
const initialQueryLogsWithType: Query = {
...initialQueryWithType,
builder: {
...initialQueryWithType.builder,
queryData: [initialQueryBuilderFormValuesMap.logs],
},
};
const initialQueryTracesWithType: Query = {
...initialQueryWithType,
builder: {
...initialQueryWithType.builder,
queryData: [initialQueryBuilderFormValuesMap.traces],
},
};
export const initialQueriesMap: Record<DataSource, Query> = {
metrics: initialQueryWithType,
logs: initialQueryLogsWithType,
traces: initialQueryTracesWithType,
};
export const operatorsByTypes: Record<LocalDataType, string[]> = {
string: Object.values(StringOperators),
number: Object.values(NumberOperators),

View File

@ -1 +1,2 @@
export const COMPOSITE_QUERY = 'compositeQuery';
export const PANEL_TYPES_QUERY = 'panelTypes';

View File

@ -27,6 +27,7 @@ const ROUTES = {
UN_AUTHORIZED: '/un-authorized',
NOT_FOUND: '/not-found',
LOGS: '/logs',
LOGS_EXPLORER: '/logs-explorer',
HOME_PAGE: '/',
PASSWORD_RESET: '/password-reset',
LIST_LICENSES: '/licenses',

View File

@ -36,8 +36,11 @@ const themeColors = {
royalGrey: '#888888',
matterhornGrey: '#555555',
whiteCream: '#ffffffd5',
white: '#ffffff',
black: '#000000',
lightBlack: '#141414',
lightgrey: '#ddd',
lightWhite: '#ffffffd9',
borderLightGrey: '#d9d9d9',
borderDarkGrey: '#424242',
};

View File

@ -1,5 +1,5 @@
import {
initialQueryBuilderFormValues,
initialQueryBuilderFormValuesMap,
initialQueryPromQLData,
PANEL_TYPES,
} from 'constants/queryBuilder';
@ -11,11 +11,6 @@ import {
defaultMatchType,
} from 'types/api/alerts/def';
import { EQueryType } from 'types/common/dashboard';
import {
DataSource,
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
const defaultAlertDescription =
'This alert is fired when the defined metric (current value: {{$value}}) crosses the threshold ({{$threshold}})';
@ -32,7 +27,7 @@ export const alertDefaults: AlertDef = {
condition: {
compositeQuery: {
builderQueries: {
A: initialQueryBuilderFormValues,
A: initialQueryBuilderFormValuesMap.metrics,
},
promQueries: { A: initialQueryPromQLData },
chQueries: {
@ -61,11 +56,7 @@ export const logAlertDefaults: AlertDef = {
condition: {
compositeQuery: {
builderQueries: {
A: {
...initialQueryBuilderFormValues,
aggregateOperator: LogsAggregatorOperator.COUNT,
dataSource: DataSource.LOGS,
},
A: initialQueryBuilderFormValuesMap.logs,
},
promQueries: { A: initialQueryPromQLData },
chQueries: {
@ -95,11 +86,7 @@ export const traceAlertDefaults: AlertDef = {
condition: {
compositeQuery: {
builderQueries: {
A: {
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
dataSource: DataSource.TRACES,
},
A: initialQueryBuilderFormValuesMap.traces,
},
promQueries: { A: initialQueryPromQLData },
chQueries: {
@ -129,11 +116,7 @@ export const exceptionAlertDefaults: AlertDef = {
condition: {
compositeQuery: {
builderQueries: {
A: {
...initialQueryBuilderFormValues,
aggregateOperator: TracesAggregatorOperator.COUNT,
dataSource: DataSource.TRACES,
},
A: initialQueryBuilderFormValuesMap.traces,
},
promQueries: { A: initialQueryPromQLData },
chQueries: {

View File

@ -1,16 +1,11 @@
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,
@ -18,18 +13,12 @@ import {
import SelectAlertType from './SelectAlertType';
function CreateRules(): JSX.Element {
const [initValues, setInitValues] = useState<AlertDef>(alertDefaults);
const [initValues, setInitValues] = useState<AlertDef | null>(null);
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) {
@ -45,15 +34,9 @@ function CreateRules(): JSX.Element {
default:
setInitValues(alertDefaults);
}
const value = ALERTS_VALUES_MAP[typ].condition.compositeQuery;
const compositeQuery = mapQueryDataFromApi(value);
redirectWithQueryBuilderData(compositeQuery);
};
if (!compositeQuery) {
if (!initValues) {
return (
<Row wrap={false}>
<SelectAlertType onSelect={onSelectType} />

View File

@ -1,7 +1,7 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { StaticLineProps } from 'components/Graph';
import Spinner from 'components/Spinner';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import GridGraphComponent from 'container/GridGraphComponent';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
@ -17,7 +17,7 @@ import { ChartContainer, FailedMessageContainer } from './styles';
export interface ChartPreviewProps {
name: string;
query: Query | undefined;
query: Query | null;
graphType?: GRAPH_TYPES;
selectedTime?: timePreferenceType;
selectedInterval?: Time;
@ -74,15 +74,7 @@ function ChartPreview({
const queryResponse = useGetQueryRange(
{
query: query || {
queryType: EQueryType.QUERY_BUILDER,
promql: [],
builder: {
queryFormulas: [],
queryData: [],
},
clickhouse_sql: [],
},
query: query || initialQueriesMap.metrics,
globalSelectedInterval: selectedInterval,
graphType,
selectedTime,

View File

@ -48,7 +48,12 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const {
currentQuery,
stagedQuery,
handleRunQuery,
redirectWithQueryBuilderData,
} = useQueryBuilder();
// use query client
const ruleCache = useQueryClient();
@ -65,35 +70,14 @@ function FormAlertRules({
const sq = useMemo(() => mapQueryDataFromApi(initQuery), [initQuery]);
// 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<Query>();
// this use effect initiates staged query and
// other queries based on server data.
// useful when fetching of initial values (from api)
// is delayed
const { compositeQuery } = useShareBuilderUrl({ defaultValue: sq });
useShareBuilderUrl({ defaultValue: sq });
useEffect(() => {
if (compositeQuery && !manualStagedQuery) {
setManualStagedQuery(compositeQuery);
}
setAlertDef(initialValue);
}, [
initialValue,
initQuery,
redirectWithQueryBuilderData,
currentQuery,
manualStagedQuery,
compositeQuery,
]);
}, [initialValue]);
const onRunQuery = (): void => {
setManualStagedQuery(currentQuery);
redirectWithQueryBuilderData(currentQuery);
handleRunQuery();
};
const onCancelHandler = useCallback(() => {
@ -115,8 +99,6 @@ function FormAlertRules({
}
const query: Query = { ...currentQuery, queryType: val };
setManualStagedQuery(query);
redirectWithQueryBuilderData(query);
};
const { notifications } = useNotifications();
@ -368,7 +350,7 @@ function FormAlertRules({
headline={<PlotTag queryType={currentQuery.queryType} />}
name=""
threshold={alertDef.condition?.target}
query={manualStagedQuery}
query={stagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);
@ -378,7 +360,7 @@ function FormAlertRules({
headline={<PlotTag queryType={currentQuery.queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
query={stagedQuery}
/>
);
@ -387,7 +369,7 @@ function FormAlertRules({
headline={<PlotTag queryType={currentQuery.queryType} />}
name="Chart Preview"
threshold={alertDef.condition?.target}
query={manualStagedQuery}
query={stagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
/>
);

View File

@ -1,15 +1,10 @@
import { NotificationInstance } from 'antd/es/notification/interface';
import updateDashboardApi from 'api/dashboard/update';
import {
initialClickHouseData,
initialQueryBuilderFormValues,
initialQueryPromQLData,
} from 'constants/queryBuilder';
import { initialQueriesMap } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Layout } from 'react-grid-layout';
import store from 'store';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
export const UpdateDashboard = async (
{
@ -41,15 +36,7 @@ export const UpdateDashboard = async (
nullZeroValues: widgetData?.nullZeroValues || '',
opacity: '',
panelTypes: graphType,
query: widgetData?.query || {
queryType: EQueryType.QUERY_BUILDER,
promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData],
builder: {
queryFormulas: [],
queryData: [initialQueryBuilderFormValues],
},
},
query: widgetData?.query || initialQueriesMap.metrics,
timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME',
title: widgetData ? copyTitle : '',
},

View File

@ -0,0 +1,11 @@
import { Card } from 'antd';
import styled from 'styled-components';
export const CardStyled = styled(Card)`
position: relative;
margin: 0.5rem 0 3.1rem 0;
.ant-card-body {
height: 20vh;
min-height: 200px;
}
`;

View File

@ -0,0 +1,66 @@
import Graph from 'components/Graph';
import Spinner from 'components/Spinner';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getExplorerChartData } from 'lib/explorer/getExplorerChartData';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { CardStyled } from './LogsExplorerChart.styled';
export function LogsExplorerChart(): JSX.Element {
const { stagedQuery } = useQueryBuilder();
const { selectedTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const panelTypeParam = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
const { data, isFetching } = useGetQueryRange(
{
query: stagedQuery || initialQueriesMap.metrics,
graphType: panelTypeParam,
globalSelectedInterval: selectedTime,
selectedTime: 'GLOBAL_TIME',
},
{
queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE,
selectedTime,
stagedQuery,
panelTypeParam,
],
enabled: !!stagedQuery,
},
);
const graphData = useMemo(() => {
if (data?.payload.data && data.payload.data.result.length > 0) {
return getExplorerChartData([data.payload.data.result[0]]);
}
return getExplorerChartData([]);
}, [data]);
return (
<CardStyled>
{isFetching ? (
<Spinner size="default" height="100%" />
) : (
<Graph
name="logsExplorerChart"
data={graphData}
type="bar"
containerHeight="100%"
animate
/>
)}
</CardStyled>
);
}

View File

@ -0,0 +1 @@
export { LogsExplorerChart } from './LogsExplorerChart';

View File

@ -0,0 +1,9 @@
import { Tabs } from 'antd';
import { themeColors } from 'constants/theme';
import styled from 'styled-components';
export const TabsStyled = styled(Tabs)`
& .ant-tabs-nav {
background-color: ${themeColors.lightBlack};
}
`;

View File

@ -0,0 +1,75 @@
import { TabsProps } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useUrlQuery from 'hooks/useUrlQuery';
import { useCallback, useEffect, useMemo } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { TabsStyled } from './LogsExplorerViews.styled';
export function LogsExplorerViews(): JSX.Element {
const location = useLocation();
const urlQuery = useUrlQuery();
const history = useHistory();
const { currentQuery } = useQueryBuilder();
const panelTypeParams = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
const isMultipleQueries = useMemo(
() =>
currentQuery.builder.queryData.length > 1 ||
currentQuery.builder.queryFormulas.length > 0,
[currentQuery],
);
const tabsItems: TabsProps['items'] = useMemo(
() => [
{
label: 'List View',
key: PANEL_TYPES.LIST,
disabled: isMultipleQueries,
},
{ label: 'TimeSeries', key: PANEL_TYPES.TIME_SERIES },
{ label: 'Table', key: PANEL_TYPES.TABLE },
],
[isMultipleQueries],
);
const handleChangeView = useCallback(
(panelType: string) => {
urlQuery.set(PANEL_TYPES_QUERY, JSON.stringify(panelType) as GRAPH_TYPES);
const path = `${location.pathname}?${urlQuery}`;
history.push(path);
},
[history, location, urlQuery],
);
const currentTabKey = useMemo(
() =>
Object.values(PANEL_TYPES).includes(panelTypeParams)
? panelTypeParams
: PANEL_TYPES.LIST,
[panelTypeParams],
);
useEffect(() => {
if (panelTypeParams === 'list' && isMultipleQueries) {
handleChangeView(PANEL_TYPES.TIME_SERIES);
}
}, [panelTypeParams, isMultipleQueries, handleChangeView]);
return (
<div>
<TabsStyled
items={tabsItems}
defaultActiveKey={currentTabKey}
activeKey={currentTabKey}
onChange={handleChangeView}
/>
</div>
);
}

View File

@ -0,0 +1 @@
export { LogsExplorerViews } from './LogsExplorerViews';

View File

@ -1,6 +1,6 @@
import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
@ -18,7 +18,7 @@ export const getQueryBuilderQueries = ({
queryFormulas: [],
queryData: [
{
...initialQueryBuilderFormValues,
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled: false,
groupBy,
@ -53,7 +53,7 @@ export const getQueryBuilderQuerieswithFormula = ({
],
queryData: [
{
...initialQueryBuilderFormValues,
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,
@ -66,7 +66,7 @@ export const getQueryBuilderQuerieswithFormula = ({
},
},
{
...initialQueryBuilderFormValues,
...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled,
groupBy,

View File

@ -14,6 +14,7 @@ import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { Button } from './styles';
@ -56,6 +57,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -69,6 +71,7 @@ function DBCall({ getWidgetQueryBuilder }: DBCallProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);

View File

@ -15,6 +15,7 @@ import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
import { Card, GraphContainer, GraphTitle, Row } from '../styles';
import { legend } from './constant';
@ -48,6 +49,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -67,6 +69,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -82,6 +85,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);
@ -97,6 +101,7 @@ function External({ getWidgetQueryBuilder }: ExternalProps): JSX.Element {
tagFilterItems,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, tagFilterItems],
);

View File

@ -21,6 +21,7 @@ import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
import MetricReducer from 'types/reducer/metrics';
import { v4 as uuid } from 'uuid';
import {
errorPercentage,
@ -91,6 +92,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
topLevelOperations,
}),
clickhouse_sql: [],
id: uuid(),
}),
[getWidgetQueryBuilder, servicename, topLevelOperations, tagFilterItems],
);
@ -106,6 +108,7 @@ function Application({ getWidgetQueryBuilder }: DashboardProps): JSX.Element {
topLevelOperations,
}),
clickhouse_sql: [],
id: uuid(),
}),
[servicename, topLevelOperations, tagFilterItems, getWidgetQueryBuilder],
);

View File

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

View File

@ -1,4 +1,5 @@
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { ReactNode } from 'react';
import { DataSource } from 'types/common/queryBuilder';
export type QueryBuilderConfig =
@ -11,4 +12,5 @@ export type QueryBuilderConfig =
export type QueryBuilderProps = {
config?: QueryBuilderConfig;
panelType: ITEMS;
actions?: ReactNode;
};

View File

@ -0,0 +1,6 @@
import { Col } from 'antd';
import styled from 'styled-components';
export const ActionsWrapperStyled = styled(Col)`
padding-right: 1rem;
`;

View File

@ -11,15 +11,16 @@ import { Formula, Query } from './components';
// ** Types
import { QueryBuilderProps } from './QueryBuilder.interfaces';
// ** Styles
import { ActionsWrapperStyled } from './QueryBuilder.styled';
export const QueryBuilder = memo(function QueryBuilder({
config,
panelType,
actions,
}: QueryBuilderProps): JSX.Element {
const {
currentQuery,
setupInitialDataSource,
resetQueryBuilderInfo,
addNewBuilderQuery,
addNewFormula,
handleSetPanelType,
@ -35,13 +36,6 @@ export const QueryBuilder = memo(function QueryBuilder({
handleSetPanelType(panelType);
}, [handleSetPanelType, panelType]);
useEffect(
() => (): void => {
resetQueryBuilderInfo();
},
[resetQueryBuilderInfo],
);
const isDisabledQueryButton = useMemo(
() => currentQuery.builder.queryData.length >= MAX_QUERIES,
[currentQuery],
@ -81,28 +75,31 @@ export const QueryBuilder = memo(function QueryBuilder({
</Row>
</Col>
<Row gutter={[20, 0]}>
<Col>
<Button
disabled={isDisabledQueryButton}
type="primary"
icon={<PlusOutlined />}
onClick={addNewBuilderQuery}
>
Query
</Button>
</Col>
<Col>
<Button
disabled={isDisabledFormulaButton}
onClick={addNewFormula}
type="primary"
icon={<PlusOutlined />}
>
Formula
</Button>
</Col>
</Row>
<ActionsWrapperStyled span={24}>
<Row gutter={[20, 0]}>
<Col>
<Button
disabled={isDisabledQueryButton}
type="primary"
icon={<PlusOutlined />}
onClick={addNewBuilderQuery}
>
Query
</Button>
</Col>
<Col>
<Button
disabled={isDisabledFormulaButton}
onClick={addNewFormula}
type="primary"
icon={<PlusOutlined />}
>
Formula
</Button>
</Col>
{actions}
</Row>
</ActionsWrapperStyled>
</Row>
);
});

View File

@ -3,19 +3,17 @@ import userEvent from '@testing-library/user-event';
// Constants
import {
HAVING_OPERATORS,
initialQueryBuilderFormValues,
initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder';
import { transformFromStringToHaving } from 'lib/query/transformQueryBuilderData';
// ** Types
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
// ** Components
import { HavingFilter } from '../HavingFilter';
const valueWithAttributeAndOperator: IBuilderQuery = {
...initialQueryBuilderFormValues,
dataSource: DataSource.LOGS,
...initialQueryBuilderFormValuesMap.logs,
aggregateOperator: 'SUM',
aggregateAttribute: {
isColumn: false,
@ -29,7 +27,10 @@ describe('Having filter behaviour', () => {
test('Having filter render is rendered', () => {
const mockFn = jest.fn();
const { unmount } = render(
<HavingFilter query={initialQueryBuilderFormValues} onChange={mockFn} />,
<HavingFilter
query={initialQueryBuilderFormValuesMap.metrics}
onChange={mockFn}
/>,
);
const selectId = 'havingSelect';
@ -44,7 +45,10 @@ describe('Having filter behaviour', () => {
test('Having render is disabled initially', () => {
const mockFn = jest.fn();
const { unmount } = render(
<HavingFilter query={initialQueryBuilderFormValues} onChange={mockFn} />,
<HavingFilter
query={initialQueryBuilderFormValuesMap.metrics}
onChange={mockFn}
/>,
);
const input = screen.getByRole('combobox');

View File

@ -53,16 +53,20 @@ const menus: SidebarMenu[] = [
],
},
{
key: ROUTES.LOGS,
key: 'logs',
label: 'Logs',
icon: <AlignLeftOutlined />,
// label: createLabelWithTags('Logs', ['Beta']),
// children: [
// {
// key: ROUTES.LOGS,
// label: 'Search',
// },
// ],
children: [
{
key: ROUTES.LOGS,
label: 'Search',
},
// TODO: uncomment when will be ready explorer
// {
// key: ROUTES.LOGS_EXPLORER,
// label: 'Views',
// },
],
},
{
key: ROUTES.ALL_DASHBOARD,

View File

@ -19,6 +19,7 @@ const breadcrumbNameMap = {
[ROUTES.LIST_ALL_ALERT]: 'Alerts',
[ROUTES.ALL_DASHBOARD]: 'Dashboard',
[ROUTES.LOGS]: 'Logs',
[ROUTES.LOGS_EXPLORER]: 'Logs Explorer',
};
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element {

View File

@ -0,0 +1,14 @@
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
export const useGetCompositeQueryParam = (): Query | null => {
const urlQuery = useUrlQuery();
return useMemo(() => {
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
return compositeQuery ? JSON.parse(compositeQuery) : null;
}, [urlQuery]);
};

View File

@ -0,0 +1,16 @@
import { PANEL_TYPES_QUERY } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
export const useGetPanelTypesQueryParam = <T extends GRAPH_TYPES | undefined>(
defaultPanelType?: T,
): T extends undefined ? GRAPH_TYPES | null : GRAPH_TYPES => {
const urlQuery = useUrlQuery();
return useMemo(() => {
const panelTypeQuery = urlQuery.get(PANEL_TYPES_QUERY);
return panelTypeQuery ? JSON.parse(panelTypeQuery) : defaultPanelType;
}, [urlQuery, defaultPanelType]);
};

View File

@ -1,7 +1,6 @@
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { useLocation } from 'react-router-dom';
import {
GetMetricQueryRange,
GetQueryResultsProps,
@ -9,18 +8,18 @@ import {
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
export const useGetQueryRange = (
type UseGetQueryRange = (
requestData: GetQueryResultsProps,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
const { key } = useLocation();
) => UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>;
export const useGetQueryRange: UseGetQueryRange = (requestData, options) => {
const queryKey = useMemo(() => {
if (options?.queryKey) {
return [...options.queryKey, key];
return [...options.queryKey];
}
return [REACT_QUERY_KEY.GET_QUERY_RANGE, key, requestData];
}, [key, options?.queryKey, requestData]);
return [REACT_QUERY_KEY.GET_QUERY_RANGE, requestData];
}, [options?.queryKey, requestData]);
return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
queryFn: async () => GetMetricQueryRange(requestData),

View File

@ -1,6 +1,6 @@
import {
initialAutocompleteData,
initialQueryBuilderFormValues,
initialQueryBuilderFormValuesMap,
mapOfFilters,
} from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@ -21,6 +21,7 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
handleSetQueryData,
removeQueryBuilderEntityByIndex,
panelType,
initialDataSource,
} = useQueryBuilder();
const [operators, setOperators] = useState<SelectOption<string, string>[]>([]);
const [listOfAdditionalFilters, setListOfAdditionalFilters] = useState<
@ -80,9 +81,9 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
panelType,
});
const entries = Object.entries(initialQueryBuilderFormValues).filter(
([key]) => key !== 'queryName' && key !== 'expression',
);
const entries = Object.entries(
initialQueryBuilderFormValuesMap.metrics,
).filter(([key]) => key !== 'queryName' && key !== 'expression');
const initCopyResult = Object.fromEntries(entries);
@ -121,12 +122,24 @@ export const useQueryOperations: UseQueryOperations = ({ query, index }) => {
);
useEffect(() => {
if (initialDataSource && dataSource !== initialDataSource) return;
const initialOperators = getOperatorsBySourceAndPanelType({
dataSource,
panelType,
});
if (JSON.stringify(operators) === JSON.stringify(initialOperators)) return;
setOperators(initialOperators);
}, [dataSource, panelType]);
handleChangeOperator(initialOperators[0].value);
}, [
dataSource,
initialDataSource,
panelType,
operators,
handleChangeOperator,
]);
useEffect(() => {
const additionalFilters = getNewListOfAdditionalFilters(dataSource);

View File

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

View File

@ -0,0 +1,46 @@
import { ChartData } from 'chart.js';
import getLabelName from 'lib/getLabelName';
import { QueryData } from 'types/api/widgets/getQuery';
import { colors } from '../getRandomColor';
export const getExplorerChartData = (
queryData: QueryData[],
): ChartData<'bar'> => {
const uniqueTimeLabels = new Set<number>();
const sortedData = [...queryData].sort((a, b) => {
if (a.queryName < b.queryName) return -1;
if (a.queryName > b.queryName) return 1;
return 0;
});
const modifiedData: { label: string }[] = sortedData.map((result) => {
const { metric, queryName, legend } = result;
result.values.forEach((value) => {
uniqueTimeLabels.add(value[0] * 1000);
});
return {
label: getLabelName(metric, queryName || '', legend || ''),
};
});
const labels = Array.from(uniqueTimeLabels)
.sort((a, b) => a - b)
.map((value) => new Date(value));
const allLabels = modifiedData.map((e) => e.label);
const data: ChartData<'bar'> = {
labels,
datasets: queryData.map((result, index) => ({
label: allLabels[index],
data: result.values.map((item) => parseFloat(item[1])),
backgroundColor: colors[index % colors.length] || 'red',
borderColor: colors[index % colors.length] || 'red',
})),
};
return data;
};

View File

@ -15,6 +15,11 @@ export const getOperatorsBySourceAndPanelType = ({
}: GetQueryOperatorsParams): SelectOption<string, string>[] => {
let operatorsByDataSource = mapOfOperators[dataSource];
if (panelType === PANEL_TYPES.LIST) {
operatorsByDataSource = operatorsByDataSource.filter(
(operator) => operator.value === StringOperators.NOOP,
);
}
if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) {
operatorsByDataSource = operatorsByDataSource.filter(
(operator) => operator.value !== StringOperators.NOOP,

View File

@ -1,6 +1,7 @@
import { initialQuery } from 'constants/queryBuilder';
import { initialQueryState } from 'constants/queryBuilder';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
import { transformQueryBuilderDataModel } from '../transformQueryBuilderDataModel';
@ -9,14 +10,14 @@ export const mapQueryDataFromApi = (
): Query => {
const builder = compositeQuery.builderQueries
? transformQueryBuilderDataModel(compositeQuery.builderQueries)
: initialQuery.builder;
: initialQueryState.builder;
const promql = compositeQuery.promQueries
? Object.keys(compositeQuery.promQueries).map((key) => ({
...compositeQuery.promQueries[key],
name: key,
}))
: initialQuery.promql;
: initialQueryState.promql;
const clickhouseSql = compositeQuery.chQueries
? Object.keys(compositeQuery.chQueries).map((key) => ({
@ -24,12 +25,13 @@ export const mapQueryDataFromApi = (
name: key,
query: compositeQuery.chQueries[key].query,
}))
: initialQuery.clickhouse_sql;
: initialQueryState.clickhouse_sql;
return {
builder,
promql,
clickhouse_sql: clickhouseSql,
queryType: compositeQuery.queryType,
id: uuid(),
};
};

View File

@ -1,6 +1,6 @@
import {
initialFormulaBuilderFormValues,
initialQueryBuilderFormValues,
initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder';
import { FORMULA_REGEXP } from 'constants/regExp';
import {
@ -22,7 +22,7 @@ export const transformQueryBuilderDataModel = (
queryFormulas.push({ ...initialFormulaBuilderFormValues, ...formula });
} else {
const query = value as IBuilderQuery;
queryData.push({ ...initialQueryBuilderFormValues, ...query });
queryData.push({ ...initialQueryBuilderFormValuesMap.metrics, ...query });
}
});

View File

@ -0,0 +1,45 @@
import { Button, Col, Row } from 'antd';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { LogsExplorerChart } from 'container/LogsExplorerChart';
import { LogsExplorerViews } from 'container/LogsExplorerViews';
import { QueryBuilder } from 'container/QueryBuilder';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { DataSource } from 'types/common/queryBuilder';
// ** Styles
import { ButtonWrapperStyled, WrapperStyled } from './styles';
function LogsExporer(): JSX.Element {
const { handleRunQuery } = useQueryBuilder();
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
useShareBuilderUrl({ defaultValue: initialQueriesMap.logs });
return (
<WrapperStyled>
<Row gutter={[0, 28]}>
<Col xs={24}>
<QueryBuilder
panelType={panelTypes}
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
actions={
<ButtonWrapperStyled>
<Button type="primary" onClick={handleRunQuery}>
Run Query
</Button>
</ButtonWrapperStyled>
}
/>
</Col>
<Col xs={24}>
<LogsExplorerChart />
<LogsExplorerViews />
</Col>
</Row>
</WrapperStyled>
);
}
export default LogsExporer;

View File

@ -0,0 +1,11 @@
import { Col } from 'antd';
import { themeColors } from 'constants/theme';
import styled from 'styled-components';
export const WrapperStyled = styled.div`
color: ${themeColors.lightWhite};
`;
export const ButtonWrapperStyled = styled(Col)`
margin-left: auto;
`;

View File

@ -4,10 +4,10 @@ import {
formulasNames,
initialClickHouseData,
initialFormulaBuilderFormValues,
initialQuery,
initialQueryBuilderFormValues,
initialQueriesMap,
initialQueryBuilderFormValuesMap,
initialQueryPromQLData,
initialQueryWithType,
initialQueryState,
initialSingleQueryMap,
MAX_FORMULAS,
MAX_QUERIES,
@ -15,6 +15,7 @@ import {
} from 'constants/queryBuilder';
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import useUrlQuery from 'hooks/useUrlQuery';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
@ -44,19 +45,17 @@ import {
QueryBuilderContextType,
QueryBuilderData,
} from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
currentQuery: initialQueryWithType,
currentQuery: initialQueriesMap.metrics,
stagedQuery: initialQueriesMap.metrics,
initialDataSource: null,
panelType: PANEL_TYPES.TIME_SERIES,
resetQueryBuilderData: () => {},
resetQueryBuilderInfo: () => {},
handleSetQueryData: () => {},
handleSetFormulaData: () => {},
handleSetQueryItemData: () => {},
handleSetPanelType: () => {},
handleSetQueryType: () => {},
initQueryBuilderData: () => {},
setupInitialDataSource: () => {},
removeQueryBuilderEntityByIndex: () => {},
removeQueryTypeItemByIndex: () => {},
@ -64,6 +63,8 @@ export const QueryBuilderContext = createContext<QueryBuilderContextType>({
addNewFormula: () => {},
addNewQueryItem: () => {},
redirectWithQueryBuilderData: () => {},
handleRunQuery: () => {},
resetStagedQuery: () => {},
});
export function QueryBuilderProvider({
@ -73,6 +74,8 @@ export function QueryBuilderProvider({
const history = useHistory();
const location = useLocation();
const compositeQueryParam = useGetCompositeQueryParam();
const [initialDataSource, setInitialDataSource] = useState<DataSource | null>(
null,
);
@ -81,81 +84,77 @@ export function QueryBuilderProvider({
PANEL_TYPES.TIME_SERIES,
);
const [currentQuery, setCurrentQuery] = useState<QueryState>(initialQuery);
const [currentQuery, setCurrentQuery] = useState<QueryState>(
initialQueryState,
);
const [stagedQuery, setStagedQuery] = useState<Query | null>(null);
const [queryType, setQueryType] = useState<EQueryType>(
EQueryType.QUERY_BUILDER,
);
const handleSetQueryType = useCallback((newQueryType: EQueryType) => {
setQueryType(newQueryType);
}, []);
const initQueryBuilderData = useCallback(
(query: Query): void => {
const { queryType: newQueryType, ...queryState } = query;
const resetQueryBuilderInfo = useCallback((): void => {
setInitialDataSource(null);
setPanelType(PANEL_TYPES.TIME_SERIES);
}, []);
const resetQueryBuilderData = useCallback(() => {
setCurrentQuery(initialQuery);
}, []);
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,
const builder: QueryBuilderData = {
queryData: queryState.builder.queryData.map((item) => ({
...initialQueryBuilderFormValuesMap[
initialDataSource || DataSource.METRICS
],
...item,
}))
: initialQuery.promql;
})),
queryFormulas: queryState.builder.queryFormulas.map((item) => ({
...initialFormulaBuilderFormValues,
...item,
})),
};
const clickHouse: IClickHouseQuery[] = queryState.clickhouse_sql
? queryState.clickhouse_sql.map((item) => ({
const promql: IPromQLQuery[] = queryState.promql.map((item) => ({
...initialQueryPromQLData,
...item,
}));
const clickHouse: IClickHouseQuery[] = queryState.clickhouse_sql.map(
(item) => ({
...initialClickHouseData,
...item,
}))
: initialQuery.clickhouse_sql;
}),
);
setCurrentQuery({
clickhouse_sql: clickHouse,
promql,
builder: {
...builder,
queryData: builder.queryData.map((q) => ({
...q,
groupBy: q.groupBy.map(({ id: _, ...item }) => ({
...item,
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
const type = newQueryType || EQueryType.QUERY_BUILDER;
const newQueryState: QueryState = {
clickhouse_sql: clickHouse,
promql,
builder: {
...builder,
queryData: builder.queryData.map((q) => ({
...q,
groupBy: q.groupBy.map(({ id: _, ...item }) => ({
...item,
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
})),
aggregateAttribute: {
...q.aggregateAttribute,
id: createIdFromObjectFields(
q.aggregateAttribute,
baseAutoCompleteIdKeysOrder,
),
},
})),
aggregateAttribute: {
...q.aggregateAttribute,
id: createIdFromObjectFields(
q.aggregateAttribute,
baseAutoCompleteIdKeysOrder,
),
},
})),
},
});
},
id: queryState.id,
};
setQueryType(queryType || EQueryType.QUERY_BUILDER);
}, []);
const nextQuery: Query = { ...newQueryState, queryType: type };
setStagedQuery(nextQuery);
setCurrentQuery(newQueryState);
setQueryType(type);
},
[initialDataSource],
);
const removeQueryBuilderEntityByIndex = useCallback(
(type: keyof QueryBuilderData, index: number) => {
@ -190,9 +189,11 @@ export function QueryBuilderProvider({
const createNewBuilderQuery = useCallback(
(queries: IBuilderQuery[]): IBuilderQuery => {
const existNames = queries.map((item) => item.queryName);
const initialBuilderQuery =
initialQueryBuilderFormValuesMap[initialDataSource || DataSource.METRICS];
const newQuery: IBuilderQuery = {
...initialQueryBuilderFormValues,
...initialBuilderQuery,
queryName: createNewBuilderItemName({ existNames, sourceNames: alphabet }),
expression: createNewBuilderItemName({
existNames,
@ -381,7 +382,7 @@ export function QueryBuilderProvider({
}, []);
const redirectWithQueryBuilderData = useCallback(
(query: Partial<Query>) => {
(query: Partial<Query>, searchParams?: Record<string, unknown>) => {
const currentGeneratedQuery: Query = {
queryType:
!query.queryType || !Object.values(EQueryType).includes(query.queryType)
@ -389,63 +390,84 @@ export function QueryBuilderProvider({
: query.queryType,
builder:
!query.builder || query.builder.queryData.length === 0
? initialQuery.builder
? initialQueryState.builder
: query.builder,
promql:
!query.promql || query.promql.length === 0
? initialQuery.promql
? initialQueryState.promql
: query.promql,
clickhouse_sql:
!query.clickhouse_sql || query.clickhouse_sql.length === 0
? initialQuery.clickhouse_sql
? initialQueryState.clickhouse_sql
: query.clickhouse_sql,
id: uuid(),
};
urlQuery.set(COMPOSITE_QUERY, JSON.stringify(currentGeneratedQuery));
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
if (searchParams) {
Object.keys(searchParams).forEach((param) =>
urlQuery.set(param, JSON.stringify(searchParams[param])),
);
}
const generatedUrl = `${location.pathname}?${urlQuery}`;
history.push(generatedUrl);
},
[history, location, urlQuery],
);
useEffect(() => {
const compositeQuery = urlQuery.get(COMPOSITE_QUERY);
if (!compositeQuery) return;
const handleRunQuery = useCallback(() => {
redirectWithQueryBuilderData({ ...currentQuery, queryType });
}, [redirectWithQueryBuilderData, currentQuery, queryType]);
const newQuery: Query = JSON.parse(compositeQuery);
const resetStagedQuery = useCallback(() => {
setStagedQuery(null);
}, []);
useEffect(() => {
if (!compositeQueryParam) return;
if (stagedQuery && stagedQuery.id === compositeQueryParam.id) {
return;
}
const { isValid, validData } = replaceIncorrectObjectFields(
newQuery,
initialQueryWithType,
compositeQueryParam,
initialQueriesMap.metrics,
);
if (!isValid) {
redirectWithQueryBuilderData(validData);
} else {
initQueryBuilderData(newQuery);
initQueryBuilderData(compositeQueryParam);
}
}, [initQueryBuilderData, redirectWithQueryBuilderData, urlQuery]);
const query: Query = useMemo(() => ({ ...currentQuery, queryType }), [
currentQuery,
queryType,
}, [
initQueryBuilderData,
redirectWithQueryBuilderData,
compositeQueryParam,
stagedQuery,
]);
const query: Query = useMemo(
() => ({
...currentQuery,
queryType,
}),
[currentQuery, queryType],
);
const contextValues: QueryBuilderContextType = useMemo(
() => ({
currentQuery: query,
stagedQuery,
initialDataSource,
panelType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetQueryItemData,
handleSetPanelType,
handleSetQueryType,
initQueryBuilderData,
setupInitialDataSource,
removeQueryBuilderEntityByIndex,
removeQueryTypeItemByIndex,
@ -453,19 +475,18 @@ export function QueryBuilderProvider({
addNewFormula,
addNewQueryItem,
redirectWithQueryBuilderData,
handleRunQuery,
resetStagedQuery,
}),
[
query,
stagedQuery,
initialDataSource,
panelType,
resetQueryBuilderData,
resetQueryBuilderInfo,
handleSetQueryData,
handleSetFormulaData,
handleSetQueryItemData,
handleSetPanelType,
handleSetQueryType,
initQueryBuilderData,
setupInitialDataSource,
removeQueryBuilderEntityByIndex,
removeQueryTypeItemByIndex,
@ -473,6 +494,8 @@ export function QueryBuilderProvider({
addNewFormula,
addNewQueryItem,
redirectWithQueryBuilderData,
handleRunQuery,
resetStagedQuery,
],
);

View File

@ -1,5 +1,5 @@
import getDashboard from 'api/dashboard/get';
import { initialQueryWithType, PANEL_TYPES } from 'constants/queryBuilder';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Dispatch } from 'redux';
import AppActions from 'types/actions';
@ -39,7 +39,7 @@ export const GetDashboard = ({
panelTypes: graphType || PANEL_TYPES.TIME_SERIES,
timePreferance: 'GLOBAL_TIME',
title: '',
query: initialQueryWithType,
query: initialQueriesMap.metrics,
},
});
}

View File

@ -12,6 +12,7 @@ import getStep from 'lib/getStep';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es';
import store from 'store';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { SuccessResponse } from 'types/api';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';

View File

@ -79,6 +79,7 @@ export interface Query {
promql: IPromQLQuery[];
builder: QueryBuilderData;
clickhouse_sql: IClickHouseQuery[];
id: string;
}
export type QueryState = Omit<Query, 'queryType'>;

View File

@ -154,10 +154,9 @@ export type QueryBuilderData = {
export type QueryBuilderContextType = {
currentQuery: Query;
stagedQuery: Query | null;
initialDataSource: DataSource | null;
panelType: GRAPH_TYPES;
resetQueryBuilderData: () => void;
resetQueryBuilderInfo: () => void;
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
handleSetQueryItemData: (
@ -166,8 +165,6 @@ export type QueryBuilderContextType = {
newQueryData: IPromQLQuery | IClickHouseQuery,
) => void;
handleSetPanelType: (newPanelType: GRAPH_TYPES) => void;
handleSetQueryType: (newQueryType: EQueryType) => void;
initQueryBuilderData: (query: Partial<Query>) => void;
setupInitialDataSource: (newInitialDataSource: DataSource | null) => void;
removeQueryBuilderEntityByIndex: (
type: keyof QueryBuilderData,
@ -180,7 +177,12 @@ export type QueryBuilderContextType = {
addNewBuilderQuery: () => void;
addNewFormula: () => void;
addNewQueryItem: (type: EQueryType.PROM | EQueryType.CLICKHOUSE) => void;
redirectWithQueryBuilderData: (query: Query) => void;
redirectWithQueryBuilderData: (
query: Query,
searchParams?: Record<string, unknown>,
) => void;
handleRunQuery: () => void;
resetStagedQuery: () => void;
};
export type QueryAdditionalFilter = {

View File

@ -69,6 +69,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
USAGE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
VERSION: ['ADMIN', 'EDITOR', 'VIEWER'],
LOGS: ['ADMIN', 'EDITOR', 'VIEWER'],
LOGS_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
LIST_LICENSES: ['ADMIN'],
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
};