mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 22:59:01 +08:00
chore: add to alerts/dashboard improvements for one chart per query mode in metrics explorer (#8014)
This commit is contained in:
parent
0925ae73a9
commit
3ca3db2567
@ -14,6 +14,8 @@ function ExplorerOptionWrapper({
|
||||
isLoading,
|
||||
onExport,
|
||||
sourcepage,
|
||||
isOneChartPerQuery,
|
||||
splitedQueries,
|
||||
}: ExplorerOptionsWrapperProps): JSX.Element {
|
||||
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
|
||||
|
||||
@ -32,6 +34,8 @@ function ExplorerOptionWrapper({
|
||||
sourcepage={sourcepage}
|
||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||
isOneChartPerQuery={isOneChartPerQuery}
|
||||
splitedQueries={splitedQueries}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,21 @@
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
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 {
|
||||
|
@ -90,6 +90,8 @@ function ExplorerOptions({
|
||||
sourcepage,
|
||||
isExplorerOptionHidden = false,
|
||||
setIsExplorerOptionHidden,
|
||||
isOneChartPerQuery = false,
|
||||
splitedQueries = [],
|
||||
}: ExplorerOptionsProps): JSX.Element {
|
||||
const [isExport, setIsExport] = useState<boolean>(false);
|
||||
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
|
||||
@ -99,6 +101,8 @@ function ExplorerOptions({
|
||||
const history = useHistory();
|
||||
const ref = useRef<RefSelectProps>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [queryToExport, setQueryToExport] = useState<Query | null>(null);
|
||||
|
||||
const isLogsExplorer = sourcepage === DataSource.LOGS;
|
||||
const isMetricsExplorer = sourcepage === DataSource.METRICS;
|
||||
|
||||
@ -149,51 +153,62 @@ function ExplorerOptions({
|
||||
|
||||
const { user } = useAppContext();
|
||||
|
||||
const handleConditionalQueryModification = useCallback((): string => {
|
||||
if (
|
||||
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP
|
||||
) {
|
||||
return JSON.stringify(query);
|
||||
}
|
||||
const handleConditionalQueryModification = useCallback(
|
||||
(defaultQuery: Query | null): string => {
|
||||
const queryToUse = defaultQuery || query;
|
||||
if (
|
||||
queryToUse?.builder?.queryData?.[0]?.aggregateOperator !==
|
||||
StringOperators.NOOP
|
||||
) {
|
||||
return JSON.stringify(queryToUse);
|
||||
}
|
||||
|
||||
// Modify aggregateOperator to count, as noop is not supported in alerts
|
||||
const modifiedQuery = cloneDeep(query);
|
||||
// Modify aggregateOperator to count, as noop is not supported in alerts
|
||||
const modifiedQuery = cloneDeep(queryToUse);
|
||||
|
||||
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
|
||||
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
|
||||
|
||||
return JSON.stringify(modifiedQuery);
|
||||
}, [query]);
|
||||
return JSON.stringify(modifiedQuery);
|
||||
},
|
||||
[query],
|
||||
);
|
||||
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
if (sourcepage === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
} else if (isLogsExplorer) {
|
||||
logEvent('Logs Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
} else if (isMetricsExplorer) {
|
||||
logEvent('Metrics Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
}
|
||||
const onCreateAlertsHandler = useCallback(
|
||||
(defaultQuery: Query | null) => {
|
||||
if (sourcepage === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
} else if (isLogsExplorer) {
|
||||
logEvent('Logs Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
} else if (isMetricsExplorer) {
|
||||
logEvent('Metrics Explorer: Create alert', {
|
||||
panelType,
|
||||
});
|
||||
}
|
||||
|
||||
const stringifiedQuery = handleConditionalQueryModification();
|
||||
const stringifiedQuery = handleConditionalQueryModification(defaultQuery);
|
||||
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
stringifiedQuery,
|
||||
)}`,
|
||||
);
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
stringifiedQuery,
|
||||
)}`,
|
||||
);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [handleConditionalQueryModification, history]);
|
||||
[handleConditionalQueryModification, history],
|
||||
);
|
||||
|
||||
const onCancel = (value: boolean) => (): void => {
|
||||
onModalToggle(value);
|
||||
if (isOneChartPerQuery) {
|
||||
setQueryToExport(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onAddToDashboard = (): void => {
|
||||
const onAddToDashboard = useCallback((): void => {
|
||||
if (sourcepage === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Add to dashboard clicked', {
|
||||
panelType,
|
||||
@ -208,7 +223,7 @@ function ExplorerOptions({
|
||||
});
|
||||
}
|
||||
setIsExport(true);
|
||||
};
|
||||
}, [isLogsExplorer, isMetricsExplorer, panelType, setIsExport, sourcepage]);
|
||||
|
||||
const {
|
||||
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';
|
||||
}, [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 (
|
||||
<div className="explorer-options-container">
|
||||
{
|
||||
@ -719,24 +848,8 @@ function ExplorerOptions({
|
||||
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
||||
|
||||
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
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>
|
||||
{alertButton}
|
||||
{dashboardButton}
|
||||
</div>
|
||||
<div className="actions">
|
||||
{/* Hide the info icon for metrics explorer until we get the docs link */}
|
||||
@ -818,9 +931,15 @@ function ExplorerOptions({
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
query={isOneChartPerQuery ? queryToExport : query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
onExport={(dashboard, isNewDashboard): void => {
|
||||
if (isOneChartPerQuery && queryToExport) {
|
||||
onExport(dashboard, isNewDashboard, queryToExport);
|
||||
} else {
|
||||
onExport(dashboard, isNewDashboard);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
@ -829,18 +948,26 @@ function ExplorerOptions({
|
||||
|
||||
export interface ExplorerOptionsProps {
|
||||
isLoading?: boolean;
|
||||
onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void;
|
||||
onExport: (
|
||||
dashboard: Dashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void;
|
||||
query: Query | null;
|
||||
disabled: boolean;
|
||||
sourcepage: DataSource;
|
||||
isExplorerOptionHidden?: boolean;
|
||||
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
||||
isOneChartPerQuery?: boolean;
|
||||
splitedQueries?: Query[];
|
||||
}
|
||||
|
||||
ExplorerOptions.defaultProps = {
|
||||
isLoading: false,
|
||||
isExplorerOptionHidden: false,
|
||||
setIsExplorerOptionHidden: undefined,
|
||||
isOneChartPerQuery: false,
|
||||
splitedQueries: [],
|
||||
};
|
||||
|
||||
export default ExplorerOptions;
|
||||
|
@ -19,6 +19,7 @@ import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFall
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@ -26,6 +27,7 @@ import { v4 as uuid } from 'uuid';
|
||||
import QuerySection from './QuerySection';
|
||||
import TimeSeries from './TimeSeries';
|
||||
import { ExplorerTabs } from './types';
|
||||
import { splitQueryIntoOneChartPerQuery } from './utils';
|
||||
|
||||
const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled';
|
||||
|
||||
@ -75,14 +77,18 @@ function Explorer(): JSX.Element {
|
||||
useShareBuilderUrl(exportDefaultQuery);
|
||||
|
||||
const handleExport = useCallback(
|
||||
(dashboard: Dashboard | null): void => {
|
||||
(
|
||||
dashboard: Dashboard | null,
|
||||
_isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
): void => {
|
||||
if (!dashboard) return;
|
||||
|
||||
const widgetId = uuid();
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
exportDefaultQuery,
|
||||
queryToExport || exportDefaultQuery,
|
||||
widgetId,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
options.selectColumns,
|
||||
@ -114,7 +120,7 @@ function Explorer(): JSX.Element {
|
||||
return;
|
||||
}
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query: exportDefaultQuery,
|
||||
query: queryToExport || exportDefaultQuery,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
@ -135,6 +141,14 @@ function Explorer(): JSX.Element {
|
||||
[exportDefaultQuery, notifications, updateDashboard],
|
||||
);
|
||||
|
||||
const splitedQueries = useMemo(
|
||||
() =>
|
||||
splitQueryIntoOneChartPerQuery(
|
||||
stagedQuery || initialQueriesMap[DataSource.METRICS],
|
||||
),
|
||||
[stagedQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<div className="metrics-explorer-explore-container">
|
||||
@ -190,6 +204,8 @@ function Explorer(): JSX.Element {
|
||||
isLoading={isLoading}
|
||||
sourcepage={DataSource.METRICS}
|
||||
onExport={handleExport}
|
||||
isOneChartPerQuery={showOneChartPerQuery}
|
||||
splitedQueries={splitedQueries}
|
||||
/>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user