Skip to content
Open
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
65 changes: 52 additions & 13 deletions static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {useMemo} from 'react';
import type {Location} from 'history';

import {
Expand All @@ -11,6 +12,7 @@ import {t, tct} from 'sentry/locale';
import type {PageFilters} from 'sentry/types/core';
import type {Organization} from 'sentry/types/organization';
import {trackAnalytics} from 'sentry/utils/analytics';
import {getUtcDateString} from 'sentry/utils/dates';
import {
MEPState,
useMEPSettingContext,
Expand Down Expand Up @@ -49,26 +51,63 @@ 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.features, widget.widgetType, widget.exploreUrls, selection]);

if (!exploreUrl) {
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): string => {
const params: string[] = [];

if (selection.datetime?.start) {
params.push(`start=${getUtcDateString(selection.datetime.start)}`);
}
if (selection.datetime?.end) {
params.push(`end=${getUtcDateString(selection.datetime.end)}`);
}
if (selection.datetime?.period) {
params.push(`statsPeriod=${selection.datetime.period}`);
}
if (selection.datetime?.utc) {
params.push('utc=true');
}

selection.projects.forEach(project => {
params.push(`project=${project}`);
});

selection.environments.forEach(environment => {
params.push(`environment=${environment}`);
});
Comment on lines +89 to +108
Copy link
Member

@shruthilayaj shruthilayaj Oct 8, 2025

Choose a reason for hiding this comment

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

this feels a little janky to me, could we instead parse this URL, something like

const parsedURL = safeURL(baseUrl);
const queryParams = qs.parse(parsedURL?.search ?? '');

// append all the page filters to queryParams - perhaps use an existing util like pageFiltersToQueryParams
return  getExploreUrl(queryParams)


Comment on lines +99 to 109
Copy link
Contributor

Choose a reason for hiding this comment

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

Potential bug: URL parameters are not encoded, leading to broken links for environment names with special characters.
  • Description: The createExploreUrl function constructs URL parameters without encoding them. Since environment names can contain special characters like '&', '=', or spaces, concatenating them directly into the URL string will result in malformed and broken URLs. This impacts the functionality of the 'Explore' links for users with such environment names.

  • Suggested fix: Use encodeURIComponent on the values of the parameters before adding them to the URL string. For example, change params.push(environment=${environment}); to params.push(environment=${encodeURIComponent(environment)});.
    severity: 3.0, confidence: 5.0

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

return null;
return params.length > 0 ? `${baseUrl}&${params.join('&')}` : baseUrl;
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: URL Construction Fails Without Existing Query

The createExploreUrl function constructs invalid URLs because it always appends query parameters with &. When the baseUrl doesn't already have a query string, this results in an improperly formatted URL.

Fix in Cursor Fix in Web

};

export function getMenuOptions(
Expand Down
Loading