Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 69 additions & 5 deletions static/app/views/explore/metrics/useSaveAsMetricItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import {
addSuccessMessage,
} from 'sentry/actionCreators/indicator';
import {openSaveQueryModal} from 'sentry/actionCreators/modal';
import {usePageFilters} from 'sentry/components/pageFilters/usePageFilters';
import {t} from 'sentry/locale';
import {defined} from 'sentry/utils';
import {trackAnalytics} from 'sentry/utils/analytics';
import {parseFunction, prettifyParsedFunction} from 'sentry/utils/discover/fields';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useProjects} from 'sentry/utils/useProjects';
import {Dataset, EventTypes} from 'sentry/views/alerts/rules/metric/types';
import {formatTraceMetricsFunction} from 'sentry/views/dashboards/datasetConfig/traceMetrics';
import {getIdFromLocation} from 'sentry/views/explore/contexts/pageParamsContext/id';
import {useGetSavedQuery} from 'sentry/views/explore/hooks/useGetSavedQueries';
Expand All @@ -21,26 +25,35 @@ import {useMultiMetricsQueryParams} from 'sentry/views/explore/metrics/multiMetr
import {
isVisualize,
isVisualizeEquation,
isVisualizeFunction,
} from 'sentry/views/explore/queryParams/visualize';
import {getVisualizeLabel} from 'sentry/views/explore/toolbar/toolbarVisualize';
import {TraceItemDataset} from 'sentry/views/explore/types';
import {getAlertsUrl} from 'sentry/views/insights/common/utils/getAlertsUrl';

import {canUseMetricsSavedQueriesUI} from './metricsFlags';
import {canUseMetricsAlertsUI, canUseMetricsSavedQueriesUI} from './metricsFlags';

interface UseSaveAsMetricItemsOptions {
interval: string;
}

export function useSaveAsMetricItems(_options: UseSaveAsMetricItemsOptions) {
export function useSaveAsMetricItems(options: UseSaveAsMetricItemsOptions) {
const location = useLocation();
const organization = useOrganization();
const {projects} = useProjects();
const pageFilters = usePageFilters();
const {saveQuery, updateQuery} = useSaveMetricsMultiQuery();
const id = getIdFromLocation(location);
const {data: savedQuery} = useGetSavedQuery(id);

const metricQueries = useMultiMetricsQueryParams();
const {addToDashboard} = useAddMetricToDashboard();

const project =
projects.length === 1
? projects[0]
: projects.find(p => p.id === `${pageFilters.selection.projects[0]}`);
Comment on lines +52 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: When "All Projects" is selected, the project variable becomes undefined, causing the alert creation URL to lack a pre-selected project, degrading the user experience.
Severity: LOW

Suggested Fix

Add an explicit check to handle the "All Projects" case. If pageFilters.selection.projects is empty or contains the sentinel value for all projects, do not attempt to find a specific project. Instead, allow the alert creation URL to be generated without a project, or implement logic to handle this case as intended, ensuring the user experience is not degraded.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/views/explore/metrics/useSaveAsMetricItems.tsx#L52-L55

Potential issue: When a user has "All Projects" or "My Projects" selected in the metrics
explore view, the logic to determine the current project results in an `undefined`
value. This is because `projects.find(p => p.id ===
`${pageFilters.selection.projects[0]}`)` fails when `pageFilters.selection.projects[0]`
is `undefined` or `-1`. While the downstream `getAlertsUrl()` function handles the
`undefined` project gracefully by generating a URL without a project slug, this leads to
a functional regression. The user is navigated to the alert creation page but must
manually re-select the project, losing the context from the explore view.

Did we get this right? 👍 / 👎 to inform future reviews.


const saveAsItems = useMemo(() => {
if (!canUseMetricsSavedQueriesUI(organization)) {
return [];
Expand Down Expand Up @@ -94,7 +107,58 @@ export function useSaveAsMetricItems(_options: UseSaveAsMetricItemsOptions) {
return items;
}, [id, savedQuery?.isPrebuilt, updateQuery, saveQuery, organization]);

// TODO: Implement alert functionality when organizations:tracemetrics-alerts flag is enabled
const saveAsAlertItems = useMemo(() => {
if (!canUseMetricsAlertsUI(organization)) {
return [];
}

const alertsUrls = metricQueries
.filter(mq => isVisualizeFunction(mq.queryParams.visualizes[0]!))
.map((metricQuery, index) => {
const visualize = metricQuery.queryParams.visualizes[0]!;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should prob fix this in another PR to not allow multiple if they are really 1:1

const yAxis = isVisualizeFunction(visualize) ? visualize.yAxis : '';
const func = parseFunction(yAxis);
const label = func ? prettifyParsedFunction(func) : yAxis;
const query = metricQuery.queryParams.query ?? '';

return {
key: `create-alert-${index}`,
label,
to: getAlertsUrl({
project,
query,
pageFilters: pageFilters.selection,
aggregate: yAxis,
organization,
dataset: Dataset.EVENTS_ANALYTICS_PLATFORM,
interval: options.interval,
eventTypes: [EventTypes.TRACE_ITEM_METRIC],
}),
onAction: () => {
trackAnalytics('metrics.save_as', {
save_type: 'alert',
ui_source: 'searchbar',
organization,
});
},
};
});

const newAlertLabel = organization.features.includes('workflow-engine-ui')
? t('Monitor for')
: t('Alert for');

return [
{
key: 'create-alert',
label: newAlertLabel,
textValue: newAlertLabel,
children: alertsUrls,
disabled: alertsUrls.length === 0,
isSubmenu: true,
},
];
}, [metricQueries, organization, project, pageFilters, options.interval]);

const addToDashboardItems = useMemo(() => {
return [
Expand Down Expand Up @@ -137,6 +201,6 @@ export function useSaveAsMetricItems(_options: UseSaveAsMetricItemsOptions) {
}, [addToDashboard, metricQueries]);

return useMemo(() => {
return [...saveAsItems, ...addToDashboardItems];
}, [saveAsItems, addToDashboardItems]);
return [...saveAsItems, ...saveAsAlertItems, ...addToDashboardItems];
}, [saveAsItems, saveAsAlertItems, addToDashboardItems]);
}
Loading