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'), () => import(/* webpackChunkName: "Logs" */ 'pages/Logs'),
); );
export const LogsExplorer = Loadable(
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'),
);
export const Login = Loadable( export const Login = Loadable(
() => import(/* webpackChunkName: "Login" */ 'pages/Login'), () => import(/* webpackChunkName: "Login" */ 'pages/Login'),
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,10 @@
import { NotificationInstance } from 'antd/es/notification/interface'; import { NotificationInstance } from 'antd/es/notification/interface';
import updateDashboardApi from 'api/dashboard/update'; import updateDashboardApi from 'api/dashboard/update';
import { import { initialQueriesMap } from 'constants/queryBuilder';
initialClickHouseData,
initialQueryBuilderFormValues,
initialQueryPromQLData,
} from 'constants/queryBuilder';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import store from 'store'; import store from 'store';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard';
export const UpdateDashboard = async ( export const UpdateDashboard = async (
{ {
@ -41,15 +36,7 @@ export const UpdateDashboard = async (
nullZeroValues: widgetData?.nullZeroValues || '', nullZeroValues: widgetData?.nullZeroValues || '',
opacity: '', opacity: '',
panelTypes: graphType, panelTypes: graphType,
query: widgetData?.query || { query: widgetData?.query || initialQueriesMap.metrics,
queryType: EQueryType.QUERY_BUILDER,
promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData],
builder: {
queryFormulas: [],
queryData: [initialQueryBuilderFormValues],
},
},
timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME', timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME',
title: widgetData ? copyTitle : '', 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 { import {
initialFormulaBuilderFormValues, initialFormulaBuilderFormValues,
initialQueryBuilderFormValues, initialQueryBuilderFormValuesMap,
} from 'constants/queryBuilder'; } from 'constants/queryBuilder';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
@ -18,7 +18,7 @@ export const getQueryBuilderQueries = ({
queryFormulas: [], queryFormulas: [],
queryData: [ queryData: [
{ {
...initialQueryBuilderFormValues, ...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE, aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled: false, disabled: false,
groupBy, groupBy,
@ -53,7 +53,7 @@ export const getQueryBuilderQuerieswithFormula = ({
], ],
queryData: [ queryData: [
{ {
...initialQueryBuilderFormValues, ...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE, aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled, disabled,
groupBy, groupBy,
@ -66,7 +66,7 @@ export const getQueryBuilderQuerieswithFormula = ({
}, },
}, },
{ {
...initialQueryBuilderFormValues, ...initialQueryBuilderFormValuesMap.metrics,
aggregateOperator: MetricAggregateOperator.SUM_RATE, aggregateOperator: MetricAggregateOperator.SUM_RATE,
disabled, disabled,
groupBy, groupBy,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems';
import { ReactNode } from 'react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
export type QueryBuilderConfig = export type QueryBuilderConfig =
@ -11,4 +12,5 @@ export type QueryBuilderConfig =
export type QueryBuilderProps = { export type QueryBuilderProps = {
config?: QueryBuilderConfig; config?: QueryBuilderConfig;
panelType: ITEMS; 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 // ** Types
import { QueryBuilderProps } from './QueryBuilder.interfaces'; import { QueryBuilderProps } from './QueryBuilder.interfaces';
// ** Styles // ** Styles
import { ActionsWrapperStyled } from './QueryBuilder.styled';
export const QueryBuilder = memo(function QueryBuilder({ export const QueryBuilder = memo(function QueryBuilder({
config, config,
panelType, panelType,
actions,
}: QueryBuilderProps): JSX.Element { }: QueryBuilderProps): JSX.Element {
const { const {
currentQuery, currentQuery,
setupInitialDataSource, setupInitialDataSource,
resetQueryBuilderInfo,
addNewBuilderQuery, addNewBuilderQuery,
addNewFormula, addNewFormula,
handleSetPanelType, handleSetPanelType,
@ -35,13 +36,6 @@ export const QueryBuilder = memo(function QueryBuilder({
handleSetPanelType(panelType); handleSetPanelType(panelType);
}, [handleSetPanelType, panelType]); }, [handleSetPanelType, panelType]);
useEffect(
() => (): void => {
resetQueryBuilderInfo();
},
[resetQueryBuilderInfo],
);
const isDisabledQueryButton = useMemo( const isDisabledQueryButton = useMemo(
() => currentQuery.builder.queryData.length >= MAX_QUERIES, () => currentQuery.builder.queryData.length >= MAX_QUERIES,
[currentQuery], [currentQuery],
@ -81,28 +75,31 @@ export const QueryBuilder = memo(function QueryBuilder({
</Row> </Row>
</Col> </Col>
<Row gutter={[20, 0]}> <ActionsWrapperStyled span={24}>
<Col> <Row gutter={[20, 0]}>
<Button <Col>
disabled={isDisabledQueryButton} <Button
type="primary" disabled={isDisabledQueryButton}
icon={<PlusOutlined />} type="primary"
onClick={addNewBuilderQuery} icon={<PlusOutlined />}
> onClick={addNewBuilderQuery}
Query >
</Button> Query
</Col> </Button>
<Col> </Col>
<Button <Col>
disabled={isDisabledFormulaButton} <Button
onClick={addNewFormula} disabled={isDisabledFormulaButton}
type="primary" onClick={addNewFormula}
icon={<PlusOutlined />} type="primary"
> icon={<PlusOutlined />}
Formula >
</Button> Formula
</Col> </Button>
</Row> </Col>
{actions}
</Row>
</ActionsWrapperStyled>
</Row> </Row>
); );
}); });

View File

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

View File

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

View File

@ -19,6 +19,7 @@ const breadcrumbNameMap = {
[ROUTES.LIST_ALL_ALERT]: 'Alerts', [ROUTES.LIST_ALL_ALERT]: 'Alerts',
[ROUTES.ALL_DASHBOARD]: 'Dashboard', [ROUTES.ALL_DASHBOARD]: 'Dashboard',
[ROUTES.LOGS]: 'Logs', [ROUTES.LOGS]: 'Logs',
[ROUTES.LOGS_EXPLORER]: 'Logs Explorer',
}; };
function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { 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 { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
import { useLocation } from 'react-router-dom';
import { import {
GetMetricQueryRange, GetMetricQueryRange,
GetQueryResultsProps, GetQueryResultsProps,
@ -9,18 +8,18 @@ import {
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
export const useGetQueryRange = ( type UseGetQueryRange = (
requestData: GetQueryResultsProps, requestData: GetQueryResultsProps,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>, options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => { ) => UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>;
const { key } = useLocation();
export const useGetQueryRange: UseGetQueryRange = (requestData, options) => {
const queryKey = useMemo(() => { const queryKey = useMemo(() => {
if (options?.queryKey) { if (options?.queryKey) {
return [...options.queryKey, key]; return [...options.queryKey];
} }
return [REACT_QUERY_KEY.GET_QUERY_RANGE, key, requestData]; return [REACT_QUERY_KEY.GET_QUERY_RANGE, requestData];
}, [key, options?.queryKey, requestData]); }, [options?.queryKey, requestData]);
return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({ return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
queryFn: async () => GetMetricQueryRange(requestData), queryFn: async () => GetMetricQueryRange(requestData),

View File

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

View File

@ -1,27 +1,19 @@
import { COMPOSITE_QUERY } from 'constants/queryBuilderQueryNames';
import useUrlQuery from 'hooks/useUrlQuery'; import useUrlQuery from 'hooks/useUrlQuery';
import { useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { useGetCompositeQueryParam } from './useGetCompositeQueryParam';
import { useQueryBuilder } from './useQueryBuilder'; import { useQueryBuilder } from './useQueryBuilder';
type UseShareBuilderUrlParams = { defaultValue: Query }; type UseShareBuilderUrlParams = { defaultValue: Query };
type UseShareBuilderUrlReturnType = { compositeQuery: Query | null };
export const useShareBuilderUrl = ({ export const useShareBuilderUrl = ({
defaultValue, defaultValue,
}: UseShareBuilderUrlParams): UseShareBuilderUrlReturnType => { }: UseShareBuilderUrlParams): void => {
const { redirectWithQueryBuilderData } = useQueryBuilder(); const { redirectWithQueryBuilderData, resetStagedQuery } = useQueryBuilder();
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const compositeQuery: Query | null = useMemo(() => { const compositeQuery = useGetCompositeQueryParam();
const query = urlQuery.get(COMPOSITE_QUERY);
if (query) {
return JSON.parse(query);
}
return null;
}, [urlQuery]);
useEffect(() => { useEffect(() => {
if (!compositeQuery) { if (!compositeQuery) {
@ -29,5 +21,10 @@ export const useShareBuilderUrl = ({
} }
}, [defaultValue, urlQuery, redirectWithQueryBuilderData, compositeQuery]); }, [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>[] => { }: GetQueryOperatorsParams): SelectOption<string, string>[] => {
let operatorsByDataSource = mapOfOperators[dataSource]; 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) { if (dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST) {
operatorsByDataSource = operatorsByDataSource.filter( operatorsByDataSource = operatorsByDataSource.filter(
(operator) => operator.value !== StringOperators.NOOP, (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 { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { v4 as uuid } from 'uuid';
import { transformQueryBuilderDataModel } from '../transformQueryBuilderDataModel'; import { transformQueryBuilderDataModel } from '../transformQueryBuilderDataModel';
@ -9,14 +10,14 @@ export const mapQueryDataFromApi = (
): Query => { ): Query => {
const builder = compositeQuery.builderQueries const builder = compositeQuery.builderQueries
? transformQueryBuilderDataModel(compositeQuery.builderQueries) ? transformQueryBuilderDataModel(compositeQuery.builderQueries)
: initialQuery.builder; : initialQueryState.builder;
const promql = compositeQuery.promQueries const promql = compositeQuery.promQueries
? Object.keys(compositeQuery.promQueries).map((key) => ({ ? Object.keys(compositeQuery.promQueries).map((key) => ({
...compositeQuery.promQueries[key], ...compositeQuery.promQueries[key],
name: key, name: key,
})) }))
: initialQuery.promql; : initialQueryState.promql;
const clickhouseSql = compositeQuery.chQueries const clickhouseSql = compositeQuery.chQueries
? Object.keys(compositeQuery.chQueries).map((key) => ({ ? Object.keys(compositeQuery.chQueries).map((key) => ({
@ -24,12 +25,13 @@ export const mapQueryDataFromApi = (
name: key, name: key,
query: compositeQuery.chQueries[key].query, query: compositeQuery.chQueries[key].query,
})) }))
: initialQuery.clickhouse_sql; : initialQueryState.clickhouse_sql;
return { return {
builder, builder,
promql, promql,
clickhouse_sql: clickhouseSql, clickhouse_sql: clickhouseSql,
queryType: compositeQuery.queryType, queryType: compositeQuery.queryType,
id: uuid(),
}; };
}; };

View File

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

View File

@ -1,5 +1,5 @@
import getDashboard from 'api/dashboard/get'; 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 { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import AppActions from 'types/actions'; import AppActions from 'types/actions';
@ -39,7 +39,7 @@ export const GetDashboard = ({
panelTypes: graphType || PANEL_TYPES.TIME_SERIES, panelTypes: graphType || PANEL_TYPES.TIME_SERIES,
timePreferance: 'GLOBAL_TIME', timePreferance: 'GLOBAL_TIME',
title: '', 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 { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import store from 'store'; import store from 'store';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';

View File

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

View File

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

View File

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