mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:45:59 +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,
|
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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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,22 +153,28 @@ function ExplorerOptions({
|
|||||||
|
|
||||||
const { user } = useAppContext();
|
const { user } = useAppContext();
|
||||||
|
|
||||||
const handleConditionalQueryModification = useCallback((): string => {
|
const handleConditionalQueryModification = useCallback(
|
||||||
|
(defaultQuery: Query | null): string => {
|
||||||
|
const queryToUse = defaultQuery || query;
|
||||||
if (
|
if (
|
||||||
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP
|
queryToUse?.builder?.queryData?.[0]?.aggregateOperator !==
|
||||||
|
StringOperators.NOOP
|
||||||
) {
|
) {
|
||||||
return JSON.stringify(query);
|
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(
|
||||||
|
(defaultQuery: Query | null) => {
|
||||||
if (sourcepage === DataSource.TRACES) {
|
if (sourcepage === DataSource.TRACES) {
|
||||||
logEvent('Traces Explorer: Create alert', {
|
logEvent('Traces Explorer: Create alert', {
|
||||||
panelType,
|
panelType,
|
||||||
@ -179,21 +189,26 @@ function ExplorerOptions({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user