chore: add to alerts/dashboard improvements for one chart per query mode in metrics explorer (#8014)

This commit is contained in:
Amlan Kumar Nandy 2025-05-26 12:45:21 +07:00 committed by GitHub
parent 0925ae73a9
commit 3ca3db2567
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 220 additions and 58 deletions

View File

@ -14,6 +14,8 @@ function ExplorerOptionWrapper({
isLoading, isLoading,
onExport, onExport,
sourcepage, sourcepage,
isOneChartPerQuery,
splitedQueries,
}: ExplorerOptionsWrapperProps): JSX.Element { }: ExplorerOptionsWrapperProps): JSX.Element {
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false); const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
@ -32,6 +34,8 @@ function ExplorerOptionWrapper({
sourcepage={sourcepage} sourcepage={sourcepage}
isExplorerOptionHidden={isExplorerOptionHidden} isExplorerOptionHidden={isExplorerOptionHidden}
setIsExplorerOptionHidden={setIsExplorerOptionHidden} setIsExplorerOptionHidden={setIsExplorerOptionHidden}
isOneChartPerQuery={isOneChartPerQuery}
splitedQueries={splitedQueries}
/> />
); );
} }

View File

@ -8,6 +8,21 @@
display: flex; display: flex;
gap: 16px; gap: 16px;
background-color: transparent; background-color: transparent;
.multi-alert-button,
.multi-dashboard-button {
min-width: 130px;
.ant-select-selector {
.ant-select-selection-placeholder {
margin-left: 0 !important;
}
.ant-select-arrow {
display: none !important;
}
}
}
} }
.hide-update { .hide-update {

View File

@ -90,6 +90,8 @@ function ExplorerOptions({
sourcepage, sourcepage,
isExplorerOptionHidden = false, isExplorerOptionHidden = false,
setIsExplorerOptionHidden, setIsExplorerOptionHidden,
isOneChartPerQuery = false,
splitedQueries = [],
}: ExplorerOptionsProps): JSX.Element { }: ExplorerOptionsProps): JSX.Element {
const [isExport, setIsExport] = useState<boolean>(false); const [isExport, setIsExport] = useState<boolean>(false);
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false); const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
@ -99,6 +101,8 @@ function ExplorerOptions({
const history = useHistory(); const history = useHistory();
const ref = useRef<RefSelectProps>(null); const ref = useRef<RefSelectProps>(null);
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const [queryToExport, setQueryToExport] = useState<Query | null>(null);
const isLogsExplorer = sourcepage === DataSource.LOGS; const isLogsExplorer = sourcepage === DataSource.LOGS;
const isMetricsExplorer = sourcepage === DataSource.METRICS; const isMetricsExplorer = sourcepage === DataSource.METRICS;
@ -149,51 +153,62 @@ function ExplorerOptions({
const { user } = useAppContext(); const { user } = useAppContext();
const handleConditionalQueryModification = useCallback((): string => { const handleConditionalQueryModification = useCallback(
if ( (defaultQuery: Query | null): string => {
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP const queryToUse = defaultQuery || query;
) { if (
return JSON.stringify(query); queryToUse?.builder?.queryData?.[0]?.aggregateOperator !==
} StringOperators.NOOP
) {
return JSON.stringify(queryToUse);
}
// Modify aggregateOperator to count, as noop is not supported in alerts // Modify aggregateOperator to count, as noop is not supported in alerts
const modifiedQuery = cloneDeep(query); const modifiedQuery = cloneDeep(queryToUse);
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT; modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
return JSON.stringify(modifiedQuery); return JSON.stringify(modifiedQuery);
}, [query]); },
[query],
);
const onCreateAlertsHandler = useCallback(() => { const onCreateAlertsHandler = useCallback(
if (sourcepage === DataSource.TRACES) { (defaultQuery: Query | null) => {
logEvent('Traces Explorer: Create alert', { if (sourcepage === DataSource.TRACES) {
panelType, logEvent('Traces Explorer: Create alert', {
}); panelType,
} else if (isLogsExplorer) { });
logEvent('Logs Explorer: Create alert', { } else if (isLogsExplorer) {
panelType, logEvent('Logs Explorer: Create alert', {
}); panelType,
} else if (isMetricsExplorer) { });
logEvent('Metrics Explorer: Create alert', { } else if (isMetricsExplorer) {
panelType, logEvent('Metrics Explorer: Create alert', {
}); panelType,
} });
}
const stringifiedQuery = handleConditionalQueryModification(); const stringifiedQuery = handleConditionalQueryModification(defaultQuery);
history.push( history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent( `${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
stringifiedQuery, stringifiedQuery,
)}`, )}`,
); );
},
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [handleConditionalQueryModification, history]); [handleConditionalQueryModification, history],
);
const onCancel = (value: boolean) => (): void => { const onCancel = (value: boolean) => (): void => {
onModalToggle(value); onModalToggle(value);
if (isOneChartPerQuery) {
setQueryToExport(null);
}
}; };
const onAddToDashboard = (): void => { const onAddToDashboard = useCallback((): void => {
if (sourcepage === DataSource.TRACES) { if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Add to dashboard clicked', { logEvent('Traces Explorer: Add to dashboard clicked', {
panelType, panelType,
@ -208,7 +223,7 @@ function ExplorerOptions({
}); });
} }
setIsExport(true); setIsExport(true);
}; }, [isLogsExplorer, isMetricsExplorer, panelType, setIsExport, sourcepage]);
const { const {
data: viewsData, data: viewsData,
@ -616,6 +631,120 @@ function ExplorerOptions({
return 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar'; return 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar';
}, [isLogsExplorer, isMetricsExplorer]); }, [isLogsExplorer, isMetricsExplorer]);
const getQueryName = (query: Query): string => {
if (query.builder.queryFormulas.length > 0) {
return `Formula ${query.builder.queryFormulas[0].queryName}`;
}
return `Query ${query.builder.queryData[0].queryName}`;
};
const alertButton = useMemo(() => {
if (isOneChartPerQuery) {
const selectLabel = (
<Button
disabled={disabled}
shape="round"
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
);
return (
<Select
disabled={disabled}
className="multi-alert-button"
placeholder={selectLabel}
value={selectLabel}
suffixIcon={null}
onSelect={(e): void => {
const selectedQuery = splitedQueries.find(
(query) => query.id === ((e as unknown) as string),
);
if (selectedQuery) {
onCreateAlertsHandler(selectedQuery);
}
}}
>
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
);
}
return (
<Button
disabled={disabled}
shape="round"
onClick={(): void => onCreateAlertsHandler(query)}
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
);
}, [
disabled,
isOneChartPerQuery,
onCreateAlertsHandler,
query,
splitedQueries,
]);
const dashboardButton = useMemo(() => {
if (isOneChartPerQuery) {
const selectLabel = (
<Button
type="primary"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
);
return (
<Select
disabled={disabled}
className="multi-dashboard-button"
placeholder={selectLabel}
value={selectLabel}
suffixIcon={null}
onSelect={(e): void => {
const selectedQuery = splitedQueries.find(
(query) => query.id === ((e as unknown) as string),
);
if (selectedQuery) {
setQueryToExport(() => {
onAddToDashboard();
return selectedQuery;
});
}
}}
>
{/* eslint-disable-next-line sonarjs/no-identical-functions */}
{splitedQueries.map((splittedQuery) => (
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
{getQueryName(splittedQuery)}
</Select.Option>
))}
</Select>
);
}
return (
<Button
type="primary"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
);
}, [disabled, isOneChartPerQuery, onAddToDashboard, splitedQueries]);
return ( return (
<div className="explorer-options-container"> <div className="explorer-options-container">
{ {
@ -719,24 +848,8 @@ function ExplorerOptions({
<hr className={isEditDeleteSupported ? '' : 'hidden'} /> <hr className={isEditDeleteSupported ? '' : 'hidden'} />
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}> <div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
<Button {alertButton}
disabled={disabled} {dashboardButton}
shape="round"
onClick={onCreateAlertsHandler}
icon={<ConciergeBell size={16} />}
>
Create an Alert
</Button>
<Button
type="primary"
disabled={disabled}
shape="round"
onClick={onAddToDashboard}
icon={<Plus size={16} />}
>
Add to Dashboard
</Button>
</div> </div>
<div className="actions"> <div className="actions">
{/* Hide the info icon for metrics explorer until we get the docs link */} {/* Hide the info icon for metrics explorer until we get the docs link */}
@ -818,9 +931,15 @@ function ExplorerOptions({
destroyOnClose destroyOnClose
> >
<ExportPanelContainer <ExportPanelContainer
query={query} query={isOneChartPerQuery ? queryToExport : query}
isLoading={isLoading} isLoading={isLoading}
onExport={onExport} onExport={(dashboard, isNewDashboard): void => {
if (isOneChartPerQuery && queryToExport) {
onExport(dashboard, isNewDashboard, queryToExport);
} else {
onExport(dashboard, isNewDashboard);
}
}}
/> />
</Modal> </Modal>
</div> </div>
@ -829,18 +948,26 @@ function ExplorerOptions({
export interface ExplorerOptionsProps { export interface ExplorerOptionsProps {
isLoading?: boolean; isLoading?: boolean;
onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void; onExport: (
dashboard: Dashboard | null,
isNewDashboard?: boolean,
queryToExport?: Query,
) => void;
query: Query | null; query: Query | null;
disabled: boolean; disabled: boolean;
sourcepage: DataSource; sourcepage: DataSource;
isExplorerOptionHidden?: boolean; isExplorerOptionHidden?: boolean;
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>; setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
isOneChartPerQuery?: boolean;
splitedQueries?: Query[];
} }
ExplorerOptions.defaultProps = { ExplorerOptions.defaultProps = {
isLoading: false, isLoading: false,
isExplorerOptionHidden: false, isExplorerOptionHidden: false,
setIsExplorerOptionHidden: undefined, setIsExplorerOptionHidden: undefined,
isOneChartPerQuery: false,
splitedQueries: [],
}; };
export default ExplorerOptions; export default ExplorerOptions;

View File

@ -19,6 +19,7 @@ import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFall
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router-dom-v5-compat'; import { useSearchParams } from 'react-router-dom-v5-compat';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink'; import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
@ -26,6 +27,7 @@ import { v4 as uuid } from 'uuid';
import QuerySection from './QuerySection'; import QuerySection from './QuerySection';
import TimeSeries from './TimeSeries'; import TimeSeries from './TimeSeries';
import { ExplorerTabs } from './types'; import { ExplorerTabs } from './types';
import { splitQueryIntoOneChartPerQuery } from './utils';
const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled'; const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled';
@ -75,14 +77,18 @@ function Explorer(): JSX.Element {
useShareBuilderUrl(exportDefaultQuery); useShareBuilderUrl(exportDefaultQuery);
const handleExport = useCallback( const handleExport = useCallback(
(dashboard: Dashboard | null): void => { (
dashboard: Dashboard | null,
_isNewDashboard?: boolean,
queryToExport?: Query,
): void => {
if (!dashboard) return; if (!dashboard) return;
const widgetId = uuid(); const widgetId = uuid();
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery( const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
dashboard, dashboard,
exportDefaultQuery, queryToExport || exportDefaultQuery,
widgetId, widgetId,
PANEL_TYPES.TIME_SERIES, PANEL_TYPES.TIME_SERIES,
options.selectColumns, options.selectColumns,
@ -114,7 +120,7 @@ function Explorer(): JSX.Element {
return; return;
} }
const dashboardEditView = generateExportToDashboardLink({ const dashboardEditView = generateExportToDashboardLink({
query: exportDefaultQuery, query: queryToExport || exportDefaultQuery,
panelType: PANEL_TYPES.TIME_SERIES, panelType: PANEL_TYPES.TIME_SERIES,
dashboardId: data.payload?.uuid || '', dashboardId: data.payload?.uuid || '',
widgetId, widgetId,
@ -135,6 +141,14 @@ function Explorer(): JSX.Element {
[exportDefaultQuery, notifications, updateDashboard], [exportDefaultQuery, notifications, updateDashboard],
); );
const splitedQueries = useMemo(
() =>
splitQueryIntoOneChartPerQuery(
stagedQuery || initialQueriesMap[DataSource.METRICS],
),
[stagedQuery],
);
return ( return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}> <Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="metrics-explorer-explore-container"> <div className="metrics-explorer-explore-container">
@ -190,6 +204,8 @@ function Explorer(): JSX.Element {
isLoading={isLoading} isLoading={isLoading}
sourcepage={DataSource.METRICS} sourcepage={DataSource.METRICS}
onExport={handleExport} onExport={handleExport}
isOneChartPerQuery={showOneChartPerQuery}
splitedQueries={splitedQueries}
/> />
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
); );