Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion static/app/views/dashboards/widgetCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ function WidgetCard(props: Props) {
const extractionStatus = useExtractionStatus({queryKey: widget});
const indexedEventsWarning = useIndexedEventsWarning();
const onDemandWarning = useOnDemandWarning({widget});
const transactionsDeprecationWarning = useTransactionsDeprecationWarning({widget});
const transactionsDeprecationWarning = useTransactionsDeprecationWarning({
widget,
selection,
});
const sessionDurationWarning = hasSessionDuration ? SESSION_DURATION_ALERT_TEXT : null;
const spanTimeRangeWarning = useTimeRangeWarning({widget});

Expand Down
61 changes: 48 additions & 13 deletions static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import {useMemo} from 'react';
import type {Location} from 'history';
import qs from 'query-string';

import {
openAddToDashboardModal,
Expand All @@ -15,6 +17,7 @@ import {
MEPState,
useMEPSettingContext,
} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
import {safeURL} from 'sentry/utils/url/safeURL';
import useOrganization from 'sentry/utils/useOrganization';
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
import {DashboardWidgetSource, WidgetType} from 'sentry/views/dashboards/types';
Expand All @@ -31,6 +34,7 @@ import {
} from 'sentry/views/dashboards/utils/getWidgetExploreUrl';
import {getReferrer} from 'sentry/views/dashboards/widgetCard/genericWidgetQueries';
import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
import {getExploreUrl} from 'sentry/views/explore/utils';

import {useDashboardsMEPContext} from './dashboardsMEPContext';

Expand All @@ -49,26 +53,57 @@ export const useIndexedEventsWarning = (): string | null => {

export const useTransactionsDeprecationWarning = ({
widget,
selection,
}: {
selection: PageFilters;
widget: Widget;
}): React.JSX.Element | null => {
const organization = useOrganization();

if (
organization.features.includes('transaction-widget-deprecation-explore-view') &&
widget.widgetType === WidgetType.TRANSACTIONS &&
widget.exploreUrls &&
widget.exploreUrls.length > 0
) {
return tct(
'Transactions widgets are in the process of being migrated to spans widgets. To see what your query could look like, open it in [explore:Explore].',
{
explore: <Link to={widget.exploreUrls[0]!} />,
}
);
// memoize the URL to avoid recalculating it on every render
const exploreUrl = useMemo(() => {
if (
!organization.features.includes('transaction-widget-deprecation-explore-view') ||
widget.widgetType !== WidgetType.TRANSACTIONS ||
!widget.exploreUrls ||
widget.exploreUrls.length === 0
) {
return null;
}
return createExploreUrl(widget.exploreUrls[0]!, selection, organization);
}, [organization, widget.widgetType, widget.exploreUrls, selection]);

if (!exploreUrl) {
return null;
}

return null;
return tct(
'Transactions widgets are in the process of being migrated to spans widgets. To see what your query could look like, open it in [explore:Explore].',
{
explore: <Link to={exploreUrl} />,
}
);
};

const createExploreUrl = (
baseUrl: string,
selection: PageFilters,
organization: Organization
): string => {
const parsedUrl = safeURL(baseUrl);
const queryParams = qs.parse(parsedUrl?.search ?? '');

if (queryParams.aggregateField) {
// we need to parse the aggregateField because it comes in stringified but needs to be passed in JSON format
if (typeof queryParams.aggregateField === 'string') {
queryParams.aggregateField = JSON.parse(queryParams.aggregateField);
} else if (Array.isArray(queryParams.aggregateField)) {
queryParams.aggregateField = queryParams.aggregateField.map(item =>
JSON.parse(item)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: JSON Parsing Errors in createExploreUrl

The createExploreUrl function calls JSON.parse() on aggregateField values without validation or error handling. This can lead to runtime errors and component crashes if the aggregateField parameter contains malformed JSON strings or if array elements are not strings.

Fix in Cursor Fix in Web

}
return getExploreUrl({organization, selection, ...queryParams});
};

export function getMenuOptions(
Expand Down
Loading