Merge pull request #2539 from SigNoz/release/v0.18.1

Release/v0.18.1
This commit is contained in:
Ankit Nayan 2023-04-03 16:19:24 +05:30 committed by GitHub
commit c8f3e9024c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 671 additions and 156 deletions

View File

@ -137,7 +137,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.18.0
image: signoz/query-service:0.18.1
command: ["-config=/root/config/prometheus.yml"]
# ports:
# - "6060:6060" # pprof port
@ -166,7 +166,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:0.18.0
image: signoz/frontend:0.18.1
deploy:
restart_policy:
condition: on-failure

View File

@ -153,7 +153,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.18.0}
image: signoz/query-service:${DOCKER_TAG:-0.18.1}
container_name: query-service
command: ["-config=/root/config/prometheus.yml"]
# ports:
@ -181,7 +181,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.18.0}
image: signoz/frontend:${DOCKER_TAG:-0.18.1}
container_name: frontend
restart: on-failure
depends_on:

View File

@ -1,6 +1,7 @@
const apiV1 = '/api/v1/';
export const apiV2 = '/api/v2/';
export const apiV3 = '/api/v3/';
export const apiAlertManager = '/api/alertmanager';
export default apiV1;

View File

@ -9,7 +9,7 @@ import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import store from 'store';
import apiV1, { apiAlertManager, apiV2 } from './apiV1';
import apiV1, { apiAlertManager, apiV2, apiV3 } from './apiV1';
import { Logout } from './utils';
const interceptorsResponse = (
@ -109,6 +109,17 @@ ApiV2Instance.interceptors.response.use(
);
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
// axios V3
export const ApiV3Instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV3}`,
});
ApiV3Instance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,
);
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
//
AxiosAlertManagerInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,

View File

@ -0,0 +1,33 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
// ** Helpers
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAggregateAttributePayload } from 'types/api/queryBuilder/getAggregatorAttribute';
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
export const getAggregateAttribute = async ({
aggregateOperator,
searchText,
dataSource,
}: IGetAggregateAttributePayload): Promise<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
> => {
try {
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
`autocomplete/aggregate_attributes?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&searchText=${searchText}`,
);
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data.data,
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);
}
};

View File

@ -0,0 +1,33 @@
import { ApiV3Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
// ** Types
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
import { IQueryAutocompleteResponse } from 'types/api/queryBuilder/queryAutocompleteResponse';
export const getAggregateKeys = async ({
aggregateOperator,
searchText,
dataSource,
aggregateAttribute,
}: IGetAttributeKeysPayload): Promise<
SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse
> => {
try {
const response: AxiosResponse<{
data: IQueryAutocompleteResponse;
}> = await ApiV3Instance.get(
`autocomplete/attribute_keys?aggregateOperator=${aggregateOperator}&dataSource=${dataSource}&aggregateAttribute=${aggregateAttribute}&searchText=${searchText}`,
);
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data.data,
};
} catch (e) {
return ErrorResponseHandler(e as AxiosError);
}
};

View File

@ -0,0 +1,3 @@
export enum QueryBuilderKeys {
GET_AGGREGATE_ATTRIBUTE = 'GET_AGGREGATE_ATTRIBUTE',
}

View File

@ -61,7 +61,7 @@ export function onGraphClickHandler(
const points = chart.getElementsAtEventForMode(
event.native,
'nearest',
{ intersect: true },
{ intersect: false },
true,
);
const id = `${from}_button`;

View File

@ -1,10 +1,9 @@
/* eslint-disable */
//@ts-nocheck
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Button, Tabs } from 'antd';
import TextToolTip from 'components/TextToolTip';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
import { QueryBuilder } from 'container/QueryBuilder';
import { cloneDeep, isEqual } from 'lodash-es';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { connect, useSelector } from 'react-redux';
@ -31,6 +30,7 @@ import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL';
import QueryBuilderQueryContainer from './QueryBuilder/queryBuilder';
import TabHeader from './TabHeader';
import { IHandleUpdatedQuery } from './types';
import { getQueryKey } from './utils/getQueryKey';
import { showUnstagedStashConfirmBox } from './utils/userSettings';
@ -54,9 +54,7 @@ function QuerySection({
const { search } = useLocation();
const { widgets } = selectedDashboards.data;
const urlQuery = useMemo(() => {
return new URLSearchParams(search);
}, [search]);
const urlQuery = useMemo(() => new URLSearchParams(search), [search]);
const getWidget = useCallback(() => {
const widgetId = urlQuery.get('widgetId');
@ -169,6 +167,9 @@ function QuerySection({
}
selectedGraph={selectedGraph}
/>
// TODO: uncomment for testing new QueryBuilder
// <QueryBuilder />
),
},
{

View File

@ -1,9 +1,12 @@
import {
IBuilderFormula,
IBuilderQuery,
} from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
export type QueryBuilderConfig =
| {
queryVariant: 'static';
initialDataSource: DataSource;
}
| { queryVariant: 'dropdown' };
export type QueryBuilderProps = {
queryData: IBuilderQuery[];
queryFormula: IBuilderFormula[];
config?: QueryBuilderConfig;
};

View File

@ -2,14 +2,14 @@
import { useQueryBuilder } from 'hooks/useQueryBuilder';
import React from 'react';
// ** Components
import { Query } from './components';
// ** Types
import { QueryBuilderProps } from './QueryBuilder.interfaces';
// TODO: temporary eslint disable while variable isn't used
// TODO: I think it can be components switcher, because if we have different views based on the data source, we can render based on source
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function QueryBuilder(props: QueryBuilderProps): JSX.Element {
// TODO: temporary doesn't use
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function QueryBuilder({ config }: QueryBuilderProps): JSX.Element {
const { queryBuilderData } = useQueryBuilder();
// Here we can use Form from antd library and fill context data or edit
@ -19,5 +19,17 @@ export function QueryBuilder(props: QueryBuilderProps): JSX.Element {
// Each component can be part of antd Form list where we can add or remove items
// Also need decide to make a copy of queryData for working with form or not and after it set the full new list with formulas or queries to the context
// With button to add him
return <div>null</div>;
return (
<div>
{queryBuilderData.queryData.map((query, index) => (
<Query
key={query.queryName}
index={index}
isAvailableToDisable={queryBuilderData.queryData.length > 1}
queryVariant={config?.queryVariant || 'dropdown'}
query={query}
/>
))}
</div>
);
}

View File

@ -0,0 +1,6 @@
import { SelectProps } from 'antd';
import { DataSource } from 'types/common/queryBuilder';
export type QueryLabelProps = {
onChange: (value: DataSource) => void;
} & Omit<SelectProps, 'onChange'>;

View File

@ -0,0 +1,33 @@
import { Select } from 'antd';
import React from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
// ** Helpers
import { transformToUpperCase } from 'utils/transformToUpperCase';
// ** Types
import { QueryLabelProps } from './DataSourceDropdown.interfaces';
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
export function DataSourceDropdown(props: QueryLabelProps): JSX.Element {
const { onChange, value, style } = props;
const dataSourceOptions: SelectOption<
DataSource,
string
>[] = dataSourceMap.map((source) => ({
label: transformToUpperCase(source),
value: source,
}));
return (
<Select
defaultValue={dataSourceOptions[0].value}
options={dataSourceOptions}
onChange={onChange}
value={value}
style={style}
/>
);
}

View File

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

View File

@ -0,0 +1,6 @@
import { CSSProperties } from 'react';
export type FilterLabelProps = {
label: string;
style?: CSSProperties;
};

View File

@ -0,0 +1,13 @@
import styled from 'styled-components';
export const StyledLabel = styled.div`
padding: 0 0.6875rem;
min-width: 6.5rem;
width: fit-content;
min-height: 2rem;
display: inline-flex;
align-items: center;
border-radius: 0.125rem;
border: 0.0625rem solid #434343;
background-color: #141414;
`;

View File

@ -0,0 +1,10 @@
import React from 'react';
// ** Types
import { FilterLabelProps } from './FilterLabel.interfaces';
// ** Styles
import { StyledLabel } from './FilterLabel.styled';
export function FilterLabel({ label }: FilterLabelProps): JSX.Element {
return <StyledLabel>{label}</StyledLabel>;
}

View File

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

View File

@ -0,0 +1,2 @@
// TODO: temporary type
export type FormulaProps = { test: string };

View File

@ -0,0 +1,5 @@
import React from 'react';
export function Formula(): JSX.Element {
return <div>null</div>;
}

View File

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

View File

@ -1,3 +1,5 @@
import { CSSProperties } from 'react';
export type ListMarkerProps = {
isDisabled: boolean;
labelName: string;
@ -5,4 +7,5 @@ export type ListMarkerProps = {
className?: string;
isAvailableToDisable: boolean;
toggleDisabled: (index: number) => void;
style?: CSSProperties;
};

View File

@ -14,6 +14,7 @@ export function ListMarker({
isAvailableToDisable,
className,
toggleDisabled,
style,
}: ListMarkerProps): JSX.Element {
const buttonProps: Partial<ButtonProps> = isAvailableToDisable
? {
@ -29,6 +30,7 @@ export function ListMarker({
icon={buttonProps.icon}
onClick={buttonProps.onClick}
className={className}
style={{ marginRight: '0.1rem', ...style }}
>
{labelName}
</StyledButton>

View File

@ -0,0 +1,8 @@
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
export type QueryProps = {
index: number;
isAvailableToDisable: boolean;
query: IBuilderQueryForm;
queryVariant: 'static' | 'dropdown';
};

View File

@ -0,0 +1,95 @@
/* eslint-disable react/jsx-props-no-spreading */
import { Col, Row } from 'antd';
// ** Components
import {
DataSourceDropdown,
FilterLabel,
ListMarker,
} from 'container/QueryBuilder/components';
import {
AggregatorFilter,
OperatorsSelect,
} from 'container/QueryBuilder/filters';
// Context
import { useQueryBuilder } from 'hooks/useQueryBuilder';
// ** Hooks
import React from 'react';
import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
// ** Constants
import {
LogsAggregatorOperator,
MetricAggregateOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { transformToUpperCase } from 'utils/transformToUpperCase';
// ** Types
import { QueryProps } from './Query.interfaces';
const mapOfOperators: Record<DataSource, string[]> = {
metrics: Object.values(MetricAggregateOperator),
logs: Object.values(LogsAggregatorOperator),
traces: Object.values(TracesAggregatorOperator),
};
export function Query({
index,
isAvailableToDisable,
queryVariant,
query,
}: QueryProps): JSX.Element {
const { handleSetQueryData } = useQueryBuilder();
const currentListOfOperators = mapOfOperators[query.dataSource];
const handleChangeOperator = (value: string): void => {
handleSetQueryData(index, { aggregateOperator: value });
};
const handleChangeDataSource = (nextSource: DataSource): void => {
handleSetQueryData(index, { dataSource: nextSource });
};
const handleToggleDisableQuery = (): void => {
handleSetQueryData(index, { disabled: !query.disabled });
};
const handleChangeAggregatorAttribute = (value: AutocompleteData): void => {
handleSetQueryData(index, { aggregateAttribute: value });
};
return (
<Row style={{ gap: '0.75rem' }}>
<Col span={12}>
<ListMarker
isDisabled={query.disabled}
toggleDisabled={handleToggleDisableQuery}
labelName={query.queryName}
index={index}
isAvailableToDisable={isAvailableToDisable}
/>
{queryVariant === 'dropdown' ? (
<DataSourceDropdown
onChange={handleChangeDataSource}
value={query.dataSource}
/>
) : (
<FilterLabel label={transformToUpperCase(query.dataSource)} />
)}
</Col>
<Col span={24}>
<OperatorsSelect
value={query.aggregateOperator || currentListOfOperators[0]}
onChange={handleChangeOperator}
operators={currentListOfOperators}
style={{ minWidth: 104, marginRight: '0.75rem' }}
/>
<AggregatorFilter
onChange={handleChangeAggregatorAttribute}
query={query}
/>
</Col>
</Row>
);
}

View File

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

View File

@ -1,17 +0,0 @@
import { SelectProps } from 'antd';
import { DataSource } from 'types/common/queryBuilder';
type StaticLabel = { variant: 'static'; dataSource: DataSource };
export type DropdownLabel = {
variant: 'dropdown';
onChange: (value: DataSource) => void;
} & Omit<SelectProps, 'onChange'>;
export type QueryLabelProps = StaticLabel | DropdownLabel;
export function isLabelDropdown(
label: QueryLabelProps,
): label is DropdownLabel {
return label.variant === 'dropdown';
}

View File

@ -1,19 +0,0 @@
import { Select } from 'antd';
import styled, { css } from 'styled-components';
// ** Types
import { DropdownLabel } from './QueryLabel.interfaces';
const LabelStyle = css`
width: fit-content;
min-width: 5.75rem;
`;
export const StyledSingleLabel = styled(Select)`
pointer-events: none;
${LabelStyle}
`;
export const StyledDropdownLabel = styled(Select)<DropdownLabel>`
${LabelStyle}
`;

View File

@ -1,49 +0,0 @@
import { Select } from 'antd';
import React from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
// ** Types
import { isLabelDropdown, QueryLabelProps } from './QueryLabel.interfaces';
// ** Styles
import { StyledSingleLabel } from './QueryLabel.styled';
const { Option } = Select;
const dataSourceMap = [DataSource.LOGS, DataSource.METRICS, DataSource.TRACES];
export function QueryLabel(props: QueryLabelProps): JSX.Element {
const isDropdown = isLabelDropdown(props);
if (!isDropdown) {
const { dataSource } = props;
return (
<StyledSingleLabel
defaultValue={dataSource}
showArrow={false}
dropdownStyle={{ display: 'none' }}
>
<Option value={dataSource}>{dataSource}</Option>
</StyledSingleLabel>
);
}
const { onChange } = props;
const dataSourceOptions: SelectOption<
DataSource,
string
>[] = dataSourceMap.map((source) => ({
label: source.charAt(0).toUpperCase() + source.slice(1),
value: source,
}));
return (
<Select
defaultValue={dataSourceOptions[0].value}
options={dataSourceOptions}
onChange={onChange}
/>
);
}

View File

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

View File

@ -1,2 +1,5 @@
export { DataSourceDropdown } from './DataSourceDropdown';
export { FilterLabel } from './FilterLabel';
export { Formula } from './Formula';
export { ListMarker } from './ListMarker';
export { QueryLabel } from './QueryLabel';
export { Query } from './Query';

View File

@ -0,0 +1,7 @@
import { AutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQueryForm } from 'types/api/queryBuilder/queryBuilderData';
export type AgregatorFilterProps = {
onChange: (value: AutocompleteData) => void;
query: IBuilderQueryForm;
};

View File

@ -0,0 +1,84 @@
// ** Components
import { AutoComplete, Spin } from 'antd';
// ** Api
import { getAggregateAttribute } from 'api/queryBuilder/getAggregateAttribute';
import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix';
import React, { useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { SelectOption } from 'types/common/select';
import { transformToUpperCase } from 'utils/transformToUpperCase';
// ** Types
import { AgregatorFilterProps } from './AggregatorFilter.intefaces';
export function AggregatorFilter({
onChange,
query,
}: AgregatorFilterProps): JSX.Element {
const [searchText, setSearchText] = useState<string>('');
const { data, isFetching } = useQuery(
[
'GET_AGGREGATE_ATTRIBUTE',
searchText,
query.aggregateOperator,
query.dataSource,
],
async () =>
getAggregateAttribute({
aggregateOperator: query.aggregateOperator,
dataSource: query.dataSource,
searchText,
}),
{ enabled: !!query.aggregateOperator && !!query.dataSource },
);
const handleSearchAttribute = (searchText: string): void => {
setSearchText(searchText);
};
const optionsData: SelectOption<string, string>[] =
data?.payload?.attributeKeys?.map((item) => ({
label: transformStringWithPrefix({
str: item.key,
prefix: item.type || '',
condition: !item.isColumn,
}),
value: item.key,
})) || [];
const handleChangeAttribute = (value: string): void => {
const currentAttributeObj = data?.payload?.attributeKeys?.find(
(item) => item.key === value,
) || { key: value, type: null, dataType: null, isColumn: null };
onChange(currentAttributeObj);
};
const value = useMemo(
() =>
transformStringWithPrefix({
str: query.aggregateAttribute.key,
prefix: query.aggregateAttribute.type || '',
condition: !query.aggregateAttribute.isColumn,
}),
[query],
);
return (
<AutoComplete
showSearch
placeholder={`${transformToUpperCase(
query.dataSource,
)} Name (Start typing to get suggestions)`}
style={{ flex: 1, minWidth: 200 }}
showArrow={false}
filterOption={false}
onSearch={handleSearchAttribute}
notFoundContent={isFetching ? <Spin size="small" /> : null}
options={optionsData}
value={value}
onChange={handleChangeAttribute}
/>
);
}

View File

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

View File

@ -0,0 +1,7 @@
import { SelectProps } from 'antd';
export type OperatorsSelectProps = Omit<SelectProps, 'onChange' | 'value'> & {
operators: string[];
onChange: (value: string) => void;
value: string;
};

View File

@ -0,0 +1,32 @@
import { Select } from 'antd';
import React from 'react';
// ** Types
import { SelectOption } from 'types/common/select';
// ** Helpers
import { transformToUpperCase } from 'utils/transformToUpperCase';
import { OperatorsSelectProps } from './OperatorsSelect.interfaces';
export function OperatorsSelect({
operators,
value,
onChange,
...props
}: OperatorsSelectProps): JSX.Element {
const operatorsOptions: SelectOption<string, string>[] = operators.map(
(operator) => ({
label: transformToUpperCase(operator),
value: operator,
}),
);
return (
<Select
options={operatorsOptions}
value={value}
onChange={onChange}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);
}

View File

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

View File

@ -0,0 +1,2 @@
export { AggregatorFilter } from './AggregatorFilter';
export { OperatorsSelect } from './OperatorsSelect';

View File

@ -29,7 +29,6 @@ function SideNav(): JSX.Element {
const [collapsed, setCollapsed] = useState<boolean>(
getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true',
);
const { search } = useLocation();
const { currentVersion, latestVersion, isCurrentVersionError } = useSelector<
AppState,
AppReducer
@ -48,15 +47,11 @@ function SideNav(): JSX.Element {
const onClickHandler = useCallback(
(to: string) => {
const queryParams = new URLSearchParams(search);
const url = queryParams.toString();
if (pathname !== to) {
history.push(`${to}?${url}`);
history.push(`${to}`);
}
},
[pathname, search],
[pathname],
);
const onClickSlackHandler = (): void => {

View File

@ -178,7 +178,7 @@ function Duration(): JSX.Element {
if (value === undefined) {
return <div />;
}
return <div>{`${getMs(value?.toString())}ms`}</div>;
return <div>{`${value?.toString()}ms`}</div>;
}, []);
return (

View File

@ -1,8 +1,6 @@
import {
QueryBuilderContext,
QueryBuilderContextType,
} from 'providers/QueryBuilder';
import { QueryBuilderContext } from 'providers/QueryBuilder';
import { useContext } from 'react';
import { QueryBuilderContextType } from 'types/common/queryBuilder';
export function useQueryBuilder(): QueryBuilderContextType {
return useContext(QueryBuilderContext);

View File

@ -0,0 +1,16 @@
type TransformStringWithPrefixParams = {
str: string;
prefix: string;
condition: boolean;
};
export const transformStringWithPrefix = ({
str,
prefix,
condition,
}: TransformStringWithPrefixParams): string => {
if (prefix) {
return condition ? `${prefix}_${str}` : str;
}
return str;
};

View File

@ -1,3 +1,4 @@
// ** Helpers
import React, {
createContext,
PropsWithChildren,
@ -9,27 +10,21 @@ import React, {
// TODO: Rename Types on the Reusable type for any source
import {
IBuilderFormula,
IBuilderQuery,
IBuilderQueryForm,
} from 'types/api/queryBuilder/queryBuilderData';
export type QueryBuilderData = {
queryData: IBuilderQuery[];
queryFormulas: IBuilderFormula[];
};
// ** TODO: temporary types for context, fix it during development
export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData;
resetQueryBuilderData: () => void;
handleSetQueryData: (index: number, queryData: IBuilderQuery) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
};
import {
DataSource,
MetricAggregateOperator,
QueryBuilderContextType,
QueryBuilderData,
} from 'types/common/queryBuilder';
export const QueryBuilderContext = createContext<QueryBuilderContextType>({
queryBuilderData: { queryData: [], queryFormulas: [] },
resetQueryBuilderData: () => {},
handleSetQueryData: () => {},
handleSetFormulaData: () => {},
initQueryBuilderData: () => {},
});
const initialQueryBuilderData: QueryBuilderData = {
@ -44,21 +39,51 @@ export function QueryBuilderProvider({
// ** TODO: type the params which will be used for request of the data for query builder
const [queryBuilderData, setQueryBuilderData] = useState<QueryBuilderData>({
queryData: [],
// ** TODO temporary initial value for first query for testing first filters
queryData: [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
{
dataSource: DataSource.METRICS,
queryName: 'A',
aggregateOperator: Object.values(MetricAggregateOperator)[0],
aggregateAttribute: {
dataType: null,
key: '',
isColumn: null,
type: null,
},
},
],
queryFormulas: [],
});
// ** TODO: Also in the future need to add AddFormula and AddQuery and remove them.
// ** Method for resetting query builder data
const resetQueryBuilderData = useCallback((): void => {
setQueryBuilderData(initialQueryBuilderData);
}, []);
const handleSetQueryData = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(index: number, queryData: IBuilderQuery): void => {},
// ** Method for setupping query builder data
const initQueryBuilderData = useCallback(
(queryBuilderData: QueryBuilderData): void => {
setQueryBuilderData(queryBuilderData);
},
[],
);
const handleSetQueryData = useCallback(
(index: number, newQueryData: Partial<IBuilderQueryForm>): void => {
const updatedQueryBuilderData = queryBuilderData.queryData.map((item, idx) =>
index === idx ? { ...item, ...newQueryData } : item,
);
setQueryBuilderData((prevState) => ({
...prevState,
queryData: updatedQueryBuilderData,
}));
},
[queryBuilderData],
);
const handleSetFormulaData = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(index: number, formulaData: IBuilderFormula): void => {},
@ -76,12 +101,14 @@ export function QueryBuilderProvider({
resetQueryBuilderData,
handleSetQueryData,
handleSetFormulaData,
initQueryBuilderData,
}),
[
queryBuilderData,
resetQueryBuilderData,
handleSetQueryData,
handleSetFormulaData,
initQueryBuilderData,
],
);

View File

@ -0,0 +1,7 @@
import { DataSource } from 'types/common/queryBuilder';
export interface IGetAggregateAttributePayload {
aggregateOperator: string;
dataSource: DataSource;
searchText: string;
}

View File

@ -0,0 +1,8 @@
import { DataSource } from 'types/common/queryBuilder';
export interface IGetAttributeKeysPayload {
aggregateOperator: string;
dataSource: DataSource;
searchText: string;
aggregateAttribute: string;
}

View File

@ -0,0 +1,10 @@
export interface AutocompleteData {
dataType: 'number' | 'string' | 'boolean' | null;
isColumn: boolean | null;
key: string;
type: 'tag' | 'resource' | null;
}
export interface IQueryAutocompleteResponse {
attributeKeys: AutocompleteData[];
}

View File

@ -1,22 +1,44 @@
import { EAggregateOperator, EReduceOperator } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { IQueryBuilderTagFilters } from '../dashboard/getAll';
export interface IBuilderQuery {
// TODO: add another list of operator depended from data source
aggregateOperator: EAggregateOperator;
disabled: boolean;
label: string;
legend: string;
attribute: string;
groupBy: string[];
tagFilters: IQueryBuilderTagFilters;
reduceTo?: EReduceOperator;
}
import { AutocompleteData } from './queryAutocompleteResponse';
// Type for Formula
export interface IBuilderFormula {
expression: string;
disabled: boolean;
label: string;
legend: string;
}
export interface TagFilterItem {
key: string;
// TODO: type it in the future
op: string;
value: string[];
}
export interface TagFilter {
items: TagFilterItem[];
// TODO: type it in the future
op: string;
}
// Type for query builder
export type IBuilderQuery = {
queryName: string;
dataSource: DataSource;
aggregateOperator: string;
aggregateAttribute: string;
tagFilters: TagFilter[];
groupBy: string[];
expression: string;
disabled: boolean;
having?: string;
limit?: number;
orderBy?: string[];
reduceTo?: string;
};
export type IBuilderQueryForm = Omit<IBuilderQuery, 'aggregateAttribute'> & {
aggregateAttribute: AutocompleteData;
};

View File

@ -1,5 +1,100 @@
import {
IBuilderFormula,
IBuilderQueryForm,
} from 'types/api/queryBuilder/queryBuilderData';
export enum DataSource {
METRICS = 'metrics',
TRACES = 'traces',
LOGS = 'logs',
}
export enum MetricAggregateOperator {
NOOP = 'noop',
COUNT = 'count',
COUNT_DISTINCT = 'count_distinct',
SUM = 'sum',
AVG = 'avg',
MAX = 'max',
MIN = 'min',
P05 = 'p05',
P10 = 'p10',
P20 = 'p20',
P25 = 'p25',
P50 = 'p50',
P75 = 'p75',
P90 = 'p90',
P95 = 'p95',
P99 = 'p99',
RATE = 'rate',
SUM_RATE = 'sum_rate',
AVG_RATE = 'avg_rate',
MAX_RATE = 'max_rate',
MIN_RATE = 'min_rate',
RATE_SUM = 'rate_sum',
RATE_AVG = 'rate_avg',
RATE_MIN = 'rate_min',
RATE_MAX = 'rate_max',
HIST_QUANTILE_50 = 'hist_quantile_50',
HIST_QUANTILE_75 = 'hist_quantile_75',
HIST_QUANTILE_90 = 'hist_quantile_90',
HIST_QUANTILE_95 = 'hist_quantile_95',
HIST_QUANTILE_99 = 'hist_quantile_99',
}
export enum TracesAggregatorOperator {
NOOP = 'noop',
COUNT = 'count',
COUNT_DISTINCT = 'count_distinct',
SUM = 'sum',
AVG = 'avg',
MAX = 'max',
MIN = 'min',
P05 = 'p05',
P10 = 'p10',
P20 = 'p20',
P25 = 'p25',
P50 = 'p50',
P75 = 'p75',
P90 = 'p90',
P95 = 'p95',
P99 = 'p99',
RATE = 'rate',
}
export enum LogsAggregatorOperator {
NOOP = 'noop',
COUNT = 'count',
COUNT_DISTINCT = 'count_distinct',
SUM = 'sum',
AVG = 'avg',
MAX = 'max',
MIN = 'min',
P05 = 'p05',
P10 = 'p10',
P20 = 'p20',
P25 = 'p25',
P50 = 'p50',
P75 = 'p75',
P90 = 'p90',
P95 = 'p95',
P99 = 'p99',
RATE = 'rate',
}
export type QueryBuilderData = {
queryData: IBuilderQueryForm[];
queryFormulas: IBuilderFormula[];
};
// ** TODO: temporary types for context, fix it during development
export type QueryBuilderContextType = {
queryBuilderData: QueryBuilderData;
resetQueryBuilderData: () => void;
handleSetQueryData: (
index: number,
queryData: Partial<IBuilderQueryForm>,
) => void;
handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void;
initQueryBuilderData: (queryBuilderData: QueryBuilderData) => void;
};

View File

@ -0,0 +1,2 @@
export const transformToUpperCase = (str: string): string =>
str.charAt(0).toUpperCase() + str.slice(1);