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_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"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_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
"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 apiV3 = '/api/v3/';
export const apiV4 = '/api/v4/';
export const apiAlertManager = '/api/alertmanager';
export default apiV1;

View File

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

View File

@ -1,4 +1,4 @@
import { ApiV3Instance as axios } from 'api';
import { ApiV3Instance, ApiV4Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@ -9,10 +9,23 @@ import {
export const getMetricsQueryRange = async (
props: QueryRangePayload,
version: string,
signal: AbortSignal,
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
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 {
statusCode: 200,

View File

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

View File

@ -13,3 +13,5 @@ export const SIGNOZ_UPGRADE_PLAN_URL =
'https://upgrade.signoz.io/upgrade-from-app';
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 {
logsAggregateOperatorOptions,
metricAggregateOperatorOptions,
metricsGaugeAggregateOperatorOptions,
metricsGaugeSpaceAggregateOperatorOptions,
metricsHistogramSpaceAggregateOperatorOptions,
metricsSumAggregateOperatorOptions,
metricsSumSpaceAggregateOperatorOptions,
tracesAggregateOperatorOptions,
} from './queryBuilderOperators';
@ -74,6 +79,18 @@ export const mapOfOperators = {
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[]> = {
metrics: [
// eslint-disable-next-line sonarjs/no-duplicate-string
@ -148,6 +165,9 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }),
aggregateOperator: MetricAggregateOperator.COUNT,
aggregateAttribute: initialAutocompleteData,
timeAggregation: MetricAggregateOperator.RATE,
spaceAggregation: MetricAggregateOperator.SUM,
functions: [],
filters: { items: [], op: 'AND' },
expression: createNewBuilderItemName({
existNames: [],
@ -160,7 +180,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
orderBy: [],
groupBy: [],
legend: '',
reduceTo: 'sum',
reduceTo: 'avg',
};
const initialQueryBuilderFormLogsValues: IBuilderQuery = {
@ -268,6 +288,14 @@ export enum PANEL_TYPES {
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 const QUERY_BUILDER_SEARCH_VALUES = {

View File

@ -302,3 +302,126 @@ export const logsAggregateOperatorOptions: SelectOption<string, string>[] = [
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 = {
alertType: AlertTypes.METRICS_BASED_ALERT,
version: 'v4',
condition: {
compositeQuery: {
builderQueries: {

View File

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

View File

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

View File

@ -3,13 +3,16 @@ import './QuerySection.styles.scss';
import { Button, Tabs, Tooltip } from 'antd';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
import { QueryBuilder } from 'container/QueryBuilder';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { Atom, Play, Terminal } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef } from 'types/api/alerts/def';
import { EQueryType } from 'types/common/dashboard';
import AppReducer from 'types/reducer/app';
@ -22,6 +25,7 @@ function QuerySection({
setQueryCategory,
alertType,
runQuery,
alertDef,
panelType,
}: QuerySectionProps): JSX.Element {
// init namespace for translations
@ -50,6 +54,10 @@ function QuerySection({
queryVariant: 'static',
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 => {
switch (typ) {
case AlertTypes.TRACES_BASED_ALERT:
@ -197,6 +216,7 @@ interface QuerySectionProps {
setQueryCategory: (n: EQueryType) => void;
alertType: AlertTypes;
runQuery: VoidFunction;
alertDef: AlertDef;
panelType: PANEL_TYPES;
}

View File

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

View File

@ -6,6 +6,7 @@ import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types';
import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import GridPanelSwitch from 'container/GridPanelSwitch';
import {
@ -96,6 +97,7 @@ function FullView({
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables),
},
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
{
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,6 +62,7 @@ function LogExplorerQuerySection({
index: 0,
query,
filterConfigs,
entityVersion: '',
});
const renderOrderBy = useCallback(
@ -103,6 +104,7 @@ function LogExplorerQuerySection({
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
filterConfigs={filterConfigs}
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 Spinner from 'components/Spinner';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
@ -105,6 +106,7 @@ function LogsContextList({
const { isError, isFetching } = useGetExplorerQueryRange(
requestData,
PANEL_TYPES.LIST,
DEFAULT_ENTITY_VERSION,
{
keepPreviousData: true,
enabled: !!requestData,

View File

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

View File

@ -1,80 +1,88 @@
.logs-table {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
.resize-table {
height: calc(92% - 5px);
overflow: scroll;
.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;
}
.resize-table {
height: calc(100% - 40px);
overflow: scroll;
overflow-x: hidden;
.ant-table-wrapper .ant-table-header {
border-bottom: 0.5px solid var(--bg-slate-400);
}
&::-webkit-scrollbar {
width: 0.2rem;
height: 0.2rem;
}
.ant-table-wrapper .ant-table-thead > tr > th {
font-family: Inter;
color: var(--bg-vanilla-100);
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-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-thead > tr > th::before {
display: none;
}
}
.ant-table-wrapper .ant-table-header {
border-bottom: 0.5px solid var(--bg-slate-400);
}
.controller {
position: absolute;
bottom: 5px;
right: 10px;
}
.ant-table-wrapper .ant-table-thead > tr > th {
font-family: Inter;
color: var(--bg-vanilla-100);
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 {
.logs-table {
.resize-table {
.ant-table-wrapper .ant-table-tbody >tr >td {
background-color: var(--bg-vanilla-100);
.ant-typography {
color: var(--bg-ink-500);
}
}
.ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500);
}
.logs-table {
.resize-table {
.ant-table-wrapper .ant-table-tbody > tr > td {
background-color: var(--bg-vanilla-100);
.ant-typography {
color: var(--bg-ink-500);
}
}
.ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500);
}
.ant-table-wrapper .ant-table-header {
border-bottom: 0.5px solid var(--bg-vanilla-400);
}
}
}
}
.ant-table-wrapper .ant-table-header {
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 { VIEW_TYPES } from 'components/LogDetail/constants';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import Controls from 'container/Controls';
@ -106,6 +107,7 @@ function LogsPanelComponent({
selectedTime: selectedTime?.enum || 'GLOBAL_TIME',
variables: getDashboardVariables(selectedDashboard?.data.variables),
},
DEFAULT_ENTITY_VERSION,
{
queryKey: [
REACT_QUERY_KEY.GET_QUERY_RANGE,

View File

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

View File

@ -1,3 +1,4 @@
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { topOperationMetricsDownloadOptions } from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
@ -67,6 +68,7 @@ function TopOperationMetrics(): JSX.Element {
globalSelectedInterval,
variables: {},
},
DEFAULT_ENTITY_VERSION,
{
queryKey: [
`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 { PANEL_TYPES } from 'constants/queryBuilder';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { CSSProperties } from 'react';
import { LogsAggregatorOperator } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
@ -20,8 +18,6 @@ import menuItems from './menuItems';
import { Card, Container, Text } from './styles';
function DashboardGraphSlider(): JSX.Element {
const isDarkMode = useIsDarkMode();
const {
handleToggleDashboardSlider,
layouts,
@ -47,6 +43,7 @@ function DashboardGraphSlider(): JSX.Element {
description: data?.description || '',
name: data?.name || '',
tags: data?.tags || [],
version: data?.version || 'v3',
layout: [
{
i: id,
@ -146,13 +143,11 @@ function DashboardGraphSlider(): JSX.Element {
);
};
const fillColor: CSSProperties['color'] = isDarkMode ? 'white' : 'black';
return (
<Container>
{menuItems.map(({ name, Icon, display }) => (
{menuItems.map(({ name, icon, display }) => (
<Card onClick={onClickHandler(name)} id={name} key={name}>
<Icon fillColor={fillColor} />
{icon}
<Text>{display}</Text>
</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;
justify-content: right;
gap: 8px;
margin-bottom: 12px;
`;
export const Card = styled(CardComponent)`
min-height: 10vh;
min-height: 80px;
min-width: 120px;
overflow-y: auto;
cursor: pointer;
transition: transform 0.2s;
.ant-card-body {
padding: 12px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
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 {
display: -webkit-box;
-webkit-line-clamp: 2; /* Show up to 2 lines */
@ -5,3 +18,22 @@
text-overflow: ellipsis;
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 } from 'antd';
import { Button, Tooltip } from 'antd';
import { Cog } from 'lucide-react';
import { useState } from 'react';
import DashboardSettingsContent from '../DashboardSettings';
@ -18,14 +18,16 @@ function SettingsDrawer({ drawerTitle }: { drawerTitle: string }): JSX.Element {
return (
<>
<Button
type="dashed"
onClick={showDrawer}
style={{ width: '100%' }}
data-testid="show-drawer"
>
<SettingOutlined /> Configure
</Button>
<Tooltip title="Configure" placement="left">
<Button
className="periscope-btn"
onClick={showDrawer}
style={{ width: '100%' }}
data-testid="show-drawer"
icon={<Cog size={16} />}
/>
</Tooltip>
<DrawerContainer
title={drawerTitle}
placement="right"

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import { InfoCircleOutlined } from '@ant-design/icons';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { Card } from 'container/GridCardLayout/styles';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@ -34,10 +35,13 @@ function WidgetGraph({
const selectedWidget = widgets.find((e) => e.id === widgetId);
const getWidgetQueryRange = useGetWidgetQueryRange({
graphType: getGraphType(selectedGraph),
selectedTime: selectedTime.enum,
});
const getWidgetQueryRange = useGetWidgetQueryRange(
{
graphType: getGraphType(selectedGraph),
selectedTime: selectedTime.enum,
},
selectedDashboard?.data?.version || DEFAULT_ENTITY_VERSION,
);
if (selectedWidget === undefined) {
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 { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
@ -18,7 +20,7 @@ import {
getPreviousWidgets,
getSelectedWidgetIndex,
} from 'providers/Dashboard/util';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { generatePath, useLocation, useParams } from 'react-router-dom';
@ -53,6 +55,8 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const { t } = useTranslation(['dashboard']);
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
const { currentQuery, stagedQuery } = useQueryBuilder();
const isQueryModified = useMemo(
@ -311,6 +315,17 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
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 (
<Container>
<ButtonContainer>
@ -414,7 +429,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
<Typography>
{t('your_graph_build_with')}{' '}
<QueryTypeTag queryType={currentQuery.queryType} />
{t('dashboar_ok_confirm')}
{t('dashboard_ok_confirm')}
</Typography>
) : (
<Typography>{t('dashboard_unsave_changes')} </Typography>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,19 +16,11 @@ export const StyledCheckOutlined = styled(CheckOutlined)`
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)`
&&& {
border-radius: 3px;
padding: 0.3rem 0.3rem;
font-weight: 400;
padding: 0.1rem 0.2rem;
font-weight: 300;
font-size: 0.6rem;
}
`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,65 +1,73 @@
.traces-table {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
height: 100%;
.resize-table {
height: calc(90% - 5px);
overflow: scroll;
.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;
}
.resize-table {
height: calc(100% - 40px);
overflow: scroll;
overflow-x: hidden;
.ant-table-wrapper .ant-table-thead > tr > th {
font-family: Inter;
color: var(--bg-vanilla-100);
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;
}
&::-webkit-scrollbar {
width: 0.2rem;
height: 0.2rem;
}
.ant-table-wrapper .ant-table-thead > tr > th::before {
display: none;
}
}
.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;
}
.controller {
position: absolute;
bottom: 5px;
right: 10px;
}
.ant-table-wrapper .ant-table-thead > tr > th {
font-family: Inter;
color: var(--bg-vanilla-100);
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 {
.traces-table {
.resize-table {
.ant-table-wrapper .ant-table-tbody >tr >td {
background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06);
}
.ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06);
}
}
}
}
.traces-table {
.resize-table {
.ant-table-wrapper .ant-table-tbody > tr > td {
background-color: var(--bg-vanilla-100);
color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06);
}
.ant-table-wrapper .ant-table-thead > tr > th {
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-500);
border-color: rgba(0, 0, 0, 0.06);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -144,8 +144,6 @@ export const parseQuery = (queryString) => {
];
}
}
// console.log(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;
tooltipValue: string;
textContent: string;
queryName: string;
}
const generateTooltipContent = (
@ -35,6 +36,7 @@ const generateTooltipContent = (
let tooltipTitle = '';
const formattedData: Record<string, UplotTooltipDataProps> = {};
const duplicatedLegendLabels: Record<string, true> = {};
function sortTooltipContentBasedOnValue(
tooltipDataObj: Record<string, UplotTooltipDataProps>,
@ -57,8 +59,29 @@ const generateTooltipContent = (
const color = generateColor(label, themeColors.chartcolors);
let tooltipItemLabel = label;
if (Number.isFinite(value)) {
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 = {
show: item.show || false,
@ -69,11 +92,13 @@ const generateTooltipContent = (
focus: item?._focus || false,
value,
tooltipValue,
textContent: `${label} : ${tooltipValue}`,
queryName,
textContent: `${tooltipItemLabel} : ${tooltipValue}`,
};
tooltipCount += 1;
formattedData[label] = dataObj;
formattedData[tooltipItemLabel] = dataObj;
}
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -92,6 +92,8 @@ export enum MetricAggregateOperator {
HIST_QUANTILE_90 = 'hist_quantile_90',
HIST_QUANTILE_95 = 'hist_quantile_95',
HIST_QUANTILE_99 = 'hist_quantile_99',
INCREASE = 'increase',
LATEST = 'latest',
}
export enum TracesAggregatorOperator {
@ -142,6 +144,24 @@ export enum LogsAggregatorOperator {
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 =
| 'TIME_SERIES'
| 'VALUE'

View File

@ -1,4 +1,5 @@
import { Page } from '@playwright/test';
import { JsonApplicationType } from '../fixtures/constant';
// API endpoints
@ -41,6 +42,99 @@ export const timeSeriesGraphName = 'Time1';
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
export const dashboardsListAndCreate = async (
page: Page,
@ -76,7 +170,8 @@ export const getTimeSeriesQueryData = async (
page: Page,
response: any,
): Promise<void> => {
await page.route(`**/${queryRangeApiEndpoint}`, (route) =>
// eslint-disable-next-line sonarjs/no-identical-functions
await page.route(`**/${queryRangeApiEndpoint}`, (route): any =>
route.fulfill({
status: 200,
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,
"orderBy": [],
"queryName": "A",
"reduceTo": "sum",
"reduceTo": "avg",
"stepInterval": 60
}
],

View File

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

View File

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

2
go.mod
View File

@ -203,4 +203,4 @@ require (
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/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
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/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc=
github.com/SigNoz/prometheus v1.9.78 h1:bB3yuDrRzi/Mv00kWayR9DZbyjTuGfendSqISyDcXiY=
github.com/SigNoz/prometheus v1.9.78/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww=
github.com/SigNoz/prometheus v1.9.79 h1:RScpt9CUyOC4KQgzEUXRZ9lXHUdFT1eYAsFY3zlqPFM=
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/go.mod h1:RH9OEjni6tkh9RgN/meSPxv3kykjcFscqMwJgbUAXmo=
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/services"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/common"
"go.signoz.io/signoz/pkg/query-service/constants"
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
"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 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 {
query = query + fmt.Sprintf(" LIMIT %d;", req.Limit)
}
@ -3986,11 +3987,18 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, req
}
defer rows.Close()
var metricName, typ string
seen := make(map[string]struct{})
var metricName, typ, temporality string
var isMonotonic bool
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())
}
// 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
key := v3.AttributeKey{
Key: metricName,
@ -3998,6 +4006,11 @@ func (r *ClickHouseReader) GetMetricAggregateAttributes(ctx context.Context, req
Type: v3.AttributeKeyType(typ),
IsColumn: true,
}
// remove duplicates
if _, ok := seen[metricName+typ]; ok {
continue
}
seen[metricName+typ] = struct{}{}
response.AttributeKeys = append(response.AttributeKeys, key)
}
@ -4012,11 +4025,11 @@ func (r *ClickHouseReader) GetMetricAttributeKeys(ctx context.Context, req *v3.F
var response v3.FilterAttributeKeyResponse
// 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 {
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 {
zap.S().Error(err)
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 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 {
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 {
zap.S().Error(err)

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