chore: update query builder to support spatial aggregations and functions (#4569)

This commit is contained in:
Yunus M 2024-03-01 14:51:50 +05:30 committed by GitHub
parent 97fdba0fae
commit 1a62a13aea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
108 changed files with 2145 additions and 552 deletions

View File

@ -25,5 +25,5 @@
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.", "dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.", "dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"your_graph_build_with": "Your graph built with", "your_graph_build_with": "Your graph built with",
"dashboar_ok_confirm": "query will be saved. Press OK to confirm." "dashboard_ok_confirm": "query will be saved. Press OK to confirm."
} }

View File

@ -28,5 +28,5 @@
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.", "dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.", "dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"your_graph_build_with": "Your graph built with", "your_graph_build_with": "Your graph built with",
"dashboar_ok_confirm": "query will be saved. Press OK to confirm." "dashboard_ok_confirm": "query will be saved. Press OK to confirm."
} }

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { ApiV3Instance as axios } from 'api'; import { ApiV3Instance, ApiV4Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api'; import { ErrorResponse, SuccessResponse } from 'types/api';
@ -9,10 +9,23 @@ import {
export const getMetricsQueryRange = async ( export const getMetricsQueryRange = async (
props: QueryRangePayload, props: QueryRangePayload,
version: string,
signal: AbortSignal, signal: AbortSignal,
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => { ): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
try { try {
const response = await axios.post('/query_range', props, { signal }); if (version && version === 'v4') {
const response = await ApiV4Instance.post('/query_range', props, { signal });
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
}
const response = await ApiV3Instance.post('/query_range', props, { signal });
return { return {
statusCode: 200, statusCode: 200,

View File

@ -72,8 +72,6 @@ export default function LogsFormatOptionsMenu({
setAddNewColumn(!addNewColumn); setAddNewColumn(!addNewColumn);
}; };
// console.log('optionsMenuConfig', config);
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => { const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
if ( if (
maxLinesPerRow && maxLinesPerRow &&
@ -221,8 +219,6 @@ export default function LogsFormatOptionsMenu({
className="column-name" className="column-name"
key={value} key={value}
onClick={(eve): void => { onClick={(eve): void => {
console.log('coluimn name', label, value);
eve.stopPropagation(); eve.stopPropagation();
if (addColumn && addColumn?.onSelect) { if (addColumn && addColumn?.onSelect) {

View File

@ -13,3 +13,5 @@ export const SIGNOZ_UPGRADE_PLAN_URL =
'https://upgrade.signoz.io/upgrade-from-app'; 'https://upgrade.signoz.io/upgrade-from-app';
export const DASHBOARD_TIME_IN_DURATION = 'refreshInterval'; export const DASHBOARD_TIME_IN_DURATION = 'refreshInterval';
export const DEFAULT_ENTITY_VERSION = 'v3';

View File

@ -36,6 +36,11 @@ import { v4 as uuid } from 'uuid';
import { import {
logsAggregateOperatorOptions, logsAggregateOperatorOptions,
metricAggregateOperatorOptions, metricAggregateOperatorOptions,
metricsGaugeAggregateOperatorOptions,
metricsGaugeSpaceAggregateOperatorOptions,
metricsHistogramSpaceAggregateOperatorOptions,
metricsSumAggregateOperatorOptions,
metricsSumSpaceAggregateOperatorOptions,
tracesAggregateOperatorOptions, tracesAggregateOperatorOptions,
} from './queryBuilderOperators'; } from './queryBuilderOperators';
@ -74,6 +79,18 @@ export const mapOfOperators = {
traces: tracesAggregateOperatorOptions, traces: tracesAggregateOperatorOptions,
}; };
export const metricsOperatorsByType = {
Sum: metricsSumAggregateOperatorOptions,
Gauge: metricsGaugeAggregateOperatorOptions,
};
export const metricsSpaceAggregationOperatorsByType = {
Sum: metricsSumSpaceAggregateOperatorOptions,
Gauge: metricsGaugeSpaceAggregateOperatorOptions,
Histogram: metricsHistogramSpaceAggregateOperatorOptions,
ExponentialHistogram: metricsHistogramSpaceAggregateOperatorOptions,
};
export const mapOfQueryFilters: Record<DataSource, QueryAdditionalFilter[]> = { export const mapOfQueryFilters: Record<DataSource, QueryAdditionalFilter[]> = {
metrics: [ metrics: [
// eslint-disable-next-line sonarjs/no-duplicate-string // eslint-disable-next-line sonarjs/no-duplicate-string
@ -148,6 +165,9 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }), queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: MetricAggregateOperator.COUNT, aggregateOperator: MetricAggregateOperator.COUNT,
aggregateAttribute: initialAutocompleteData, aggregateAttribute: initialAutocompleteData,
timeAggregation: MetricAggregateOperator.RATE,
spaceAggregation: MetricAggregateOperator.SUM,
functions: [],
filters: { items: [], op: 'AND' }, filters: { items: [], op: 'AND' },
expression: createNewBuilderItemName({ expression: createNewBuilderItemName({
existNames: [], existNames: [],
@ -160,7 +180,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
orderBy: [], orderBy: [],
groupBy: [], groupBy: [],
legend: '', legend: '',
reduceTo: 'sum', reduceTo: 'avg',
}; };
const initialQueryBuilderFormLogsValues: IBuilderQuery = { const initialQueryBuilderFormLogsValues: IBuilderQuery = {
@ -268,6 +288,14 @@ export enum PANEL_TYPES {
EMPTY_WIDGET = 'EMPTY_WIDGET', EMPTY_WIDGET = 'EMPTY_WIDGET',
} }
// eslint-disable-next-line @typescript-eslint/naming-convention
export enum ATTRIBUTE_TYPES {
SUM = 'Sum',
GAUGE = 'Gauge',
HISTOGRAM = 'Histogram',
EXPONENTIAL_HISTOGRAM = 'ExponentialHistogram',
}
export type IQueryBuilderState = 'search'; export type IQueryBuilderState = 'search';
export const QUERY_BUILDER_SEARCH_VALUES = { export const QUERY_BUILDER_SEARCH_VALUES = {

View File

@ -302,3 +302,126 @@ export const logsAggregateOperatorOptions: SelectOption<string, string>[] = [
label: 'Rate_max', label: 'Rate_max',
}, },
]; ];
export const metricsSumAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.RATE,
label: 'Rate',
},
{
value: MetricAggregateOperator.INCREASE,
label: 'Increase',
},
];
export const metricsGaugeAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.LATEST,
label: 'Latest',
},
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
{
value: MetricAggregateOperator.COUNT,
label: 'Count',
},
{
value: MetricAggregateOperator.COUNT_DISTINCT,
label: 'Count Distinct',
},
];
export const metricsSumSpaceAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
];
export const metricsGaugeSpaceAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.SUM,
label: 'Sum',
},
{
value: MetricAggregateOperator.AVG,
label: 'Avg',
},
{
value: MetricAggregateOperator.MIN,
label: 'Min',
},
{
value: MetricAggregateOperator.MAX,
label: 'Max',
},
];
export const metricsHistogramSpaceAggregateOperatorOptions: SelectOption<
string,
string
>[] = [
{
value: MetricAggregateOperator.P50,
label: 'P50',
},
{
value: MetricAggregateOperator.P75,
label: 'P75',
},
{
value: MetricAggregateOperator.P90,
label: 'P90',
},
{
value: MetricAggregateOperator.P95,
label: 'P95',
},
{
value: MetricAggregateOperator.P99,
label: 'P99',
},
];
export const metricsEmptyTimeAggregateOperatorOptions: SelectOption<
string,
string
>[] = [];

View File

@ -0,0 +1,137 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { QueryFunctionsTypes } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
export const queryFunctionOptions: SelectOption<string, string>[] = [
{
value: QueryFunctionsTypes.CUTOFF_MIN,
label: 'Cut Off Min',
},
{
value: QueryFunctionsTypes.CUTOFF_MAX,
label: 'Cut Off Max',
},
{
value: QueryFunctionsTypes.CLAMP_MIN,
label: 'Clamp Min',
},
{
value: QueryFunctionsTypes.CLAMP_MAX,
label: 'Clamp Max',
},
{
value: QueryFunctionsTypes.ABSOLUTE,
label: 'Absolute',
},
{
value: QueryFunctionsTypes.LOG_2,
label: 'Log2',
},
{
value: QueryFunctionsTypes.LOG_10,
label: 'Log10',
},
{
value: QueryFunctionsTypes.CUMULATIVE_SUM,
label: 'Cumulative Sum',
},
{
value: QueryFunctionsTypes.EWMA_3,
label: 'EWMA 3',
},
{
value: QueryFunctionsTypes.EWMA_5,
label: 'EWMA 5',
},
{
value: QueryFunctionsTypes.EWMA_7,
label: 'EWMA 7',
},
{
value: QueryFunctionsTypes.MEDIAN_3,
label: 'Median 3',
},
{
value: QueryFunctionsTypes.MEDIAN_5,
label: 'Median 5',
},
{
value: QueryFunctionsTypes.MEDIAN_7,
label: 'Median 7',
},
{
value: QueryFunctionsTypes.TIME_SHIFT,
label: 'Time Shift',
},
];
interface QueryFunctionConfigType {
[key: string]: {
showInput: boolean;
inputType?: string;
placeholder?: string;
};
}
export const queryFunctionsTypesConfig: QueryFunctionConfigType = {
cutOffMin: {
showInput: true,
inputType: 'text',
placeholder: 'Threshold',
},
cutOffMax: {
showInput: true,
inputType: 'text',
placeholder: 'Threshold',
},
clampMin: {
showInput: true,
inputType: 'text',
placeholder: 'Threshold',
},
clampMax: {
showInput: true,
inputType: 'text',
placeholder: 'Threshold',
},
absolute: {
showInput: false,
},
log2: {
showInput: false,
},
log10: {
showInput: false,
},
cumSum: {
showInput: false,
},
ewma3: {
showInput: true,
inputType: 'text',
placeholder: 'Alpha',
},
ewma5: {
showInput: true,
inputType: 'text',
placeholder: 'Alpha',
},
ewma7: {
showInput: true,
inputType: 'text',
placeholder: 'Alpha',
},
median3: {
showInput: false,
},
median5: {
showInput: false,
},
median7: {
showInput: false,
},
timeShift: {
showInput: true,
inputType: 'text',
},
};

View File

@ -0,0 +1,17 @@
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
const userOS = getUserOperatingSystem();
export const DashboardShortcuts = {
SaveChanges: 's+meta',
DiscardChanges: 'd+meta',
};
export const DashboardShortcutsName = {
SaveChanges: `${userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'}+s`,
};
export const DashboardShortcutsDescription = {
SaveChanges: 'Save Changes',
DiscardChanges: 'Discard Changes',
};

View File

@ -0,0 +1,17 @@
import { getUserOperatingSystem, UserOperatingSystem } from 'utils/getUserOS';
const userOS = getUserOperatingSystem();
export const QBShortcuts = {
StageAndRunQuery: 'enter+meta',
};
export const QBShortcutsName = {
StageAndRunQuery: `${
userOS === UserOperatingSystem.MACOS ? 'cmd' : 'ctrl'
}+enter`,
};
export const QBShortcutsDescription = {
StageAndRunQuery: 'Stage and Run the query',
};

View File

@ -25,6 +25,7 @@ const defaultAnnotations = {
export const alertDefaults: AlertDef = { export const alertDefaults: AlertDef = {
alertType: AlertTypes.METRICS_BASED_ALERT, alertType: AlertTypes.METRICS_BASED_ALERT,
version: 'v4',
condition: { condition: {
compositeQuery: { compositeQuery: {
builderQueries: { builderQueries: {

View File

@ -2,6 +2,7 @@ import { Form, Row } from 'antd';
import FormAlertRules from 'container/FormAlertRules'; import FormAlertRules from 'container/FormAlertRules';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
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';
@ -20,6 +21,10 @@ function CreateRules(): JSX.Element {
AlertTypes.METRICS_BASED_ALERT, AlertTypes.METRICS_BASED_ALERT,
); );
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const version = queryParams.get('version');
const compositeQuery = useGetCompositeQueryParam(); const compositeQuery = useGetCompositeQueryParam();
const [formInstance] = Form.useForm(); const [formInstance] = Form.useForm();
@ -37,7 +42,10 @@ function CreateRules(): JSX.Element {
setInitValues(exceptionAlertDefaults); setInitValues(exceptionAlertDefaults);
break; break;
default: default:
setInitValues(alertDefaults); setInitValues({
...alertDefaults,
version: version || 'v3',
});
} }
}; };
@ -52,6 +60,7 @@ function CreateRules(): JSX.Element {
if (alertType) { if (alertType) {
onSelectType(alertType); onSelectType(alertType);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [compositeQuery]); }, [compositeQuery]);
if (!initValues) { if (!initValues) {

View File

@ -1,5 +1,6 @@
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import GridPanelSwitch from 'container/GridPanelSwitch'; import GridPanelSwitch from 'container/GridPanelSwitch';
import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories'; import { getFormatNameByOptionId } from 'container/NewWidget/RightContainer/alertFomatCategories';
@ -39,6 +40,7 @@ export interface ChartPreviewProps {
yAxisUnit: string; yAxisUnit: string;
} }
// eslint-disable-next-line sonarjs/cognitive-complexity
function ChartPreview({ function ChartPreview({
name, name,
query, query,
@ -94,6 +96,7 @@ function ChartPreview({
allowSelectedIntervalForStepGen, allowSelectedIntervalForStepGen,
}, },
}, },
alertDef?.version || DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
'chartPreview', 'chartPreview',

View File

@ -3,13 +3,16 @@ import './QuerySection.styles.scss';
import { Button, Tabs, Tooltip } from 'antd'; import { Button, Tabs, Tooltip } from 'antd';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts'; import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
import { QueryBuilder } from 'container/QueryBuilder'; import { QueryBuilder } from 'container/QueryBuilder';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { Atom, Play, Terminal } from 'lucide-react'; import { Atom, Play, Terminal } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes'; import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
@ -22,6 +25,7 @@ function QuerySection({
setQueryCategory, setQueryCategory,
alertType, alertType,
runQuery, runQuery,
alertDef,
panelType, panelType,
}: QuerySectionProps): JSX.Element { }: QuerySectionProps): JSX.Element {
// init namespace for translations // init namespace for translations
@ -50,6 +54,10 @@ function QuerySection({
queryVariant: 'static', queryVariant: 'static',
initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType], initialDataSource: ALERTS_DATA_SOURCE_MAP[alertType],
}} }}
showFunctions={
alertType === AlertTypes.METRICS_BASED_ALERT && alertDef.version === 'v4'
}
version={alertDef.version || 'v3'}
/> />
); );
@ -112,6 +120,17 @@ function QuerySection({
[], [],
); );
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
useEffect(() => {
registerShortcut(QBShortcuts.StageAndRunQuery, runQuery);
return (): void => {
deregisterShortcut(QBShortcuts.StageAndRunQuery);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [runQuery]);
const renderTabs = (typ: AlertTypes): JSX.Element | null => { const renderTabs = (typ: AlertTypes): JSX.Element | null => {
switch (typ) { switch (typ) {
case AlertTypes.TRACES_BASED_ALERT: case AlertTypes.TRACES_BASED_ALERT:
@ -197,6 +216,7 @@ interface QuerySectionProps {
setQueryCategory: (n: EQueryType) => void; setQueryCategory: (n: EQueryType) => void;
alertType: AlertTypes; alertType: AlertTypes;
runQuery: VoidFunction; runQuery: VoidFunction;
alertDef: AlertDef;
panelType: PANEL_TYPES; panelType: PANEL_TYPES;
} }

View File

@ -304,7 +304,7 @@ function FormAlertRules({
panelType, panelType,
]); ]);
const isAlertAvialable = useIsFeatureDisabled( const isAlertAvailable = useIsFeatureDisabled(
FeatureKeys.QUERY_BUILDER_ALERTS, FeatureKeys.QUERY_BUILDER_ALERTS,
); );
@ -458,8 +458,8 @@ function FormAlertRules({
const isAlertNameMissing = !formInstance.getFieldValue('alert'); const isAlertNameMissing = !formInstance.getFieldValue('alert');
const isAlertAvialableToSave = const isAlertAvailableToSave =
isAlertAvialable && isAlertAvailable &&
currentQuery.queryType === EQueryType.QUERY_BUILDER && currentQuery.queryType === EQueryType.QUERY_BUILDER &&
alertType !== AlertTypes.METRICS_BASED_ALERT; alertType !== AlertTypes.METRICS_BASED_ALERT;
@ -509,6 +509,7 @@ function FormAlertRules({
setQueryCategory={onQueryCategoryChange} setQueryCategory={onQueryCategoryChange}
alertType={alertType || AlertTypes.METRICS_BASED_ALERT} alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
runQuery={handleRunQuery} runQuery={handleRunQuery}
alertDef={alertDef}
panelType={panelType || PANEL_TYPES.TIME_SERIES} panelType={panelType || PANEL_TYPES.TIME_SERIES}
/> />
@ -521,7 +522,7 @@ function FormAlertRules({
{renderBasicInfo()} {renderBasicInfo()}
<ButtonContainer> <ButtonContainer>
<Tooltip title={isAlertAvialableToSave ? MESSAGE.ALERT : ''}> <Tooltip title={isAlertAvailableToSave ? MESSAGE.ALERT : ''}>
<ActionButton <ActionButton
loading={loading || false} loading={loading || false}
type="primary" type="primary"
@ -529,7 +530,7 @@ function FormAlertRules({
icon={<SaveOutlined />} icon={<SaveOutlined />}
disabled={ disabled={
isAlertNameMissing || isAlertNameMissing ||
isAlertAvialableToSave || isAlertAvailableToSave ||
!isChannelConfigurationValid !isChannelConfigurationValid
} }
> >

View File

@ -6,6 +6,7 @@ import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types'; import { ToggleGraphProps } from 'components/Graph/types';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown'; import TimePreference from 'components/TimePreferenceDropDown';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import GridPanelSwitch from 'container/GridPanelSwitch'; import GridPanelSwitch from 'container/GridPanelSwitch';
import { import {
@ -96,6 +97,7 @@ function FullView({
globalSelectedInterval: globalSelectedTime, globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables), variables: getDashboardVariables(selectedDashboard?.data.variables),
}, },
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
{ {
queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`, queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
enabled: !isDependedDataLoaded && widget.panelTypes !== PANEL_TYPES.LIST, // Internally both the list view panel has it's own query range api call, so we don't need to call it again enabled: !isDependedDataLoaded && widget.panelTypes !== PANEL_TYPES.LIST, // Internally both the list view panel has it's own query range api call, so we don't need to call it again

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
@ -39,6 +40,7 @@ function GridCardGraph({
threshold, threshold,
variables, variables,
fillSpans = false, fillSpans = false,
version,
}: GridCardGraphProps): JSX.Element { }: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>(); const [errorMessage, setErrorMessage] = useState<string>();
@ -132,6 +134,7 @@ function GridCardGraph({
globalSelectedInterval, globalSelectedInterval,
variables: getDashboardVariables(variables), variables: getDashboardVariables(variables),
}, },
version || DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
maxTime, maxTime,
@ -253,6 +256,7 @@ GridCardGraph.defaultProps = {
isQueryEnabled: true, isQueryEnabled: true,
threshold: undefined, threshold: undefined,
headerMenuList: [MenuItemKeys.View], headerMenuList: [MenuItemKeys.View],
version: 'v3',
}; };
export default memo(GridCardGraph); export default memo(GridCardGraph);

View File

@ -43,6 +43,7 @@ export interface GridCardGraphProps {
isQueryEnabled: boolean; isQueryEnabled: boolean;
variables?: Dashboard['data']['variables']; variables?: Dashboard['data']['variables'];
fillSpans?: boolean; fillSpans?: boolean;
version?: string;
} }
export interface GetGraphVisibilityStateOnLegendClickProps { export interface GetGraphVisibilityStateOnLegendClickProps {

View File

@ -1,6 +1,6 @@
.fullscreen-grid-container { .fullscreen-grid-container {
overflow: auto; overflow: auto;
margin-top: 1rem; margin: 8px -8px;
.react-grid-layout { .react-grid-layout {
border: none !important; border: none !important;

View File

@ -1,6 +1,7 @@
import './GridCardLayout.styles.scss'; import './GridCardLayout.styles.scss';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Tooltip } from 'antd';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { themeColors } from 'constants/theme'; import { themeColors } from 'constants/theme';
@ -144,17 +145,19 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
return ( return (
<> <>
<ButtonContainer> <ButtonContainer>
<Button <Tooltip title="Open in Full Screen">
loading={updateDashboardMutation.isLoading} <Button
onClick={handle.enter} className="periscope-btn"
icon={<FullscreenIcon size={16} />} loading={updateDashboardMutation.isLoading}
disabled={updateDashboardMutation.isLoading} onClick={handle.enter}
> icon={<FullscreenIcon size={16} />}
{t('dashboard:full_view')} disabled={updateDashboardMutation.isLoading}
</Button> />
</Tooltip>
{!isDashboardLocked && addPanelPermission && ( {!isDashboardLocked && addPanelPermission && (
<Button <Button
className="periscope-btn"
onClick={onAddPanelHandler} onClick={onAddPanelHandler}
icon={<PlusOutlined />} icon={<PlusOutlined />}
data-testid="add-panel" data-testid="add-panel"
@ -201,6 +204,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
headerMenuList={widgetActions} headerMenuList={widgetActions}
variables={variables} variables={variables}
fillSpans={currentWidget?.fillSpans} fillSpans={currentWidget?.fillSpans}
version={selectedDashboard?.data?.version}
/> />
</Card> </Card>
</CardContainer> </CardContainer>

View File

@ -80,7 +80,6 @@ export const ReactGridLayout = styled(ReactGridLayoutComponent)`
export const ButtonContainer = styled(Space)` export const ButtonContainer = styled(Space)`
display: flex; display: flex;
justify-content: end; justify-content: end;
margin-top: 1rem;
`; `;
export const Button = styled(ButtonComponent)` export const Button = styled(ButtonComponent)`

View File

@ -1,4 +1,5 @@
import { ToggleGraphProps } from 'components/Graph/types'; import { ToggleGraphProps } from 'components/Graph/types';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { getComponentForPanelType } from 'constants/panelTypes'; import { getComponentForPanelType } from 'constants/panelTypes';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config'; import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
@ -50,11 +51,13 @@ const GridPanelSwitch = forwardRef<
? { ? {
selectedLogsFields: selectedLogFields || [], selectedLogsFields: selectedLogFields || [],
query, query,
version: DEFAULT_ENTITY_VERSION, // As we don't support for Metrics, defaulting to v3
selectedTime, selectedTime,
} }
: { : {
selectedTracesFields: selectedTracesFields || [], selectedTracesFields: selectedTracesFields || [],
query, query,
version: DEFAULT_ENTITY_VERSION, // As we don't support for Metrics, defaulting to v3
selectedTime, selectedTime,
}, },
[PANEL_TYPES.TRACE]: null, [PANEL_TYPES.TRACE]: null,

View File

@ -109,7 +109,6 @@ function DashboardsList(): JSX.Element {
width: 30, width: 30,
key: DynamicColumnsKey.CreatedAt, key: DynamicColumnsKey.CreatedAt,
sorter: (a: Data, b: Data): number => { sorter: (a: Data, b: Data): number => {
console.log({ a });
const prev = new Date(a.createdAt).getTime(); const prev = new Date(a.createdAt).getTime();
const next = new Date(b.createdAt).getTime(); const next = new Date(b.createdAt).getTime();
@ -211,6 +210,7 @@ function DashboardsList(): JSX.Element {
ns: 'dashboard', ns: 'dashboard',
}), }),
uploadedGrafana: false, uploadedGrafana: false,
version: 'v4',
}); });
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -304,6 +304,7 @@ function DashboardsList(): JSX.Element {
loading={isFilteringDashboards} loading={isFilteringDashboards}
style={{ marginBottom: 16, marginTop: 16 }} style={{ marginBottom: 16, marginTop: 16 }}
defaultValue={searchString} defaultValue={searchString}
autoFocus
/> />
</Col> </Col>

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { LIVE_TAIL_GRAPH_INTERVAL } from 'constants/liveTail'; import { LIVE_TAIL_GRAPH_INTERVAL } from 'constants/liveTail';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import LogsExplorerChart from 'container/LogsExplorerChart'; import LogsExplorerChart from 'container/LogsExplorerChart';
@ -41,6 +42,7 @@ function LiveLogsListChart({
const { data, isFetching } = useGetExplorerQueryRange( const { data, isFetching } = useGetExplorerQueryRange(
listChartQuery, listChartQuery,
PANEL_TYPES.TIME_SERIES, PANEL_TYPES.TIME_SERIES,
DEFAULT_ENTITY_VERSION,
{ {
enabled: isConnectionOpen, enabled: isConnectionOpen,
refetchInterval: LIVE_TAIL_GRAPH_INTERVAL, refetchInterval: LIVE_TAIL_GRAPH_INTERVAL,

View File

@ -62,6 +62,7 @@ function LogExplorerQuerySection({
index: 0, index: 0,
query, query,
filterConfigs, filterConfigs,
entityVersion: '',
}); });
const renderOrderBy = useCallback( const renderOrderBy = useCallback(
@ -103,6 +104,7 @@ function LogExplorerQuerySection({
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }} config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents} queryComponents={queryComponents}
version="v3" // setting this to v3 as we this is rendered in logs explorer
/> />
)} )}
</> </>

View File

@ -2,6 +2,7 @@ import './LogsContextList.styles.scss';
import RawLogView from 'components/Logs/RawLogView'; import RawLogView from 'components/Logs/RawLogView';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config'; import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange'; import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
@ -105,6 +106,7 @@ function LogsContextList({
const { isError, isFetching } = useGetExplorerQueryRange( const { isError, isFetching } = useGetExplorerQueryRange(
requestData, requestData,
PANEL_TYPES.LIST, PANEL_TYPES.LIST,
DEFAULT_ENTITY_VERSION,
{ {
keepPreviousData: true, keepPreviousData: true,
enabled: !!requestData, enabled: !!requestData,

View File

@ -3,6 +3,7 @@ import './LogsExplorerViews.styles.scss';
import { Button } from 'antd'; import { Button } from 'antd';
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu'; import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes'; import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
@ -189,13 +190,19 @@ function LogsExplorerViews({
data: listChartData, data: listChartData,
isFetching: isFetchingListChartData, isFetching: isFetchingListChartData,
isLoading: isLoadingListChartData, isLoading: isLoadingListChartData,
} = useGetExplorerQueryRange(listChartQuery, PANEL_TYPES.TIME_SERIES, { } = useGetExplorerQueryRange(
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST, listChartQuery,
}); PANEL_TYPES.TIME_SERIES,
DEFAULT_ENTITY_VERSION,
{
enabled: !!listChartQuery && panelType === PANEL_TYPES.LIST,
},
);
const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange( const { data, isLoading, isFetching, isError } = useGetExplorerQueryRange(
requestData, requestData,
panelType, panelType,
DEFAULT_ENTITY_VERSION,
{ {
keepPreviousData: true, keepPreviousData: true,
enabled: !isLimit && !!requestData, enabled: !isLimit && !!requestData,

View File

@ -1,80 +1,88 @@
.logs-table { .logs-table {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
.resize-table { .resize-table {
height: calc(92% - 5px); height: calc(100% - 40px);
overflow: scroll; overflow: scroll;
overflow-x: hidden;
.ant-table-wrapper .ant-table-tbody >tr >td {
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px;
font-family: Inter;
.ant-typography {
background-color: transparent;
color: var(--bg-vanilla-100);
margin-bottom: 0;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 18px;
font-family: Inter;
}
padding: 14px 8px;
border: none;
cursor: pointer;
}
.ant-table-wrapper .ant-table-header { &::-webkit-scrollbar {
border-bottom: 0.5px solid var(--bg-slate-400); width: 0.2rem;
} height: 0.2rem;
}
.ant-table-wrapper .ant-table-thead > tr > th { .ant-table-wrapper .ant-table-tbody > tr > td {
font-family: Inter; font-size: 13px;
color: var(--bg-vanilla-100); font-style: normal;
background-color: transparent; font-weight: 400;
border: none; line-height: 18px;
font-size: 14px; font-family: Inter;
font-style: normal; .ant-typography {
font-weight: 600; background-color: transparent;
line-height: 22px; color: var(--bg-vanilla-100);
letter-spacing: 0.5px; margin-bottom: 0;
padding: 8px; font-size: 13px;
} font-style: normal;
font-weight: 400;
line-height: 18px;
font-family: Inter;
}
padding: 14px 8px;
border: none;
cursor: pointer;
}
.ant-table-wrapper .ant-table-thead > tr > th::before { .ant-table-wrapper .ant-table-header {
display: none; border-bottom: 0.5px solid var(--bg-slate-400);
} }
}
.controller { .ant-table-wrapper .ant-table-thead > tr > th {
position: absolute; font-family: Inter;
bottom: 5px; color: var(--bg-vanilla-100);
right: 10px; background-color: transparent;
} border: none;
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 22px;
letter-spacing: 0.5px;
padding: 8px;
}
.ant-table-wrapper .ant-table-thead > tr > th::before {
display: none;
}
}
.controller {
display: flex;
align-items: center;
height: 40px;
justify-content: end;
padding: 0 8px;
}
} }
.lightMode { .lightMode {
.logs-table { .logs-table {
.resize-table { .resize-table {
.ant-table-wrapper .ant-table-tbody >tr >td { .ant-table-wrapper .ant-table-tbody > tr > td {
background-color: var(--bg-vanilla-100); background-color: var(--bg-vanilla-100);
.ant-typography { .ant-typography {
color: var(--bg-ink-500); color: var(--bg-ink-500);
} }
} }
.ant-table-wrapper .ant-table-thead > tr > th { .ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-100); background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500); color: var(--bg-ink-500);
} }
.ant-table-wrapper .ant-table-header { .ant-table-wrapper .ant-table-header {
border-bottom: 0.5px solid var(--bg-vanilla-400); border-bottom: 0.5px solid var(--bg-vanilla-400);
} }
} }
} }
} }

View File

@ -4,6 +4,7 @@ import { Table } from 'antd';
import LogDetail from 'components/LogDetail'; import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants'; import { VIEW_TYPES } from 'components/LogDetail/constants';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder'; import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import Controls from 'container/Controls'; import Controls from 'container/Controls';
@ -106,6 +107,7 @@ function LogsPanelComponent({
selectedTime: selectedTime?.enum || 'GLOBAL_TIME', selectedTime: selectedTime?.enum || 'GLOBAL_TIME',
variables: getDashboardVariables(selectedDashboard?.data.variables), variables: getDashboardVariables(selectedDashboard?.data.variables),
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

@ -48,7 +48,7 @@ export const getQueryBuilderQueries = ({
items: filterItems[index], items: filterItems[index],
op: 'AND', op: 'AND',
}, },
reduceTo: 'sum', reduceTo: 'avg',
dataSource, dataSource,
}; };
@ -86,7 +86,7 @@ export const getQueryBuilderQuerieswithFormula = ({
aggregateAttribute: autocompleteData[index], aggregateAttribute: autocompleteData[index],
queryName: alphabet[index], queryName: alphabet[index],
expression: alphabet[index], expression: alphabet[index],
reduceTo: 'sum', reduceTo: 'avg',
filters: { filters: {
items: additionalItems[index], items: additionalItems[index],
op: 'AND', op: 'AND',

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { topOperationMetricsDownloadOptions } from 'container/MetricsApplication/constant'; import { topOperationMetricsDownloadOptions } from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
@ -67,6 +68,7 @@ function TopOperationMetrics(): JSX.Element {
globalSelectedInterval, globalSelectedInterval,
variables: {}, variables: {},
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
`GetMetricsQueryRange-${keyOperationWidget?.timePreferance}-${globalSelectedInterval}-${keyOperationWidget?.id}`, `GetMetricsQueryRange-${keyOperationWidget?.timePreferance}-${globalSelectedInterval}-${keyOperationWidget?.id}`,

View File

@ -2,12 +2,10 @@ import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams'; import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history'; import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { CSSProperties } from 'react';
import { LogsAggregatorOperator } from 'types/common/queryBuilder'; import { LogsAggregatorOperator } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@ -20,8 +18,6 @@ import menuItems from './menuItems';
import { Card, Container, Text } from './styles'; import { Card, Container, Text } from './styles';
function DashboardGraphSlider(): JSX.Element { function DashboardGraphSlider(): JSX.Element {
const isDarkMode = useIsDarkMode();
const { const {
handleToggleDashboardSlider, handleToggleDashboardSlider,
layouts, layouts,
@ -47,6 +43,7 @@ function DashboardGraphSlider(): JSX.Element {
description: data?.description || '', description: data?.description || '',
name: data?.name || '', name: data?.name || '',
tags: data?.tags || [], tags: data?.tags || [],
version: data?.version || 'v3',
layout: [ layout: [
{ {
i: id, i: id,
@ -146,13 +143,11 @@ function DashboardGraphSlider(): JSX.Element {
); );
}; };
const fillColor: CSSProperties['color'] = isDarkMode ? 'white' : 'black';
return ( return (
<Container> <Container>
{menuItems.map(({ name, Icon, display }) => ( {menuItems.map(({ name, icon, display }) => (
<Card onClick={onClickHandler(name)} id={name} key={name}> <Card onClick={onClickHandler(name)} id={name} key={name}>
<Icon fillColor={fillColor} /> {icon}
<Text>{display}</Text> <Text>{display}</Text>
</Card> </Card>
))} ))}

View File

@ -1,35 +0,0 @@
import BarIcon from 'assets/Dashboard/BarIcon';
import List from 'assets/Dashboard/List';
import TableIcon from 'assets/Dashboard/Table';
import TimeSeriesIcon from 'assets/Dashboard/TimeSeries';
import ValueIcon from 'assets/Dashboard/Value';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { CSSProperties } from 'react';
const Items: ItemsProps[] = [
{
name: PANEL_TYPES.TIME_SERIES,
Icon: TimeSeriesIcon,
display: 'Time Series',
},
{
name: PANEL_TYPES.VALUE,
Icon: ValueIcon,
display: 'Value',
},
{ name: PANEL_TYPES.TABLE, Icon: TableIcon, display: 'Table' },
{ name: PANEL_TYPES.LIST, Icon: List, display: 'List' },
{ name: PANEL_TYPES.BAR, Icon: BarIcon, display: 'Bar' },
];
interface ItemsProps {
name: PANEL_TYPES;
Icon: (props: IconProps) => JSX.Element;
display: string;
}
interface IconProps {
fillColor: CSSProperties['color'];
}
export default Items;

View File

@ -0,0 +1,39 @@
import { Color } from '@signozhq/design-tokens';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { BarChart3, LineChart, List, SigmaSquare, Table } from 'lucide-react';
const Items: ItemsProps[] = [
{
name: PANEL_TYPES.TIME_SERIES,
icon: <LineChart size={32} color={Color.BG_ROBIN_400} />,
display: 'Time Series',
},
{
name: PANEL_TYPES.VALUE,
icon: <SigmaSquare size={32} color={Color.BG_ROBIN_400} />,
display: 'Value',
},
{
name: PANEL_TYPES.TABLE,
icon: <Table size={32} color={Color.BG_ROBIN_400} />,
display: 'Table',
},
{
name: PANEL_TYPES.LIST,
icon: <List size={32} color={Color.BG_ROBIN_400} />,
display: 'List',
},
{
name: PANEL_TYPES.BAR,
icon: <BarChart3 size={32} color={Color.BG_ROBIN_400} />,
display: 'Bar',
},
];
interface ItemsProps {
name: PANEL_TYPES;
icon: JSX.Element;
display: string;
}
export default Items;

View File

@ -5,20 +5,33 @@ export const Container = styled.div`
display: flex; display: flex;
justify-content: right; justify-content: right;
gap: 8px; gap: 8px;
margin-bottom: 12px;
`; `;
export const Card = styled(CardComponent)` export const Card = styled(CardComponent)`
min-height: 10vh; min-height: 80px;
min-width: 120px; min-width: 120px;
overflow-y: auto; overflow-y: auto;
cursor: pointer; cursor: pointer;
transition: transform 0.2s;
.ant-card-body { .ant-card-body {
padding: 12px;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.ant-typography {
font-size: 12px;
font-weight: 600;
}
}
&:hover {
transform: scale(1.05);
border: 1px solid var(--bg-robin-400);
} }
`; `;

View File

@ -1,3 +1,16 @@
.dashboard-description-container {
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
border: 1px solid var(--bg-slate-400, #1d212d);
background: var(--bg-ink-400, #121317);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--bg-vanilla-400, #c0c1c3);
.ant-card-body {
padding: 12px 16px;
}
}
.dashboard-description { .dashboard-description {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; /* Show up to 2 lines */ -webkit-line-clamp: 2; /* Show up to 2 lines */
@ -5,3 +18,22 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
.dashboard-actions {
display: flex;
flex-direction: column;
gap: 8px;
}
.lightMode {
.dashboard-description-container {
box-shadow: none;
border: 1px solid var(--bg-vanilla-300);
background-color: rgb(250, 250, 250);
color: var(--bg-ink-300);
.ant-card-body {
padding: 12px 16px;
}
}
}

View File

@ -1,5 +1,5 @@
import { SettingOutlined } from '@ant-design/icons'; import { Button, Tooltip } from 'antd';
import { Button } from 'antd'; import { Cog } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import DashboardSettingsContent from '../DashboardSettings'; import DashboardSettingsContent from '../DashboardSettings';
@ -18,14 +18,16 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
return ( return (
<> <>
<Button <Tooltip title="Configure" placement="left">
type="dashed" <Button
onClick={showDrawer} className="periscope-btn"
style={{ width: '100%' }} onClick={showDrawer}
data-testid="show-drawer" style={{ width: '100%' }}
> data-testid="show-drawer"
<SettingOutlined /> Configure icon={<Cog size={16} />}
</Button> />
</Tooltip>
<DrawerContainer <DrawerContainer
title={drawerTitle} title={drawerTitle}
placement="right" placement="right"

View File

@ -1,11 +1,11 @@
import './Description.styles.scss'; import './Description.styles.scss';
import { LockFilled, ShareAltOutlined, UnlockFilled } from '@ant-design/icons'; import { LockFilled, UnlockFilled } from '@ant-design/icons';
import { Button, Card, Col, Row, Space, Tag, Tooltip, Typography } from 'antd'; import { Button, Card, Col, Row, Tag, Tooltip, Typography } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission'; import useComponentPermission from 'hooks/useComponentPermission';
import { Share2 } from 'lucide-react';
import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { DashboardData } from 'types/api/dashboard/getAll'; import { DashboardData } from 'types/api/dashboard/getAll';
@ -29,7 +29,6 @@ function DashboardDescription(): JSX.Element {
const [openDashboardJSON, setOpenDashboardJSON] = useState<boolean>(false); const [openDashboardJSON, setOpenDashboardJSON] = useState<boolean>(false);
const { t } = useTranslation('common');
const { user, role } = useSelector<AppState, AppReducer>((state) => state.app); const { user, role } = useSelector<AppState, AppReducer>((state) => state.app);
const [editDashboard] = useComponentPermission(['edit_dashboard'], role); const [editDashboard] = useComponentPermission(['edit_dashboard'], role);
@ -48,7 +47,7 @@ function DashboardDescription(): JSX.Element {
}; };
return ( return (
<Card style={{ marginTop: '1rem' }}> <Card className="dashboard-description-container">
<Row gutter={16}> <Row gutter={16}>
<Col flex={1} span={9}> <Col flex={1} span={9}>
<Typography.Title <Typography.Title
@ -80,12 +79,12 @@ function DashboardDescription(): JSX.Element {
</div> </div>
)} )}
</Col> </Col>
<Col span={12}> <Col span={14}>
<Row justify="end"> <Row justify="end">
<DashboardVariableSelection /> <DashboardVariableSelection />
</Row> </Row>
</Col> </Col>
<Col span={3} style={{ textAlign: 'right' }}> <Col span={1} style={{ textAlign: 'right' }}>
{selectedData && ( {selectedData && (
<ShareModal <ShareModal
isJSONModalVisible={openDashboardJSON} isJSONModalVisible={openDashboardJSON}
@ -94,18 +93,20 @@ function DashboardDescription(): JSX.Element {
/> />
)} )}
<Space direction="vertical"> <div className="dashboard-actions">
{!isDashboardLocked && editDashboard && ( {!isDashboardLocked && editDashboard && (
<SettingsDrawer drawerTitle={title} /> <SettingsDrawer drawerTitle={title} />
)} )}
<Button
style={{ width: '100%' }} <Tooltip title="Share" placement="left">
type="dashed" <Button
onClick={onToggleHandler} className="periscope-btn"
icon={<ShareAltOutlined />} style={{ width: '100%' }}
> onClick={onToggleHandler}
{t('share')} icon={<Share2 size={16} />}
</Button> />
</Tooltip>
{(isAuthor || role === USER_ROLES.ADMIN) && ( {(isAuthor || role === USER_ROLES.ADMIN) && (
<Tooltip <Tooltip
placement="left" placement="left"
@ -113,15 +114,13 @@ function DashboardDescription(): JSX.Element {
> >
<Button <Button
style={{ width: '100%' }} style={{ width: '100%' }}
type="dashed" className="periscope-btn"
onClick={handleLockDashboardToggle} onClick={handleLockDashboardToggle}
icon={isDashboardLocked ? <LockFilled /> : <UnlockFilled />} icon={isDashboardLocked ? <LockFilled /> : <UnlockFilled />}
> />
{isDashboardLocked ? 'Unlock' : 'Lock'}
</Button>
</Tooltip> </Tooltip>
)} )}
</Space> </div>
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@ -2,10 +2,13 @@ import './QuerySection.styles.scss';
import { Button, Tabs, Tooltip, Typography } from 'antd'; import { Button, Tabs, Tooltip, Typography } from 'antd';
import TextToolTip from 'components/TextToolTip'; import TextToolTip from 'components/TextToolTip';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
import { WidgetGraphProps } from 'container/NewWidget/types'; import { WidgetGraphProps } from 'container/NewWidget/types';
import { QueryBuilder } from 'container/QueryBuilder'; import { QueryBuilder } from 'container/QueryBuilder';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange'; import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
@ -18,7 +21,7 @@ import {
getPreviousWidgets, getPreviousWidgets,
getSelectedWidgetIndex, getSelectedWidgetIndex,
} from 'providers/Dashboard/util'; } from 'providers/Dashboard/util';
import { useCallback, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
@ -36,6 +39,7 @@ function QuerySection({
}: QueryProps): JSX.Element { }: QueryProps): JSX.Element {
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder(); const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery(); const urlQuery = useUrlQuery();
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>( const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@ -47,10 +51,13 @@ function QuerySection({
const { selectedDashboard, setSelectedDashboard } = useDashboard(); const { selectedDashboard, setSelectedDashboard } = useDashboard();
const getWidgetQueryRange = useGetWidgetQueryRange({ const getWidgetQueryRange = useGetWidgetQueryRange(
graphType: selectedGraph, {
selectedTime: selectedTime.enum, graphType: selectedGraph,
}); selectedTime: selectedTime.enum,
},
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
);
const { widgets } = selectedDashboard?.data || {}; const { widgets } = selectedDashboard?.data || {};
@ -149,6 +156,7 @@ function QuerySection({
<QueryBuilder <QueryBuilder
panelType={PANEL_TYPES.LIST} panelType={PANEL_TYPES.LIST}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
version={selectedDashboard?.data?.version || 'v3'}
isListViewPanel isListViewPanel
/> />
), ),
@ -167,7 +175,11 @@ function QuerySection({
), ),
tab: <Typography>Query Builder</Typography>, tab: <Typography>Query Builder</Typography>,
children: ( children: (
<QueryBuilder panelType={selectedGraph} filterConfigs={filterConfigs} /> <QueryBuilder
panelType={selectedGraph}
filterConfigs={filterConfigs}
version={selectedDashboard?.data?.version || 'v3'}
/>
), ),
}, },
{ {
@ -196,6 +208,15 @@ function QuerySection({
}, },
]; ];
useEffect(() => {
registerShortcut(QBShortcuts.StageAndRunQuery, handleRunQuery);
return (): void => {
deregisterShortcut(QBShortcuts.StageAndRunQuery);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleRunQuery]);
return ( return (
<div className="dashboard-navigation"> <div className="dashboard-navigation">
<Tabs <Tabs

View File

@ -1,5 +1,6 @@
import { Card, Typography } from 'antd'; import { Card, Typography } from 'antd';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { WidgetGraphProps } from 'container/NewWidget/types'; import { WidgetGraphProps } from 'container/NewWidget/types';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange'; import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
@ -32,10 +33,13 @@ function WidgetGraphContainer({
const selectedWidget = widgets.find((e) => e.id === widgetId); const selectedWidget = widgets.find((e) => e.id === widgetId);
const getWidgetQueryRange = useGetWidgetQueryRange({ const getWidgetQueryRange = useGetWidgetQueryRange(
graphType: getGraphType(selectedGraph), {
selectedTime: selectedTime.enum, graphType: getGraphType(selectedGraph),
}); selectedTime: selectedTime.enum,
},
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
);
if (getWidgetQueryRange.data && selectedGraph === PANEL_TYPES.BAR) { if (getWidgetQueryRange.data && selectedGraph === PANEL_TYPES.BAR) {
const sortedSeriesData = getSortedSeriesData( const sortedSeriesData = getSortedSeriesData(

View File

@ -1,4 +1,5 @@
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { Card } from 'container/GridCardLayout/styles'; import { Card } from 'container/GridCardLayout/styles';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange'; import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@ -34,10 +35,13 @@ function WidgetGraph({
const selectedWidget = widgets.find((e) => e.id === widgetId); const selectedWidget = widgets.find((e) => e.id === widgetId);
const getWidgetQueryRange = useGetWidgetQueryRange({ const getWidgetQueryRange = useGetWidgetQueryRange(
graphType: getGraphType(selectedGraph), {
selectedTime: selectedTime.enum, graphType: getGraphType(selectedGraph),
}); selectedTime: selectedTime.enum,
},
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
);
if (selectedWidget === undefined) { if (selectedWidget === undefined) {
return <Card $panelType={selectedGraph}>Invalid widget</Card>; return <Card $panelType={selectedGraph}>Invalid widget</Card>;

View File

@ -5,7 +5,9 @@ import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag'; import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@ -18,7 +20,7 @@ import {
getPreviousWidgets, getPreviousWidgets,
getSelectedWidgetIndex, getSelectedWidgetIndex,
} from 'providers/Dashboard/util'; } from 'providers/Dashboard/util';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom'; import { generatePath, useLocation, useParams } from 'react-router-dom';
@ -53,6 +55,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const { t } = useTranslation(['dashboard']); const { t } = useTranslation(['dashboard']);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const { currentQuery, stagedQuery } = useQueryBuilder(); const { currentQuery, stagedQuery } = useQueryBuilder();
const isQueryModified = useMemo( const isQueryModified = useMemo(
@ -311,6 +315,17 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
isNewTraceLogsAvailable, isNewTraceLogsAvailable,
]); ]);
useEffect(() => {
registerShortcut(DashboardShortcuts.SaveChanges, onSaveDashboard);
registerShortcut(DashboardShortcuts.DiscardChanges, onClickDiscardHandler);
return (): void => {
deregisterShortcut(DashboardShortcuts.SaveChanges);
deregisterShortcut(DashboardShortcuts.DiscardChanges);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onSaveDashboard]);
return ( return (
<Container> <Container>
<ButtonContainer> <ButtonContainer>
@ -414,7 +429,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
<Typography> <Typography>
{t('your_graph_build_with')}{' '} {t('your_graph_build_with')}{' '}
<QueryTypeTag queryType={currentQuery.queryType} /> <QueryTypeTag queryType={currentQuery.queryType} />
{t('dashboar_ok_confirm')} {t('dashboard_ok_confirm')}
</Typography> </Typography>
) : ( ) : (
<Typography>{t('dashboard_unsave_changes')} </Typography> <Typography>{t('dashboard_unsave_changes')} </Typography>

View File

@ -5,6 +5,7 @@ import {
CloseCircleTwoTone, CloseCircleTwoTone,
LoadingOutlined, LoadingOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import Header from 'container/OnboardingContainer/common/Header/Header'; import Header from 'container/OnboardingContainer/common/Header/Header';
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext'; import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
@ -72,6 +73,9 @@ export default function LogsConnectionStatus(): JSX.Element {
reduceTo: 'sum', reduceTo: 'sum',
offset: 0, offset: 0,
pageSize: 100, pageSize: 100,
timeAggregation: '',
spaceAggregation: '',
functions: [],
}, },
], ],
queryFormulas: [], queryFormulas: [],
@ -84,6 +88,7 @@ export default function LogsConnectionStatus(): JSX.Element {
const { data, isFetching, error, isError } = useGetExplorerQueryRange( const { data, isFetching, error, isError } = useGetExplorerQueryRange(
requestData, requestData,
PANEL_TYPES.LIST, PANEL_TYPES.LIST,
DEFAULT_ENTITY_VERSION,
{ {
keepPreviousData: true, keepPreviousData: true,
refetchInterval: pollingInterval, refetchInterval: pollingInterval,

View File

@ -1,5 +1,6 @@
import './styles.scss'; import './styles.scss';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { import {
initialFilters, initialFilters,
initialQueriesMap, initialQueriesMap,
@ -26,12 +27,15 @@ function LogsCountInInterval({
return q; return q;
}, [filter]); }, [filter]);
const result = useGetQueryRange({ const result = useGetQueryRange(
graphType: PANEL_TYPES.TABLE, {
query, graphType: PANEL_TYPES.TABLE,
selectedTime: 'GLOBAL_TIME', query,
globalSelectedInterval: timeInterval, selectedTime: 'GLOBAL_TIME',
}); globalSelectedInterval: timeInterval,
},
DEFAULT_ENTITY_VERSION,
);
if (!result.isFetched) { if (!result.isFetched) {
return null; return null;

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { import {
initialFilters, initialFilters,
initialQueriesMap, initialQueriesMap,
@ -42,12 +43,15 @@ const useSampleLogs = ({
return q; return q;
}, [count, filter]); }, [count, filter]);
const response = useGetQueryRange({ const response = useGetQueryRange(
graphType: PANEL_TYPES.LIST, {
query, graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME', query,
globalSelectedInterval: timeInterval, selectedTime: 'GLOBAL_TIME',
}); globalSelectedInterval: timeInterval,
},
DEFAULT_ENTITY_VERSION,
);
const { isFetching: isLoading, data } = response; const { isFetching: isLoading, data } = response;

View File

@ -27,4 +27,6 @@ export type QueryBuilderProps = {
filterConfigs?: Partial<FilterConfigs>; filterConfigs?: Partial<FilterConfigs>;
queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode }; queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode };
isListViewPanel?: boolean; isListViewPanel?: boolean;
showFunctions?: boolean;
version: string;
}; };

View File

@ -25,6 +25,8 @@ export const QueryBuilder = memo(function QueryBuilder({
filterConfigs = {}, filterConfigs = {},
queryComponents, queryComponents,
isListViewPanel = false, isListViewPanel = false,
showFunctions = false,
version,
}: QueryBuilderProps): JSX.Element { }: QueryBuilderProps): JSX.Element {
const { const {
currentQuery, currentQuery,
@ -170,6 +172,8 @@ export const QueryBuilder = memo(function QueryBuilder({
: listViewLogFilterConfigs : listViewLogFilterConfigs
} }
queryComponents={queryComponents} queryComponents={queryComponents}
showFunctions={showFunctions}
version={version}
isListViewPanel isListViewPanel
/> />
)} )}
@ -188,6 +192,8 @@ export const QueryBuilder = memo(function QueryBuilder({
query={query} query={query}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents} queryComponents={queryComponents}
showFunctions={showFunctions}
version={version}
/> />
</Col> </Col>
))} ))}

View File

@ -39,6 +39,7 @@ export function Formula({
query, query,
filterConfigs, filterConfigs,
formula, formula,
entityVersion: '',
}); });
const [isCollapse, setIsCollapsed] = useState(false); const [isCollapse, setIsCollapsed] = useState(false);
@ -146,6 +147,7 @@ export function Formula({
<Row gutter={[0, 15]}> <Row gutter={[0, 15]}>
<QBEntityOptions <QBEntityOptions
isCollapsed={isCollapse} isCollapsed={isCollapse}
showFunctions={false}
entityType="formula" entityType="formula"
entityData={formula} entityData={formula}
onToggleVisibility={handleToggleDisableFormula} onToggleVisibility={handleToggleDisableFormula}

View File

@ -2,11 +2,13 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 4px 0; margin: 4px 0;
width: 100%;
.left-col-items { .left-col-items {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
max-width: 100%;
.title { .title {
font-weight: 500; font-weight: 500;
@ -21,6 +23,7 @@
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
border-radius: 2px; border-radius: 2px;
margin-left: -51px; margin-left: -51px;
max-width: 100%;
border-radius: 2px; border-radius: 2px;
@ -63,6 +66,10 @@
color: var(--bg-sienna-400) !important; color: var(--bg-sienna-400) !important;
} }
} }
.options-group {
max-width: 100%;
}
} }
} }

View File

@ -1,80 +1,107 @@
import './QBEntityOptions.styles.scss'; import './QBEntityOptions.styles.scss';
import { Button, Col } from 'antd'; import { Button } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { ChevronDown, ChevronRight, Eye, EyeOff, Trash2 } from 'lucide-react'; import { ChevronDown, ChevronRight, Eye, EyeOff, Trash2 } from 'lucide-react';
import {
IBuilderQuery,
QueryFunctionProps,
} from 'types/api/queryBuilder/queryBuilderData';
import QueryFunctions from '../QueryFunctions/QueryFunctions';
interface QBEntityOptionsProps { interface QBEntityOptionsProps {
query?: IBuilderQuery;
isMetricsDataSource?: boolean;
showFunctions?: boolean;
isCollapsed: boolean; isCollapsed: boolean;
entityType: string; entityType: string;
entityData: any; entityData: any;
onDelete: () => void; onDelete: () => void;
onToggleVisibility: () => void; onToggleVisibility: () => void;
onCollapseEntity: () => void; onCollapseEntity: () => void;
onQueryFunctionsUpdates?: (functions: QueryFunctionProps[]) => void;
showDeleteButton: boolean; showDeleteButton: boolean;
isListViewPanel?: boolean; isListViewPanel?: boolean;
} }
export default function QBEntityOptions({ export default function QBEntityOptions({
query,
isMetricsDataSource,
isCollapsed, isCollapsed,
showFunctions,
entityType, entityType,
entityData, entityData,
onDelete, onDelete,
onToggleVisibility, onToggleVisibility,
onCollapseEntity, onCollapseEntity,
showDeleteButton, showDeleteButton,
isListViewPanel = false, onQueryFunctionsUpdates,
isListViewPanel,
}: QBEntityOptionsProps): JSX.Element { }: QBEntityOptionsProps): JSX.Element {
return ( return (
<Col span={24}> <div className="qb-entity-options">
<div className="qb-entity-options"> <div className="left-col-items">
<div className="left-col-items"> <div className="options periscope-btn-group">
<div className="options periscope-btn-group"> <Button.Group className="options-group">
<Button.Group> <Button
<Button value="search"
value="search" className="periscope-btn collapse"
className="periscope-btn collapse" onClick={onCollapseEntity}
onClick={onCollapseEntity} >
> {isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />}
{isCollapsed ? <ChevronRight size={16} /> : <ChevronDown size={16} />} </Button>
</Button> <Button
<Button value="query-builder"
value="query-builder" className="periscope-btn visibility-toggle"
className="periscope-btn visibility-toggle" onClick={onToggleVisibility}
onClick={onToggleVisibility} disabled={isListViewPanel}
disabled={isListViewPanel} >
> {entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />} </Button>
</Button>
<Button
className={cx(
'periscope-btn',
entityType === 'query' ? 'query-name' : 'formula-name',
)}
>
{entityData.queryName}
</Button>
</Button.Group>
</div>
{isCollapsed && ( <Button
<div className="title"> className={cx(
<span className="entityType"> {entityType} </span> -{' '} 'periscope-btn',
<span className="entityData"> {entityData.queryName} </span> entityType === 'query' ? 'query-name' : 'formula-name',
</div> )}
)} >
{entityData.queryName}
</Button>
{showFunctions &&
isMetricsDataSource &&
query &&
onQueryFunctionsUpdates && (
<QueryFunctions
queryFunctions={query.functions}
onChange={onQueryFunctionsUpdates}
/>
)}
</Button.Group>
</div> </div>
{showDeleteButton && ( {isCollapsed && (
<Button className="periscope-btn ghost" onClick={onDelete}> <div className="title">
<Trash2 size={14} /> <span className="entityType"> {entityType} </span> -{' '}
</Button> <span className="entityData"> {entityData.queryName} </span>
</div>
)} )}
</div> </div>
</Col>
{showDeleteButton && (
<Button className="periscope-btn ghost" onClick={onDelete}>
<Trash2 size={14} />
</Button>
)}
</div>
); );
} }
QBEntityOptions.defaultProps = { QBEntityOptions.defaultProps = {
isListViewPanel: false, isListViewPanel: false,
query: undefined,
isMetricsDataSource: false,
onQueryFunctionsUpdates: undefined,
showFunctions: false,
}; };

View File

@ -7,4 +7,6 @@ export type QueryProps = {
query: IBuilderQuery; query: IBuilderQuery;
queryVariant: 'static' | 'dropdown'; queryVariant: 'static' | 'dropdown';
isListViewPanel?: boolean; isListViewPanel?: boolean;
showFunctions?: boolean;
version: string;
} & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>; } & Pick<QueryBuilderProps, 'filterConfigs' | 'queryComponents'>;

View File

@ -3,7 +3,7 @@ import './Query.styles.scss';
import { Col, Input, Row } from 'antd'; import { Col, Input, Row } from 'antd';
// ** Constants // ** Constants
import { PANEL_TYPES } from 'constants/queryBuilder'; import { ATTRIBUTE_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
// ** Components // ** Components
import { import {
@ -38,9 +38,11 @@ import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { transformToUpperCase } from 'utils/transformToUpperCase'; import { transformToUpperCase } from 'utils/transformToUpperCase';
import QBEntityOptions from '../QBEntityOptions/QBEntityOptions'; import QBEntityOptions from '../QBEntityOptions/QBEntityOptions';
import SpaceAggregationOptions from '../SpaceAggregationOptions/SpaceAggregationOptions';
// ** Types // ** Types
import { QueryProps } from './Query.interfaces'; import { QueryProps } from './Query.interfaces';
// eslint-disable-next-line sonarjs/cognitive-complexity
export const Query = memo(function Query({ export const Query = memo(function Query({
index, index,
queryVariant, queryVariant,
@ -48,6 +50,8 @@ export const Query = memo(function Query({
filterConfigs, filterConfigs,
queryComponents, queryComponents,
isListViewPanel = false, isListViewPanel = false,
showFunctions = false,
version,
}: QueryProps): JSX.Element { }: QueryProps): JSX.Element {
const { panelType, currentQuery } = useQueryBuilder(); const { panelType, currentQuery } = useQueryBuilder();
const { pathname } = useLocation(); const { pathname } = useLocation();
@ -56,6 +60,7 @@ export const Query = memo(function Query({
const { const {
operators, operators,
spaceAggregationOptions,
isMetricsDataSource, isMetricsDataSource,
isTracePanelType, isTracePanelType,
listOfAdditionalFilters, listOfAdditionalFilters,
@ -63,8 +68,16 @@ export const Query = memo(function Query({
handleChangeQueryData, handleChangeQueryData,
handleChangeDataSource, handleChangeDataSource,
handleChangeOperator, handleChangeOperator,
handleSpaceAggregationChange,
handleDeleteQuery, handleDeleteQuery,
} = useQueryOperations({ index, query, filterConfigs, isListViewPanel }); handleQueryFunctionsUpdates,
} = useQueryOperations({
index,
query,
filterConfigs,
isListViewPanel,
entityVersion: version,
});
const handleChangeAggregateEvery = useCallback( const handleChangeAggregateEvery = useCallback(
(value: IBuilderQuery['stepInterval']) => { (value: IBuilderQuery['stepInterval']) => {
@ -192,13 +205,17 @@ export const Query = memo(function Query({
</Col> </Col>
</Row> </Row>
</Col> </Col>
<Col span={11}> <Col span={24}>
<Row gutter={[11, 5]}> <Row gutter={[11, 5]}>
<Col flex="5.93rem"> <Col flex="5.93rem">
<FilterLabel label="HAVING" /> <FilterLabel label="HAVING" />
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">
<HavingFilter onChange={handleChangeHavingFilter} query={query} /> <HavingFilter
entityVersion={version}
onChange={handleChangeHavingFilter}
query={query}
/>
</Col> </Col>
</Row> </Row>
</Col> </Col>
@ -225,7 +242,11 @@ export const Query = memo(function Query({
<FilterLabel label="HAVING" /> <FilterLabel label="HAVING" />
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">
<HavingFilter onChange={handleChangeHavingFilter} query={query} /> <HavingFilter
onChange={handleChangeHavingFilter}
entityVersion={version}
query={query}
/>
</Col> </Col>
</Row> </Row>
</Col> </Col>
@ -257,7 +278,11 @@ export const Query = memo(function Query({
<FilterLabel label="HAVING" /> <FilterLabel label="HAVING" />
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">
<HavingFilter onChange={handleChangeHavingFilter} query={query} /> <HavingFilter
entityVersion={version}
onChange={handleChangeHavingFilter}
query={query}
/>
</Col> </Col>
</Row> </Row>
</Col> </Col>
@ -279,23 +304,33 @@ export const Query = memo(function Query({
}, [ }, [
panelType, panelType,
query, query,
filterConfigs?.limit?.isHidden,
filterConfigs?.having?.isHidden,
handleChangeLimit, handleChangeLimit,
version,
handleChangeHavingFilter, handleChangeHavingFilter,
renderOrderByFilter, renderOrderByFilter,
renderAggregateEveryFilter, renderAggregateEveryFilter,
filterConfigs?.limit?.isHidden,
filterConfigs?.having?.isHidden,
]); ]);
const disableOperatorSelector =
!query?.aggregateAttribute.key || query?.aggregateAttribute.key === '';
const isVersionV4 = version && version === 'v4';
return ( return (
<Row gutter={[0, 12]}> <Row gutter={[0, 12]}>
<QBEntityOptions <QBEntityOptions
isMetricsDataSource={isMetricsDataSource}
showFunctions={(version && version === 'v4') || showFunctions || false}
isCollapsed={isCollapse} isCollapsed={isCollapse}
entityType="query" entityType="query"
entityData={query} entityData={query}
onToggleVisibility={handleToggleDisableQuery} onToggleVisibility={handleToggleDisableQuery}
onDelete={handleDeleteQuery} onDelete={handleDeleteQuery}
onCollapseEntity={handleToggleCollapsQuery} onCollapseEntity={handleToggleCollapsQuery}
query={query}
onQueryFunctionsUpdates={handleQueryFunctionsUpdates}
showDeleteButton={currentQuery.builder.queryData.length > 1} showDeleteButton={currentQuery.builder.queryData.length > 1}
isListViewPanel={isListViewPanel} isListViewPanel={isListViewPanel}
/> />
@ -322,23 +357,42 @@ export const Query = memo(function Query({
{isMetricsDataSource && ( {isMetricsDataSource && (
<Col span={12}> <Col span={12}>
<Row gutter={[11, 5]}> <Row gutter={[11, 5]}>
<Col flex="5.93rem"> {version && version === 'v3' && (
<OperatorsSelect <Col flex="5.93rem">
value={query.aggregateOperator} <OperatorsSelect
onChange={handleChangeOperator} value={query.aggregateOperator}
operators={operators} onChange={handleChangeOperator}
/> operators={operators}
</Col> />
</Col>
)}
<Col flex="auto"> <Col flex="auto">
<AggregatorFilter <AggregatorFilter
onChange={handleChangeAggregatorAttribute} onChange={handleChangeAggregatorAttribute}
query={query} query={query}
/> />
</Col> </Col>
{version &&
version === 'v4' &&
operators &&
Array.isArray(operators) &&
operators.length > 0 && (
<Col flex="5.93rem">
<OperatorsSelect
value={query.aggregateOperator}
onChange={handleChangeOperator}
operators={operators}
disabled={disableOperatorSelector}
/>
</Col>
)}
</Row> </Row>
</Col> </Col>
)} )}
<Col flex="1 1 20rem">
<Col flex="1 1 40rem">
<Row gutter={[11, 5]}> <Row gutter={[11, 5]}>
{isMetricsDataSource && ( {isMetricsDataSource && (
<Col> <Col>
@ -379,16 +433,40 @@ export const Query = memo(function Query({
</Col> </Col>
)} )}
{!isListViewPanel && ( {!isListViewPanel && (
<Col span={11} offset={isMetricsDataSource ? 0 : 2}> <Col span={24}>
<Row gutter={[11, 5]}> <Row gutter={[11, 5]}>
<Col flex="5.93rem"> <Col flex="5.93rem">
<FilterLabel {isVersionV4 && isMetricsDataSource ? (
label={panelType === PANEL_TYPES.VALUE ? 'Reduce to' : 'Group by'} <SpaceAggregationOptions
/> panelType={panelType}
key={`${panelType}${query.spaceAggregation}${query.timeAggregation}`}
aggregatorAttributeType={
query?.aggregateAttribute.type as ATTRIBUTE_TYPES
}
selectedValue={query.spaceAggregation}
disabled={disableOperatorSelector}
onSelect={handleSpaceAggregationChange}
operators={spaceAggregationOptions}
/>
) : (
<FilterLabel
label={panelType === PANEL_TYPES.VALUE ? 'Reduce to' : 'Group by'}
/>
)}
</Col> </Col>
<Col flex="1 1 12.5rem"> <Col flex="1 1 12.5rem">
{panelType === PANEL_TYPES.VALUE ? ( {panelType === PANEL_TYPES.VALUE ? (
<ReduceToFilter query={query} onChange={handleChangeReduceTo} /> <Row>
{isVersionV4 && isMetricsDataSource && (
<Col span={4}>
<FilterLabel label="Reduce to" />
</Col>
)}
<Col span={isVersionV4 && isMetricsDataSource ? 20 : 24}>
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
</Col>
</Row>
) : ( ) : (
<GroupByFilter <GroupByFilter
disabled={isMetricsDataSource && !query.aggregateAttribute.key} disabled={isMetricsDataSource && !query.aggregateAttribute.key}
@ -397,6 +475,20 @@ export const Query = memo(function Query({
/> />
)} )}
</Col> </Col>
{isVersionV4 && isMetricsDataSource && panelType === PANEL_TYPES.TABLE && (
<Col flex="1 1 12.5rem">
<Row>
<Col span={6}>
<FilterLabel label="Reduce to" />
</Col>
<Col span={18}>
<ReduceToFilter query={query} onChange={handleChangeReduceTo} />
</Col>
</Row>
</Col>
)}
</Row> </Row>
</Col> </Col>
)} )}

View File

@ -0,0 +1,89 @@
/* eslint-disable react/jsx-props-no-spreading */
import { Button, Flex, Input, Select } from 'antd';
import cx from 'classnames';
import {
queryFunctionOptions,
queryFunctionsTypesConfig,
} from 'constants/queryFunctionOptions';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { debounce, isNil } from 'lodash-es';
import { X } from 'lucide-react';
import { QueryFunctionProps } from 'types/api/queryBuilder/queryBuilderData';
interface FunctionProps {
funcData: QueryFunctionProps;
index: any;
handleUpdateFunctionArgs: any;
handleUpdateFunctionName: any;
handleDeleteFunction: any;
}
export default function Function({
funcData,
index,
handleUpdateFunctionArgs,
handleUpdateFunctionName,
handleDeleteFunction,
}: FunctionProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const { showInput } = queryFunctionsTypesConfig[funcData.name];
let functionValue;
const hasValue = !isNil(
funcData.args && funcData.args.length > 0 && funcData.args[0],
);
if (hasValue) {
// eslint-disable-next-line prefer-destructuring
functionValue = funcData.args[0];
}
const debouncedhandleUpdateFunctionArgs = debounce(
handleUpdateFunctionArgs,
500,
);
return (
<Flex className="query-function">
<Select
className={cx('query-function-name-selector', showInput ? 'showInput' : '')}
value={funcData.name}
style={{ minWidth: '100px' }}
onChange={(value): void => {
handleUpdateFunctionName(funcData, index, value);
}}
dropdownStyle={{
minWidth: 200,
borderRadius: '4px',
border: isDarkMode
? '1px solid var(--bg-slate-400)'
: '1px solid var(--bg-vanilla-300)',
boxShadow: `4px 10px 16px 2px rgba(0, 0, 0, 0.20)`,
}}
placement="bottomRight"
options={queryFunctionOptions}
/>
{showInput && (
<Input
className="query-function-value"
autoFocus
defaultValue={functionValue}
onChange={(event): void => {
debouncedhandleUpdateFunctionArgs(funcData, index, event.target.value);
}}
/>
)}
<Button
className="periscope-btn query-function-delete-btn"
onClick={(): void => {
handleDeleteFunction(funcData, index);
}}
>
<X size={12} />
</Button>
</Flex>
);
}

View File

@ -0,0 +1,151 @@
.query-functions-container {
display: flex;
margin: 0 12px;
justify-content: center;
align-items: center;
.function-btn,
.add-function-btn {
display: flex;
gap: 8px;
cursor: pointer;
border-radius: 3px !important;
}
.function-btn {
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important;
.function-icon {
height: 18px;
width: 18px;
}
}
.add-function-btn {
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
background-color: var(--bg-slate-500) !important;
opacity: 0.8;
&:disabled {
opacity: 0.4;
}
}
&.hasFunctions {
.function-btn {
border-top-right-radius: 3px !important;
border-bottom-right-radius: 3px !important;
margin-right: 8px;
}
.add-function-btn {
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
margin-left: 8px;
}
}
}
.query-functions-list {
display: flex;
gap: 8px;
.query-function {
position: relative;
&::before {
content: '';
height: 1px;
width: 8px;
position: absolute;
left: -8px;
top: 16px;
z-index: 0;
color: var(--bg-sakura-500);
background-color: var(--bg-sakura-500);
}
&::after {
content: '';
height: 1px;
width: 8px;
position: absolute;
right: -8px;
top: 16px;
z-index: 0;
color: var(--bg-sakura-500);
background-color: var(--bg-sakura-500);
}
.query-function-name-selector {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
.ant-select-selector {
border: none;
background: var(--bg-ink-200);
}
&.showInput {
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}
.query-function-value {
width: 55px;
border-left: 0;
background: var(--bg-ink-200);
border-radius: 0;
border: 1px solid transparent;
&:focus {
border-color: transparent !important;
}
}
.query-function-delete-btn {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border: none !important;
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important;
min-width: 24px !important;
}
}
}
.lightMode {
.query-functions-container {
.add-function-btn {
background-color: var(--bg-vanilla-100) !important;
}
}
.query-functions-list {
.query-function {
border: 1px solid var(--bg-vanilla-300);
.query-function-name-selector {
.ant-select-selector {
background: var(--bg-vanilla-100);
}
}
.query-function-value {
background: var(--bg-vanilla-100);
&:focus {
border-color: transparent !important;
}
}
}
}
}

View File

@ -0,0 +1,181 @@
import './QueryFunctions.styles.scss';
import { Button, Tooltip } from 'antd';
import cx from 'classnames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { cloneDeep, pullAt } from 'lodash-es';
import { Plus } from 'lucide-react';
import { useState } from 'react';
import { QueryFunctionProps } from 'types/api/queryBuilder/queryBuilderData';
import { QueryFunctionsTypes } from 'types/common/queryBuilder';
import Function from './Function';
const defaultFunctionStruct: QueryFunctionProps = {
name: QueryFunctionsTypes.CUTOFF_MIN,
args: [],
};
interface QueryFunctionsProps {
queryFunctions: QueryFunctionProps[];
onChange: (functions: QueryFunctionProps[]) => void;
}
// SVG component
function FunctionIcon({
fillColor = 'white',
className,
}: {
fillColor: string;
className: string;
}): JSX.Element {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M3 18.13C5.71436 18.13 6.8001 16.7728 6.8001 14.3299V8.62978C6.8001 5.91542 8.15728 4.15109 11.1431 4.55824"
stroke={fillColor}
strokeWidth="1.995"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3 10.2583H10.7359"
stroke={fillColor}
strokeWidth="1.995"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M22.0005 11.344L15.2146 18.1299"
stroke={fillColor}
strokeWidth="1.995"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.2146 11.344L22.0005 18.1299"
stroke={fillColor}
strokeWidth="1.995"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export default function QueryFunctions({
queryFunctions,
onChange,
}: QueryFunctionsProps): JSX.Element {
const [functions, setFunctions] = useState<QueryFunctionProps[]>(
queryFunctions,
);
const isDarkMode = useIsDarkMode();
const handleAddNewFunction = (): void => {
const updatedFunctionsArr = [
...functions,
{
...defaultFunctionStruct,
},
];
setFunctions(updatedFunctionsArr);
onChange(updatedFunctionsArr);
};
const handleDeleteFunction = (
queryFunction: QueryFunctionProps,
index: number,
): void => {
const clonedFunctions = cloneDeep(functions);
pullAt(clonedFunctions, index);
setFunctions(clonedFunctions);
onChange(clonedFunctions);
};
const handleUpdateFunctionName = (
func: QueryFunctionProps,
index: number,
value: string,
): void => {
const updateFunctions = cloneDeep(functions);
if (updateFunctions && updateFunctions.length > 0 && updateFunctions[index]) {
updateFunctions[index].name = value;
setFunctions(updateFunctions);
onChange(updateFunctions);
}
};
const handleUpdateFunctionArgs = (
func: QueryFunctionProps,
index: number,
value: string,
): void => {
const updateFunctions = cloneDeep(functions);
if (updateFunctions && updateFunctions.length > 0 && updateFunctions[index]) {
updateFunctions[index].args = [value];
setFunctions(updateFunctions);
onChange(updateFunctions);
}
};
return (
<div
className={cx(
'query-functions-container',
functions && functions.length > 0 ? 'hasFunctions' : '',
)}
>
<Button className="periscope-btn function-btn">
<FunctionIcon
className="function-icon"
fillColor={!isDarkMode ? '#0B0C0E' : 'white'}
/>
</Button>
<div className="query-functions-list">
{functions.map((func, index) => (
<Function
funcData={func}
index={index}
// eslint-disable-next-line react/no-array-index-key
key={index}
handleUpdateFunctionArgs={handleUpdateFunctionArgs}
handleUpdateFunctionName={handleUpdateFunctionName}
handleDeleteFunction={handleDeleteFunction}
/>
))}
</div>
<Tooltip
title={
functions && functions.length >= 3
? 'Functions are in early access. You can add a maximum of 3 function as of now.'
: ''
}
placement="right"
>
<Button
className="periscope-btn add-function-btn"
disabled={functions && functions.length >= 3}
onClick={handleAddNewFunction}
>
<Plus size={14} color={!isDarkMode ? '#0B0C0E' : 'white'} />
</Button>
</Tooltip>
</div>
);
}

View File

@ -0,0 +1,67 @@
import { Select } from 'antd';
import { ATTRIBUTE_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
import { useEffect, useState } from 'react';
import { MetricAggregateOperator } from 'types/common/queryBuilder';
interface SpaceAggregationOptionsProps {
panelType: PANEL_TYPES | null;
selectedValue: string | undefined;
aggregatorAttributeType: ATTRIBUTE_TYPES | null;
disabled: boolean;
onSelect: (value: string) => void;
operators: any[];
}
export default function SpaceAggregationOptions({
panelType,
selectedValue,
aggregatorAttributeType = ATTRIBUTE_TYPES.GAUGE,
disabled,
onSelect,
operators,
}: SpaceAggregationOptionsProps): JSX.Element {
const placeHolderText = panelType === PANEL_TYPES.VALUE ? 'Sum' : 'Sum By';
const [defaultValue, setDefaultValue] = useState(
selectedValue || placeHolderText,
);
useEffect(() => {
if (!selectedValue) {
if (
aggregatorAttributeType === ATTRIBUTE_TYPES.HISTOGRAM ||
aggregatorAttributeType === ATTRIBUTE_TYPES.EXPONENTIAL_HISTOGRAM
) {
setDefaultValue(MetricAggregateOperator.P90);
onSelect(MetricAggregateOperator.P90);
} else if (aggregatorAttributeType === ATTRIBUTE_TYPES.SUM) {
setDefaultValue(MetricAggregateOperator.SUM);
onSelect(MetricAggregateOperator.SUM);
} else if (aggregatorAttributeType === ATTRIBUTE_TYPES.GAUGE) {
setDefaultValue(MetricAggregateOperator.AVG);
onSelect(MetricAggregateOperator.AVG);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [aggregatorAttributeType]);
return (
<div
className="spaceAggregationOptionsContainer"
key={aggregatorAttributeType}
>
<Select
defaultValue={defaultValue}
style={{ minWidth: '5.625rem' }}
disabled={disabled}
onChange={onSelect}
>
{operators.map((operator) => (
<Select.Option key={operator.value} value={operator.value}>
{operator.label} {panelType !== PANEL_TYPES.VALUE ? ' By' : ''}
</Select.Option>
))}
</Select>
</div>
);
}

View File

@ -111,7 +111,7 @@ export const AggregatorFilter = memo(function AggregatorFilter({
debouncedValue, debouncedValue,
query.aggregateOperator, query.aggregateOperator,
query.dataSource, query.dataSource,
])?.payload.attributeKeys || [], ])?.payload?.attributeKeys || [],
[debouncedValue, query.aggregateOperator, query.dataSource, queryClient], [debouncedValue, query.aggregateOperator, query.dataSource, queryClient],
); );

View File

@ -1,6 +1,7 @@
import { Having, IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { Having, IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
export type HavingFilterProps = { export type HavingFilterProps = {
entityVersion: string;
query: IBuilderQuery; query: IBuilderQuery;
onChange: (having: Having[]) => void; onChange: (having: Having[]) => void;
}; };

View File

@ -22,6 +22,7 @@ import { getHavingObject, isValidHavingValue } from '../utils';
import { HavingFilterProps } from './HavingFilter.interfaces'; import { HavingFilterProps } from './HavingFilter.interfaces';
export function HavingFilter({ export function HavingFilter({
entityVersion,
query, query,
onChange, onChange,
}: HavingFilterProps): JSX.Element { }: HavingFilterProps): JSX.Element {
@ -48,10 +49,18 @@ export function HavingFilter({
[query], [query],
); );
const columnName = useMemo( const columnName = useMemo(() => {
() => `${query.aggregateOperator.toUpperCase()}(${aggregatorAttribute})`, if (
[query, aggregatorAttribute], query &&
); query.dataSource === DataSource.METRICS &&
query.spaceAggregation &&
entityVersion === 'v4'
) {
return `${query.spaceAggregation.toUpperCase()}(${aggregatorAttribute})`;
}
return `${query.aggregateOperator.toUpperCase()}(${aggregatorAttribute})`;
}, [query, aggregatorAttribute, entityVersion]);
const aggregatorOptions: SelectOption<string, string>[] = useMemo( const aggregatorOptions: SelectOption<string, string>[] = useMemo(
() => [{ label: columnName, value: columnName }], () => [{ label: columnName, value: columnName }],
@ -211,7 +220,7 @@ export function HavingFilter({
disabled={isMetricsDataSource && !query.aggregateAttribute.key} disabled={isMetricsDataSource && !query.aggregateAttribute.key}
style={{ width: '100%' }} style={{ width: '100%' }}
notFoundContent={currentFormValue.value.length === 0 ? undefined : null} notFoundContent={currentFormValue.value.length === 0 ? undefined : null}
placeholder="Count(operation) > 5" placeholder="GroupBy(operation) > 5"
onDeselect={handleDeselect} onDeselect={handleDeselect}
onChange={handleChange} onChange={handleChange}
onSelect={handleSelect} onSelect={handleSelect}

View File

@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
// Constants // Constants
import { import {
HAVING_OPERATORS, HAVING_OPERATORS,
@ -31,6 +32,7 @@ describe('Having filter behaviour', () => {
<HavingFilter <HavingFilter
query={initialQueryBuilderFormValuesMap.metrics} query={initialQueryBuilderFormValuesMap.metrics}
onChange={mockFn} onChange={mockFn}
entityVersion={DEFAULT_ENTITY_VERSION}
/>, />,
); );
@ -49,6 +51,7 @@ describe('Having filter behaviour', () => {
<HavingFilter <HavingFilter
query={initialQueryBuilderFormValuesMap.metrics} query={initialQueryBuilderFormValuesMap.metrics}
onChange={mockFn} onChange={mockFn}
entityVersion={DEFAULT_ENTITY_VERSION}
/>, />,
); );
@ -62,7 +65,11 @@ describe('Having filter behaviour', () => {
test('Is having filter is enable', () => { test('Is having filter is enable', () => {
const mockFn = jest.fn(); const mockFn = jest.fn();
const { unmount } = render( const { unmount } = render(
<HavingFilter query={valueWithAttributeAndOperator} onChange={mockFn} />, <HavingFilter
query={valueWithAttributeAndOperator}
onChange={mockFn}
entityVersion={DEFAULT_ENTITY_VERSION}
/>,
); );
const input = screen.getByRole('combobox'); const input = screen.getByRole('combobox');
@ -80,7 +87,11 @@ describe('Having filter behaviour', () => {
const optionTestTitle = 'havingOption'; const optionTestTitle = 'havingOption';
const { unmount } = render( const { unmount } = render(
<HavingFilter query={valueWithAttributeAndOperator} onChange={onChange} />, <HavingFilter
query={valueWithAttributeAndOperator}
onChange={onChange}
entityVersion={DEFAULT_ENTITY_VERSION}
/>,
); );
// get input // get input

View File

@ -1,9 +1,8 @@
import { import './QueryBuilderSearch.styles.scss';
SelectOptionContainer,
TagContainer, import { Tooltip } from 'antd';
TagLabel,
TagValue, import { TagContainer, TagLabel, TagValue } from './style';
} from './style';
import { getOptionType } from './utils'; import { getOptionType } from './utils';
function OptionRenderer({ function OptionRenderer({
@ -16,21 +15,25 @@ function OptionRenderer({
return ( return (
<span className="option"> <span className="option">
{optionType ? ( {optionType ? (
<SelectOptionContainer> <Tooltip title={`${value}`} placement="topLeft">
<div className="option-value">{value}</div> <div className="selectOptionContainer">
<div className="option-meta-data-container"> <div className="option-value">{value}</div>
<TagContainer> <div className="option-meta-data-container">
<TagLabel>Type: </TagLabel> <TagContainer>
<TagValue>{optionType}</TagValue> <TagLabel>Type: </TagLabel>
</TagContainer> <TagValue>{optionType}</TagValue>
<TagContainer> </TagContainer>
<TagLabel>Data type: </TagLabel> <TagContainer>
<TagValue>{dataType}</TagValue> <TagLabel>Data type: </TagLabel>
</TagContainer> <TagValue>{dataType}</TagValue>
</TagContainer>
</div>
</div> </div>
</SelectOptionContainer> </Tooltip>
) : ( ) : (
<span>{label}</span> <Tooltip title={label} placement="topLeft">
<span>{label}</span>
</Tooltip>
)} )}
</span> </span>
); );

View File

@ -1,3 +1,16 @@
.selectOptionContainer {
display: flex;
gap: 8px;
justify-content: space-between;
align-items: center;
overflow-x: auto;
&::-webkit-scrollbar {
width: 0.2rem;
height: 0.2rem;
}
}
.lightMode { .lightMode {
.query-builder-search { .query-builder-search {
.ant-select-dropdown { .ant-select-dropdown {

View File

@ -16,19 +16,11 @@ export const StyledCheckOutlined = styled(CheckOutlined)`
float: right; float: right;
`; `;
export const SelectOptionContainer = styled.div`
display: flex;
gap: 8px;
justify-content: space-between;
align-items: center;
overflow-x: auto;
`;
export const TagContainer = styled(Tag)` export const TagContainer = styled(Tag)`
&&& { &&& {
border-radius: 3px; border-radius: 3px;
padding: 0.3rem 0.3rem; padding: 0.1rem 0.2rem;
font-weight: 400; font-weight: 300;
font-size: 0.6rem; font-size: 0.6rem;
} }
`; `;

View File

@ -1,6 +1,7 @@
import { WarningFilled } from '@ant-design/icons'; import { WarningFilled } from '@ant-design/icons';
import { Flex, Typography } from 'antd'; import { Flex, Typography } from 'antd';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { MAX_RPS_LIMIT } from 'constants/global'; import { MAX_RPS_LIMIT } from 'constants/global';
import ResourceAttributesFilter from 'container/ResourceAttributesFilter'; import ResourceAttributesFilter from 'container/ResourceAttributesFilter';
import { useGetQueriesRange } from 'hooks/queryBuilder/useGetQueriesRange'; import { useGetQueriesRange } from 'hooks/queryBuilder/useGetQueriesRange';
@ -35,22 +36,26 @@ function ServiceMetricTable({
const { data: licenseData, isFetching } = useLicense(); const { data: licenseData, isFetching } = useLicense();
const isCloudUserVal = isCloudUser(); const isCloudUserVal = isCloudUser();
const queries = useGetQueriesRange(queryRangeRequestData, { const queries = useGetQueriesRange(
queryKey: [ queryRangeRequestData,
`GetMetricsQueryRange-${queryRangeRequestData[0].selectedTime}-${globalSelectedInterval}`, DEFAULT_ENTITY_VERSION,
maxTime, {
minTime, queryKey: [
globalSelectedInterval, `GetMetricsQueryRange-${queryRangeRequestData[0].selectedTime}-${globalSelectedInterval}`,
], maxTime,
keepPreviousData: true, minTime,
enabled: true, globalSelectedInterval,
refetchOnMount: false, ],
onError: (error) => { keepPreviousData: true,
notifications.error({ enabled: true,
message: error.message, refetchOnMount: false,
}); onError: (error) => {
notifications.error({
message: error.message,
});
},
}, },
}); );
const isLoading = queries.some((query) => query.isLoading); const isLoading = queries.some((query) => query.isLoading);
const services: ServicesList[] = useMemo( const services: ServicesList[] = useMemo(

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
@ -49,6 +50,7 @@ function TimeSeriesViewContainer({
dataSource, dataSource,
}, },
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

@ -49,13 +49,16 @@ export const getTraceToLogsQuery = (
limit: null, limit: null,
aggregateAttribute: initialAutocompleteData, aggregateAttribute: initialAutocompleteData,
aggregateOperator: LogsAggregatorOperator.NOOP, aggregateOperator: LogsAggregatorOperator.NOOP,
timeAggregation: '',
spaceAggregation: '',
functions: [],
expression: 'A', expression: 'A',
groupBy: [], groupBy: [],
having: [], having: [],
legend: '', legend: '',
orderBy: [], orderBy: [],
queryName: 'A', queryName: 'A',
reduceTo: 'min', reduceTo: 'avg',
stepInterval: getStep({ stepInterval: getStep({
start: minTime, start: minTime,
end: maxTime, end: maxTime,

View File

@ -1,4 +1,5 @@
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
@ -62,6 +63,7 @@ function ListView(): JSX.Element {
selectColumns: options?.selectColumns, selectColumns: options?.selectColumns,
}, },
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

@ -53,6 +53,7 @@ function QuerySection(): JSX.Element {
}} }}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents} queryComponents={queryComponents}
version="v3" // setting this to v3 as we this is rendered in logs explorer
actions={ actions={
<ButtonWrapper> <ButtonWrapper>
<Button onClick={handleRunQuery} type="primary"> <Button onClick={handleRunQuery} type="primary">

View File

@ -1,4 +1,5 @@
import { Space } from 'antd'; import { Space } from 'antd';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { QueryTable } from 'container/QueryTable'; import { QueryTable } from 'container/QueryTable';
@ -27,6 +28,7 @@ function TableView(): JSX.Element {
dataSource: 'traces', dataSource: 'traces',
}, },
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

@ -1,5 +1,6 @@
import { Typography } from 'antd'; import { Typography } from 'antd';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
@ -41,6 +42,7 @@ function TracesView(): JSX.Element {
pagination: paginationQueryData, pagination: paginationQueryData,
}, },
}, },
DEFAULT_ENTITY_VERSION,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

@ -1,65 +1,73 @@
.traces-table { .traces-table {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
.resize-table { .resize-table {
height: calc(90% - 5px); height: calc(100% - 40px);
overflow: scroll; overflow: scroll;
overflow-x: hidden;
.ant-table-wrapper .ant-table-tbody >tr >td {
border: none;
background-color: transparent;
color: var(--bg-vanilla-100);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
padding: 10px 8px;
font-family: Inter;
cursor: pointer;
}
.ant-table-wrapper .ant-table-thead > tr > th { &::-webkit-scrollbar {
font-family: Inter; width: 0.2rem;
color: var(--bg-vanilla-100); height: 0.2rem;
background-color: transparent; }
border: none;
border-bottom: 0.5px solid var(--bg-slate-400);
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 22px;
letter-spacing: 0.5px;
padding: 8px;
}
.ant-table-wrapper .ant-table-thead > tr > th::before { .ant-table-wrapper .ant-table-tbody > tr > td {
display: none; border: none;
} background-color: transparent;
} color: var(--bg-vanilla-100);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px;
padding: 10px 8px;
font-family: Inter;
cursor: pointer;
}
.controller { .ant-table-wrapper .ant-table-thead > tr > th {
position: absolute; font-family: Inter;
bottom: 5px; color: var(--bg-vanilla-100);
right: 10px; background-color: transparent;
} border: none;
border-bottom: 0.5px solid var(--bg-slate-400);
font-size: 14px;
font-style: normal;
font-weight: 600;
line-height: 22px;
letter-spacing: 0.5px;
padding: 8px;
}
.ant-table-wrapper .ant-table-thead > tr > th::before {
display: none;
}
}
.controller {
display: flex;
align-items: center;
height: 40px;
justify-content: end;
padding: 0 8px;
}
} }
.lightMode { .lightMode {
.traces-table { .traces-table {
.resize-table { .resize-table {
.ant-table-wrapper .ant-table-tbody >tr >td { .ant-table-wrapper .ant-table-tbody > tr > td {
background-color: var(--bg-vanilla-100); background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500); color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06); border-color: rgba(0, 0, 0, 0.06);
} }
.ant-table-wrapper .ant-table-thead > tr > th { .ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-300); background-color: var(--bg-vanilla-300);
color: var(--bg-ink-500); color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06); border-color: rgba(0, 0, 0, 0.06);
} }
} }
} }
} }

View File

@ -30,6 +30,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
function TracesTableComponent({ function TracesTableComponent({
selectedTracesFields, selectedTracesFields,
query, query,
version,
selectedTime, selectedTime,
}: TracesTableComponentProps): JSX.Element { }: TracesTableComponentProps): JSX.Element {
const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector< const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
@ -59,6 +60,7 @@ function TracesTableComponent({
}, },
variables: getDashboardVariables(selectedDashboard?.data.variables), variables: getDashboardVariables(selectedDashboard?.data.variables),
}, },
version,
{ {
queryKey: [ queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE, REACT_QUERY_KEY.GET_QUERY_RANGE,
@ -160,6 +162,7 @@ function TracesTableComponent({
export type TracesTableComponentProps = { export type TracesTableComponentProps = {
selectedTracesFields: Widgets['selectedTracesFields']; selectedTracesFields: Widgets['selectedTracesFields'];
query: Query; query: Query;
version: string;
selectedTime?: timePreferance; selectedTime?: timePreferance;
}; };

View File

@ -72,6 +72,9 @@ function KeyboardHotkeysProvider({
shortcutKey = shortcutKey + isAltKey + isShiftKey + isMetaKey; shortcutKey = shortcutKey + isAltKey + isShiftKey + isMetaKey;
if (shortcuts.current[shortcutKey]) { if (shortcuts.current[shortcutKey]) {
event.preventDefault();
event.stopImmediatePropagation();
shortcuts.current[shortcutKey](); shortcuts.current[shortcutKey]();
} }
}; };

View File

@ -1,5 +1,6 @@
import { getQueryRangeFormat } from 'api/dashboard/queryRangeFormat'; import { getQueryRangeFormat } from 'api/dashboard/queryRangeFormat';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
@ -45,7 +46,9 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => {
history.push( history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(updatedQuery), JSON.stringify(updatedQuery),
)}&${QueryParams.panelTypes}=${widget.panelTypes}`, )}&${QueryParams.panelTypes}=${widget.panelTypes}&version=${
selectedDashboard?.data.version || DEFAULT_ENTITY_VERSION
}`,
); );
}, },
onError: () => { onError: () => {
@ -59,6 +62,7 @@ const useCreateAlerts = (widget?: Widgets): VoidFunction => {
notifications, notifications,
queryRangeMutation, queryRangeMutation,
selectedDashboard?.data.variables, selectedDashboard?.data.variables,
selectedDashboard?.data.version,
widget, widget,
]); ]);
}; };

View File

@ -15,6 +15,7 @@ import { useQueryBuilder } from './useQueryBuilder';
export const useGetExplorerQueryRange = ( export const useGetExplorerQueryRange = (
requestData: Query | null, requestData: Query | null,
panelType: PANEL_TYPES | null, panelType: PANEL_TYPES | null,
version: string,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>, options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
params?: Record<string, unknown>, params?: Record<string, unknown>,
isDependentOnQB = true, isDependentOnQB = true,
@ -47,6 +48,7 @@ export const useGetExplorerQueryRange = (
query: requestData || initialQueriesMap.metrics, query: requestData || initialQueriesMap.metrics,
params, params,
}, },
version,
{ {
...options, ...options,
retry: false, retry: false,

View File

@ -15,6 +15,7 @@ import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
export const useGetQueriesRange = ( export const useGetQueriesRange = (
requestData: GetQueryResultsProps[], requestData: GetQueryResultsProps[],
version: string,
options: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>, options: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>[] => { ): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>[] => {
const queryKey = useMemo(() => { const queryKey = useMemo(() => {
@ -26,7 +27,7 @@ export const useGetQueriesRange = (
const queryData = requestData.map((request, index) => ({ const queryData = requestData.map((request, index) => ({
queryFn: async (): Promise<SuccessResponse<MetricRangePayloadProps>> => queryFn: async (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
GetMetricQueryRange(request), GetMetricQueryRange(request, version),
...options, ...options,
queryKey: [...queryKey, index] as QueryKey, queryKey: [...queryKey, index] as QueryKey,
})); }));

View File

@ -11,10 +11,15 @@ import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
type UseGetQueryRange = ( type UseGetQueryRange = (
requestData: GetQueryResultsProps, requestData: GetQueryResultsProps,
version: string,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>, options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
) => UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>; ) => UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error>;
export const useGetQueryRange: UseGetQueryRange = (requestData, options) => { export const useGetQueryRange: UseGetQueryRange = (
requestData,
version,
options,
) => {
const newRequestData: GetQueryResultsProps = useMemo( const newRequestData: GetQueryResultsProps = useMemo(
() => ({ () => ({
...requestData, ...requestData,
@ -39,7 +44,8 @@ export const useGetQueryRange: UseGetQueryRange = (requestData, options) => {
}, [options?.queryKey, newRequestData]); }, [options?.queryKey, newRequestData]);
return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({ return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
queryFn: async ({ signal }) => GetMetricQueryRange(newRequestData, signal), queryFn: async ({ signal }) =>
GetMetricQueryRange(requestData, version, signal),
...options, ...options,
queryKey, queryKey,
}); });

View File

@ -18,6 +18,7 @@ export const useGetWidgetQueryRange = (
graphType, graphType,
selectedTime, selectedTime,
}: Pick<GetQueryResultsProps, 'graphType' | 'selectedTime'>, }: Pick<GetQueryResultsProps, 'graphType' | 'selectedTime'>,
version: string,
options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>, options?: UseQueryOptions<SuccessResponse<MetricRangePayloadProps>, Error>,
): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => { ): UseQueryResult<SuccessResponse<MetricRangePayloadProps>, Error> => {
const { selectedTime: globalSelectedInterval } = useSelector< const { selectedTime: globalSelectedInterval } = useSelector<
@ -37,6 +38,7 @@ export const useGetWidgetQueryRange = (
query: stagedQuery || initialQueriesMap.metrics, query: stagedQuery || initialQueriesMap.metrics,
variables: getDashboardVariables(selectedDashboard?.data.variables), variables: getDashboardVariables(selectedDashboard?.data.variables),
}, },
version,
{ {
enabled: !!stagedQuery, enabled: !!stagedQuery,
retry: false, retry: false,

View File

@ -1,16 +1,23 @@
import { LEGEND } from 'constants/global'; import { LEGEND } from 'constants/global';
import { import {
ATTRIBUTE_TYPES,
initialAutocompleteData, initialAutocompleteData,
initialQueryBuilderFormValuesMap, initialQueryBuilderFormValuesMap,
mapOfFormulaToFilters, mapOfFormulaToFilters,
mapOfQueryFilters, mapOfQueryFilters,
PANEL_TYPES, PANEL_TYPES,
} from 'constants/queryBuilder'; } from 'constants/queryBuilder';
import {
metricsGaugeSpaceAggregateOperatorOptions,
metricsHistogramSpaceAggregateOperatorOptions,
metricsSumSpaceAggregateOperatorOptions,
} from 'constants/queryBuilderOperators';
import { import {
listViewInitialLogQuery, listViewInitialLogQuery,
listViewInitialTraceQuery, listViewInitialTraceQuery,
} from 'container/NewDashboard/ComponentsSlider/constants'; } from 'container/NewDashboard/ComponentsSlider/constants';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getMetricsOperatorsByAttributeType } from 'lib/newQueryBuilder/getMetricsOperatorsByAttributeType';
import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType'; import { getOperatorsBySourceAndPanelType } from 'lib/newQueryBuilder/getOperatorsBySourceAndPanelType';
import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator'; import { findDataTypeOfOperator } from 'lib/query/findDataTypeOfOperator';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
@ -18,13 +25,14 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
import { import {
IBuilderFormula, IBuilderFormula,
IBuilderQuery, IBuilderQuery,
QueryFunctionProps,
} from 'types/api/queryBuilder/queryBuilderData'; } from 'types/api/queryBuilder/queryBuilderData';
import { import {
HandleChangeFormulaData, HandleChangeFormulaData,
HandleChangeQueryData, HandleChangeQueryData,
UseQueryOperations, UseQueryOperations,
} from 'types/common/operations.types'; } from 'types/common/operations.types';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select'; import { SelectOption } from 'types/common/select';
import { getFormatedLegend } from 'utils/getFormatedLegend'; import { getFormatedLegend } from 'utils/getFormatedLegend';
@ -34,6 +42,7 @@ export const useQueryOperations: UseQueryOperations = ({
filterConfigs, filterConfigs,
formula, formula,
isListViewPanel = false, isListViewPanel = false,
entityVersion,
}) => { }) => {
const { const {
handleSetQueryData, handleSetQueryData,
@ -46,6 +55,9 @@ export const useQueryOperations: UseQueryOperations = ({
} = useQueryBuilder(); } = useQueryBuilder();
const [operators, setOperators] = useState<SelectOption<string, string>[]>([]); const [operators, setOperators] = useState<SelectOption<string, string>[]>([]);
const [spaceAggregationOptions, setSpaceAggregationOptions] = useState<
SelectOption<string, string>[]
>([]);
const { dataSource, aggregateOperator } = query; const { dataSource, aggregateOperator } = query;
@ -104,6 +116,7 @@ export const useQueryOperations: UseQueryOperations = ({
const newQuery: IBuilderQuery = { const newQuery: IBuilderQuery = {
...query, ...query,
aggregateOperator: value, aggregateOperator: value,
timeAggregation: value,
having: [], having: [],
limit: null, limit: null,
...(shouldResetAggregateAttribute ...(shouldResetAggregateAttribute
@ -116,6 +129,52 @@ export const useQueryOperations: UseQueryOperations = ({
[index, query, handleSetQueryData], [index, query, handleSetQueryData],
); );
const handleSpaceAggregationChange = useCallback(
(value: string): void => {
const newQuery: IBuilderQuery = {
...query,
spaceAggregation: value,
};
handleSetQueryData(index, newQuery);
},
[index, query, handleSetQueryData],
);
const handleMetricAggregateAtributeTypes = useCallback(
(aggregateAttribute: BaseAutocompleteData): any => {
const newOperators = getMetricsOperatorsByAttributeType({
dataSource: DataSource.METRICS,
panelType: panelType || PANEL_TYPES.TIME_SERIES,
aggregateAttributeType:
(aggregateAttribute.type as ATTRIBUTE_TYPES) || ATTRIBUTE_TYPES.GAUGE,
});
switch (aggregateAttribute.type) {
case ATTRIBUTE_TYPES.SUM:
setSpaceAggregationOptions(metricsSumSpaceAggregateOperatorOptions);
break;
case ATTRIBUTE_TYPES.GAUGE:
setSpaceAggregationOptions(metricsGaugeSpaceAggregateOperatorOptions);
break;
case ATTRIBUTE_TYPES.HISTOGRAM:
setSpaceAggregationOptions(metricsHistogramSpaceAggregateOperatorOptions);
break;
case ATTRIBUTE_TYPES.EXPONENTIAL_HISTOGRAM:
setSpaceAggregationOptions(metricsHistogramSpaceAggregateOperatorOptions);
break;
default:
setSpaceAggregationOptions(metricsGaugeSpaceAggregateOperatorOptions);
break;
}
setOperators(newOperators);
},
[panelType],
);
const handleChangeAggregatorAttribute = useCallback( const handleChangeAggregatorAttribute = useCallback(
(value: BaseAutocompleteData): void => { (value: BaseAutocompleteData): void => {
const newQuery: IBuilderQuery = { const newQuery: IBuilderQuery = {
@ -124,9 +183,31 @@ export const useQueryOperations: UseQueryOperations = ({
having: [], having: [],
}; };
if (newQuery.dataSource === DataSource.METRICS && entityVersion === 'v4') {
handleMetricAggregateAtributeTypes(newQuery.aggregateAttribute);
if (newQuery.aggregateAttribute.type === ATTRIBUTE_TYPES.SUM) {
newQuery.aggregateOperator = MetricAggregateOperator.RATE;
newQuery.timeAggregation = MetricAggregateOperator.RATE;
} else if (newQuery.aggregateAttribute.type === ATTRIBUTE_TYPES.GAUGE) {
newQuery.aggregateOperator = MetricAggregateOperator.AVG;
newQuery.timeAggregation = MetricAggregateOperator.AVG;
} else {
newQuery.timeAggregation = '';
}
newQuery.spaceAggregation = '';
}
handleSetQueryData(index, newQuery); handleSetQueryData(index, newQuery);
}, },
[index, query, handleSetQueryData], [
query,
entityVersion,
handleSetQueryData,
index,
handleMetricAggregateAtributeTypes,
],
); );
const handleChangeDataSource = useCallback( const handleChangeDataSource = useCallback(
@ -203,6 +284,21 @@ export const useQueryOperations: UseQueryOperations = ({
[formula, handleSetFormulaData, index], [formula, handleSetFormulaData, index],
); );
const handleQueryFunctionsUpdates = useCallback(
(functions: QueryFunctionProps[]): void => {
const newQuery: IBuilderQuery = {
...query,
};
if (newQuery.dataSource === DataSource.METRICS) {
newQuery.functions = functions;
}
handleSetQueryData(index, newQuery);
},
[query, handleSetQueryData, index],
);
const isMetricsDataSource = query.dataSource === DataSource.METRICS; const isMetricsDataSource = query.dataSource === DataSource.METRICS;
const isTracePanelType = panelType === PANEL_TYPES.TRACE; const isTracePanelType = panelType === PANEL_TYPES.TRACE;
@ -210,15 +306,26 @@ export const useQueryOperations: UseQueryOperations = ({
useEffect(() => { useEffect(() => {
if (initialDataSource && dataSource !== initialDataSource) return; if (initialDataSource && dataSource !== initialDataSource) return;
const initialOperators = getOperatorsBySourceAndPanelType({ if (
dataSource, dataSource === DataSource.METRICS &&
panelType: panelType || PANEL_TYPES.TIME_SERIES, query &&
}); query.aggregateAttribute &&
entityVersion === 'v4'
) {
handleMetricAggregateAtributeTypes(query.aggregateAttribute);
} else {
const initialOperators = getOperatorsBySourceAndPanelType({
dataSource,
panelType: panelType || PANEL_TYPES.TIME_SERIES,
});
if (JSON.stringify(operators) === JSON.stringify(initialOperators)) return; if (JSON.stringify(operators) === JSON.stringify(initialOperators)) return;
setOperators(initialOperators); setOperators(initialOperators);
}, [dataSource, initialDataSource, panelType, operators]); }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSource, initialDataSource, panelType, operators, entityVersion]);
useEffect(() => { useEffect(() => {
const additionalFilters = getNewListOfAdditionalFilters(dataSource, true); const additionalFilters = getNewListOfAdditionalFilters(dataSource, true);
@ -236,13 +343,16 @@ export const useQueryOperations: UseQueryOperations = ({
isTracePanelType, isTracePanelType,
isMetricsDataSource, isMetricsDataSource,
operators, operators,
spaceAggregationOptions,
listOfAdditionalFilters, listOfAdditionalFilters,
handleChangeOperator, handleChangeOperator,
handleSpaceAggregationChange,
handleChangeAggregatorAttribute, handleChangeAggregatorAttribute,
handleChangeDataSource, handleChangeDataSource,
handleDeleteQuery, handleDeleteQuery,
handleChangeQueryData, handleChangeQueryData,
listOfAdditionalFormulaFilters, listOfAdditionalFormulaFilters,
handleChangeFormulaData, handleChangeFormulaData,
handleQueryFunctionsUpdates,
}; };
}; };

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { QueryParams } from 'constants/query'; import { QueryParams } from 'constants/query';
import { import {
initialQueryBuilderFormValues, initialQueryBuilderFormValues,
@ -126,6 +127,7 @@ export const useLogsData = ({
const { data, isFetching } = useGetExplorerQueryRange( const { data, isFetching } = useGetExplorerQueryRange(
requestData, requestData,
panelType, panelType,
DEFAULT_ENTITY_VERSION,
{ {
keepPreviousData: true, keepPreviousData: true,
enabled: !isLimit && !!requestData, enabled: !isLimit && !!requestData,

View File

@ -18,11 +18,16 @@ import { prepareQueryRangePayload } from './prepareQueryRangePayload';
export async function GetMetricQueryRange( export async function GetMetricQueryRange(
props: GetQueryResultsProps, props: GetQueryResultsProps,
version: string,
signal?: AbortSignal, signal?: AbortSignal,
): Promise<SuccessResponse<MetricRangePayloadProps>> { ): Promise<SuccessResponse<MetricRangePayloadProps>> {
const { legendMap, queryPayload } = prepareQueryRangePayload(props); const { legendMap, queryPayload } = prepareQueryRangePayload(props);
const response = await getMetricsQueryRange(queryPayload, signal); const response = await getMetricsQueryRange(
queryPayload,
version || 'v3',
signal,
);
if (response.statusCode >= 400) { if (response.statusCode >= 400) {
throw new Error( throw new Error(

View File

@ -144,8 +144,6 @@ export const parseQuery = (queryString) => {
]; ];
} }
} }
// console.log(parsedRaw);
return parsedRaw; return parsedRaw;
}; };

View File

@ -0,0 +1,31 @@
import {
ATTRIBUTE_TYPES,
metricsOperatorsByType,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { metricsEmptyTimeAggregateOperatorOptions } from 'constants/queryBuilderOperators';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
type GetQueryOperatorsParams = {
dataSource: DataSource;
panelType: PANEL_TYPES;
aggregateAttributeType: ATTRIBUTE_TYPES;
};
export const getMetricsOperatorsByAttributeType = ({
dataSource,
aggregateAttributeType,
}: GetQueryOperatorsParams): SelectOption<string, string>[] => {
if (dataSource === DataSource.METRICS && aggregateAttributeType) {
if (aggregateAttributeType === ATTRIBUTE_TYPES.SUM) {
return metricsOperatorsByType.Sum;
}
if (aggregateAttributeType === ATTRIBUTE_TYPES.GAUGE) {
return metricsOperatorsByType.Gauge;
}
}
return metricsEmptyTimeAggregateOperatorOptions;
};

View File

@ -18,6 +18,7 @@ interface UplotTooltipDataProps {
value: number; value: number;
tooltipValue: string; tooltipValue: string;
textContent: string; textContent: string;
queryName: string;
} }
const generateTooltipContent = ( const generateTooltipContent = (
@ -35,6 +36,7 @@ const generateTooltipContent = (
let tooltipTitle = ''; let tooltipTitle = '';
const formattedData: Record<string, UplotTooltipDataProps> = {}; const formattedData: Record<string, UplotTooltipDataProps> = {};
const duplicatedLegendLabels: Record<string, true> = {};
function sortTooltipContentBasedOnValue( function sortTooltipContentBasedOnValue(
tooltipDataObj: Record<string, UplotTooltipDataProps>, tooltipDataObj: Record<string, UplotTooltipDataProps>,
@ -57,8 +59,29 @@ const generateTooltipContent = (
const color = generateColor(label, themeColors.chartcolors); const color = generateColor(label, themeColors.chartcolors);
let tooltipItemLabel = label;
if (Number.isFinite(value)) { if (Number.isFinite(value)) {
const tooltipValue = getToolTipValue(value, yAxisUnit); const tooltipValue = getToolTipValue(value, yAxisUnit);
if (
duplicatedLegendLabels[label] ||
Object.prototype.hasOwnProperty.call(formattedData, label)
) {
duplicatedLegendLabels[label] = true;
const tempDataObj = formattedData[label];
if (tempDataObj) {
const newLabel = `${tempDataObj.queryName}: ${tempDataObj.label}`;
tempDataObj.textContent = `${newLabel} : ${tempDataObj.tooltipValue}`;
formattedData[newLabel] = tempDataObj;
delete formattedData[label];
}
tooltipItemLabel = `${queryName}: ${label}`;
}
const dataObj = { const dataObj = {
show: item.show || false, show: item.show || false,
@ -69,11 +92,13 @@ const generateTooltipContent = (
focus: item?._focus || false, focus: item?._focus || false,
value, value,
tooltipValue, tooltipValue,
textContent: `${label} : ${tooltipValue}`, queryName,
textContent: `${tooltipItemLabel} : ${tooltipValue}`,
}; };
tooltipCount += 1; tooltipCount += 1;
formattedData[label] = dataObj;
formattedData[tooltipItemLabel] = dataObj;
} }
} }
}); });

View File

@ -224,7 +224,6 @@ const appReducer = (
} }
case UPDATE_USER_FLAG: { case UPDATE_USER_FLAG: {
console.log('herei n update user flag');
return { return {
...state, ...state,
userFlags: { ...state.userFlags, ...action.payload.flags }, userFlags: { ...state.userFlags, ...action.payload.flags },

View File

@ -22,6 +22,7 @@ export interface AlertDef {
disabled?: boolean; disabled?: boolean;
preferredChannels?: string[]; preferredChannels?: string[];
broadcastToAll?: boolean; broadcastToAll?: boolean;
version?: string;
} }
export interface RuleCondition { export interface RuleCondition {

View File

@ -4,6 +4,7 @@ export type Props =
| { | {
title: Dashboard['data']['title']; title: Dashboard['data']['title'];
uploadedGrafana: boolean; uploadedGrafana: boolean;
version?: string;
} }
| { DashboardData: DashboardData; uploadedGrafana: boolean }; | { DashboardData: DashboardData; uploadedGrafana: boolean };

View File

@ -62,6 +62,7 @@ export interface DashboardData {
title: string; title: string;
layout?: Layout[]; layout?: Layout[];
variables: Record<string, IDashboardVariable>; variables: Record<string, IDashboardVariable>;
version?: string;
} }
export interface IBaseWidget { export interface IBaseWidget {

View File

@ -47,12 +47,20 @@ export type OrderByPayload = {
order: string; order: string;
}; };
export interface QueryFunctionProps {
name: string;
args: string[];
}
// Type for query builder // Type for query builder
export type IBuilderQuery = { export type IBuilderQuery = {
queryName: string; queryName: string;
dataSource: DataSource; dataSource: DataSource;
aggregateOperator: string; aggregateOperator: string;
aggregateAttribute: BaseAutocompleteData; aggregateAttribute: BaseAutocompleteData;
timeAggregation: string;
spaceAggregation?: string;
functions: QueryFunctionProps[];
filters: TagFilter; filters: TagFilter;
groupBy: BaseAutocompleteData[]; groupBy: BaseAutocompleteData[];
expression: string; expression: string;

View File

@ -4,6 +4,7 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
import { import {
IBuilderFormula, IBuilderFormula,
IBuilderQuery, IBuilderQuery,
QueryFunctionProps,
} from 'types/api/queryBuilder/queryBuilderData'; } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@ -13,6 +14,7 @@ type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
Pick<QueryBuilderProps, 'filterConfigs'> & { Pick<QueryBuilderProps, 'filterConfigs'> & {
formula?: IBuilderFormula; formula?: IBuilderFormula;
isListViewPanel?: boolean; isListViewPanel?: boolean;
entityVersion: string;
}; };
export type HandleChangeQueryData = < export type HandleChangeQueryData = <
@ -37,12 +39,15 @@ export type UseQueryOperations = (
isTracePanelType: boolean; isTracePanelType: boolean;
isMetricsDataSource: boolean; isMetricsDataSource: boolean;
operators: SelectOption<string, string>[]; operators: SelectOption<string, string>[];
spaceAggregationOptions: SelectOption<string, string>[];
listOfAdditionalFilters: string[]; listOfAdditionalFilters: string[];
handleChangeOperator: (value: string) => void; handleChangeOperator: (value: string) => void;
handleSpaceAggregationChange: (value: string) => void;
handleChangeAggregatorAttribute: (value: BaseAutocompleteData) => void; handleChangeAggregatorAttribute: (value: BaseAutocompleteData) => void;
handleChangeDataSource: (newSource: DataSource) => void; handleChangeDataSource: (newSource: DataSource) => void;
handleDeleteQuery: () => void; handleDeleteQuery: () => void;
handleChangeQueryData: HandleChangeQueryData; handleChangeQueryData: HandleChangeQueryData;
handleChangeFormulaData: HandleChangeFormulaData; handleChangeFormulaData: HandleChangeFormulaData;
handleQueryFunctionsUpdates: (functions: QueryFunctionProps[]) => void;
listOfAdditionalFormulaFilters: string[]; listOfAdditionalFormulaFilters: string[];
}; };

View File

@ -92,6 +92,8 @@ export enum MetricAggregateOperator {
HIST_QUANTILE_90 = 'hist_quantile_90', HIST_QUANTILE_90 = 'hist_quantile_90',
HIST_QUANTILE_95 = 'hist_quantile_95', HIST_QUANTILE_95 = 'hist_quantile_95',
HIST_QUANTILE_99 = 'hist_quantile_99', HIST_QUANTILE_99 = 'hist_quantile_99',
INCREASE = 'increase',
LATEST = 'latest',
} }
export enum TracesAggregatorOperator { export enum TracesAggregatorOperator {
@ -142,6 +144,24 @@ export enum LogsAggregatorOperator {
RATE_MAX = 'rate_max', RATE_MAX = 'rate_max',
} }
export enum QueryFunctionsTypes {
CUTOFF_MIN = 'cutOffMin',
CUTOFF_MAX = 'cutOffMax',
CLAMP_MIN = 'clampMin',
CLAMP_MAX = 'clampMax',
ABSOLUTE = 'absolute',
LOG_2 = 'log2',
LOG_10 = 'log10',
CUMULATIVE_SUM = 'cumSum',
EWMA_3 = 'ewma3',
EWMA_5 = 'ewma5',
EWMA_7 = 'ewma7',
MEDIAN_3 = 'median3',
MEDIAN_5 = 'median5',
MEDIAN_7 = 'median7',
TIME_SHIFT = 'timeShift',
}
export type PanelTypeKeys = export type PanelTypeKeys =
| 'TIME_SERIES' | 'TIME_SERIES'
| 'VALUE' | 'VALUE'

View File

@ -1,4 +1,5 @@
import { Page } from '@playwright/test'; import { Page } from '@playwright/test';
import { JsonApplicationType } from '../fixtures/constant'; import { JsonApplicationType } from '../fixtures/constant';
// API endpoints // API endpoints
@ -41,6 +42,99 @@ export const timeSeriesGraphName = 'Time1';
let widgetsId: string; let widgetsId: string;
export const insertWidgetIdInResponse = (widgetID: string): any => ({
status: 'success',
data: {
id: 219,
uuid: 'd697fddb-a771-4bb4-aa38-810f000ed96a',
created_at: '2023-11-17T20:44:03.167646604Z',
created_by: 'vikrant@signoz.io',
updated_at: '2023-11-17T20:51:23.058536475Z',
updated_by: 'vikrant@signoz.io',
data: {
description: 'Playwright Dashboard T',
layout: [
{
h: 3,
i: '9fbcf0db-1572-4572-bf6b-0a84dd10ed85',
w: 6,
x: 0,
y: 0,
},
],
version: 'v3',
name: '',
tags: [],
title: 'Playwright Dashboard',
variables: {},
widgets: [
{
description: '',
id: widgetID,
isStacked: false,
nullZeroValues: '',
opacity: '',
panelTypes: 'graph',
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: '',
id: '------',
isColumn: false,
isJSON: false,
key: '',
type: '',
},
aggregateOperator: 'count',
dataSource: 'metrics',
disabled: false,
expression: 'A',
filters: {
items: [],
op: 'AND',
},
groupBy: [],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
stepInterval: 60,
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '6b4011e4-bcea-497d-81a9-0ee7816b679d',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: 'builder',
},
timePreferance: 'GLOBAL_TIME',
title: '',
},
],
},
isLocked: 0,
},
});
// mock API calls // mock API calls
export const dashboardsListAndCreate = async ( export const dashboardsListAndCreate = async (
page: Page, page: Page,
@ -76,7 +170,8 @@ export const getTimeSeriesQueryData = async (
page: Page, page: Page,
response: any, response: any,
): Promise<void> => { ): Promise<void> => {
await page.route(`**/${queryRangeApiEndpoint}`, (route) => // eslint-disable-next-line sonarjs/no-identical-functions
await page.route(`**/${queryRangeApiEndpoint}`, (route): any =>
route.fulfill({ route.fulfill({
status: 200, status: 200,
contentType: JsonApplicationType, contentType: JsonApplicationType,
@ -84,97 +179,3 @@ export const getTimeSeriesQueryData = async (
}), }),
); );
}; };
export const insertWidgetIdInResponse = (widgetID: string) => {
return {
status: 'success',
data: {
id: 219,
uuid: 'd697fddb-a771-4bb4-aa38-810f000ed96a',
created_at: '2023-11-17T20:44:03.167646604Z',
created_by: 'vikrant@signoz.io',
updated_at: '2023-11-17T20:51:23.058536475Z',
updated_by: 'vikrant@signoz.io',
data: {
description: 'Playwright Dashboard T',
layout: [
{
h: 3,
i: '9fbcf0db-1572-4572-bf6b-0a84dd10ed85',
w: 6,
x: 0,
y: 0,
},
],
name: '',
tags: [],
title: 'Playwright Dashboard',
variables: {},
widgets: [
{
description: '',
id: widgetID,
isStacked: false,
nullZeroValues: '',
opacity: '',
panelTypes: 'graph',
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: '',
id: '------',
isColumn: false,
isJSON: false,
key: '',
type: '',
},
aggregateOperator: 'count',
dataSource: 'metrics',
disabled: false,
expression: 'A',
filters: {
items: [],
op: 'AND',
},
groupBy: [],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'sum',
stepInterval: 60,
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '6b4011e4-bcea-497d-81a9-0ee7816b679d',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: 'builder',
},
timePreferance: 'GLOBAL_TIME',
title: '',
},
],
},
isLocked: 0,
},
};
};

View File

@ -56,7 +56,7 @@
"limit": null, "limit": null,
"orderBy": [], "orderBy": [],
"queryName": "A", "queryName": "A",
"reduceTo": "sum", "reduceTo": "avg",
"stepInterval": 60 "stepInterval": 60
} }
], ],

View File

@ -56,7 +56,7 @@
"limit": null, "limit": null,
"orderBy": [], "orderBy": [],
"queryName": "A", "queryName": "A",
"reduceTo": "sum", "reduceTo": "avg",
"stepInterval": 60 "stepInterval": 60
} }
], ],

View File

@ -40,7 +40,7 @@
"order": "desc" "order": "desc"
} }
], ],
"reduceTo": "sum" "reduceTo": "avg"
} }
}, },
"panelType": "table", "panelType": "table",

2
go.mod
View File

@ -203,4 +203,4 @@ require (
k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect
) )
replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.78 replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.79

6
go.sum
View File

@ -94,12 +94,10 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFmafKx32bynV6QrzViL/s+ZDvQxH1E4=
github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb/go.mod h1:JznGDNg9x1cujDKa22RaQOimOvvEfy3nxzDGd8XDgmA=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8= github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkbj57eGXx8H3ZJ4zhmQXBnrW523ktj8=
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc= github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/prometheus v1.9.78 h1:bB3yuDrRzi/Mv00kWayR9DZbyjTuGfendSqISyDcXiY= github.com/SigNoz/prometheus v1.9.79 h1:RScpt9CUyOC4KQgzEUXRZ9lXHUdFT1eYAsFY3zlqPFM=
github.com/SigNoz/prometheus v1.9.78/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww= github.com/SigNoz/prometheus v1.9.79/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww=
github.com/SigNoz/signoz-otel-collector v0.88.12 h1:UwkVi1o2NY9gRgCLBtWVKr+UDxb4FaTs63Sb20qgf8w= github.com/SigNoz/signoz-otel-collector v0.88.12 h1:UwkVi1o2NY9gRgCLBtWVKr+UDxb4FaTs63Sb20qgf8w=
github.com/SigNoz/signoz-otel-collector v0.88.12/go.mod h1:RH9OEjni6tkh9RgN/meSPxv3kykjcFscqMwJgbUAXmo= github.com/SigNoz/signoz-otel-collector v0.88.12/go.mod h1:RH9OEjni6tkh9RgN/meSPxv3kykjcFscqMwJgbUAXmo=
github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc= github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc=

View File

@ -47,6 +47,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/logs" "go.signoz.io/signoz/pkg/query-service/app/logs"
"go.signoz.io/signoz/pkg/query-service/app/services" "go.signoz.io/signoz/pkg/query-service/app/services"
"go.signoz.io/signoz/pkg/query-service/auth" "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/constants"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager" am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"go.signoz.io/signoz/pkg/query-service/interfaces" "go.signoz.io/signoz/pkg/query-service/interfaces"
@ -3974,7 +3975,7 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, req
var rows driver.Rows var rows driver.Rows
var response v3.AggregateAttributeResponse var response v3.AggregateAttributeResponse
query = fmt.Sprintf("SELECT DISTINCT metric_name, type from %s.%s WHERE metric_name ILIKE $1", signozMetricDBName, signozTSTableNameV41Day) query = fmt.Sprintf("SELECT metric_name, type, is_monotonic, temporality FROM %s.%s WHERE metric_name ILIKE $1 GROUP BY metric_name, type, is_monotonic, temporality", signozMetricDBName, signozTSTableNameV41Day)
if req.Limit != 0 { if req.Limit != 0 {
query = query + fmt.Sprintf(" LIMIT %d;", req.Limit) query = query + fmt.Sprintf(" LIMIT %d;", req.Limit)
} }
@ -3986,11 +3987,18 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, req
} }
defer rows.Close() defer rows.Close()
var metricName, typ string seen := make(map[string]struct{})
var metricName, typ, temporality string
var isMonotonic bool
for rows.Next() { for rows.Next() {
if err := rows.Scan(&metricName, &typ); err != nil { if err := rows.Scan(&metricName, &typ, &isMonotonic, &temporality); err != nil {
return nil, fmt.Errorf("error while scanning rows: %s", err.Error()) return nil, fmt.Errorf("error while scanning rows: %s", err.Error())
} }
// Non-monotonic cumulative sums are treated as gauges
if typ == "Sum" && !isMonotonic && temporality == string(v3.Cumulative) {
typ = "Gauge"
}
// unlike traces/logs `tag`/`resource` type, the `Type` will be metric type // unlike traces/logs `tag`/`resource` type, the `Type` will be metric type
key := v3.AttributeKey{ key := v3.AttributeKey{
Key: metricName, Key: metricName,
@ -3998,6 +4006,11 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, req
Type: v3.AttributeKeyType(typ), Type: v3.AttributeKeyType(typ),
IsColumn: true, IsColumn: true,
} }
// remove duplicates
if _, ok := seen[metricName+typ]; ok {
continue
}
seen[metricName+typ] = struct{}{}
response.AttributeKeys = append(response.AttributeKeys, key) response.AttributeKeys = append(response.AttributeKeys, key)
} }
@ -4012,11 +4025,11 @@ func (r *ClickHouseReader) GetMetricAttributeKeys(ctx context.Context, req *v3.F
var response v3.FilterAttributeKeyResponse var response v3.FilterAttributeKeyResponse
// skips the internal attributes i.e attributes starting with __ // skips the internal attributes i.e attributes starting with __
query = fmt.Sprintf("SELECT DISTINCT arrayJoin(tagKeys) as distinctTagKey from (SELECT DISTINCT(JSONExtractKeys(labels)) tagKeys from %s.%s WHERE metric_name=$1) WHERE distinctTagKey ILIKE $2 AND distinctTagKey NOT LIKE '\\_\\_%%'", signozMetricDBName, signozTSTableName) query = fmt.Sprintf("SELECT arrayJoin(tagKeys) AS distinctTagKey FROM (SELECT JSONExtractKeys(labels) AS tagKeys FROM %s.%s WHERE metric_name=$1 AND unix_milli >= $2 GROUP BY tagKeys) WHERE distinctTagKey ILIKE $3 AND distinctTagKey NOT LIKE '\\_\\_%%' GROUP BY distinctTagKey", signozMetricDBName, signozTSTableNameV41Day)
if req.Limit != 0 { if req.Limit != 0 {
query = query + fmt.Sprintf(" LIMIT %d;", req.Limit) query = query + fmt.Sprintf(" LIMIT %d;", req.Limit)
} }
rows, err = r.db.Query(ctx, query, req.AggregateAttribute, fmt.Sprintf("%%%s%%", req.SearchText)) rows, err = r.db.Query(ctx, query, req.AggregateAttribute, common.PastDayRoundOff(), fmt.Sprintf("%%%s%%", req.SearchText))
if err != nil { if err != nil {
zap.S().Error(err) zap.S().Error(err)
return nil, fmt.Errorf("error while executing query: %s", err.Error()) return nil, fmt.Errorf("error while executing query: %s", err.Error())
@ -4047,11 +4060,11 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, req *v3
var rows driver.Rows var rows driver.Rows
var attributeValues v3.FilterAttributeValueResponse var attributeValues v3.FilterAttributeValueResponse
query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels, $1)) from %s.%s WHERE metric_name=$2 AND JSONExtractString(labels, $3) ILIKE $4", signozMetricDBName, signozTSTableName) query = fmt.Sprintf("SELECT JSONExtractString(labels, $1) AS tagValue FROM %s.%s WHERE metric_name=$2 AND JSONExtractString(labels, $3) ILIKE $4 AND unix_milli >= $5 GROUP BY tagValue", signozMetricDBName, signozTSTableNameV41Day)
if req.Limit != 0 { if req.Limit != 0 {
query = query + fmt.Sprintf(" LIMIT %d;", req.Limit) query = query + fmt.Sprintf(" LIMIT %d;", req.Limit)
} }
rows, err = r.db.Query(ctx, query, req.FilterAttributeKey, req.AggregateAttribute, req.FilterAttributeKey, fmt.Sprintf("%%%s%%", req.SearchText)) rows, err = r.db.Query(ctx, query, req.FilterAttributeKey, req.AggregateAttribute, req.FilterAttributeKey, fmt.Sprintf("%%%s%%", req.SearchText), common.PastDayRoundOff())
if err != nil { if err != nil {
zap.S().Error(err) zap.S().Error(err)

Some files were not shown because too many files have changed in this diff Show More