Skip to content

fix(dashboards): Apply dashboard filters to open links #91798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function renderModal({
organization={initialData.organization}
widget={widget}
api={api}
dashboardFilters={undefined}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import type {Organization} from 'sentry/types/organization';
import {trackAnalytics} from 'sentry/utils/analytics';
import withApi from 'sentry/utils/withApi';
import withPageFilters from 'sentry/utils/withPageFilters';
import type {Widget} from 'sentry/views/dashboards/types';
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
import {getWidgetDiscoverUrl} from 'sentry/views/dashboards/utils';

export type DashboardWidgetQuerySelectorModalOptions = {
dashboardFilters: DashboardFilters | undefined;
organization: Organization;
widget: Widget;
isMetricsData?: boolean;
Expand All @@ -32,7 +33,8 @@ type Props = ModalRenderProps &
};

function DashboardWidgetQuerySelectorModal(props: Props) {
const {organization, widget, selection, isMetricsData, Body, Header} = props;
const {organization, widget, selection, isMetricsData, Body, Header, dashboardFilters} =
props;

const renderQueries = () => {
const querySearchBars = widget.queries.map((query, index) => {
Expand All @@ -41,6 +43,7 @@ function DashboardWidgetQuerySelectorModal(props: Props) {
...widget,
queries: [query],
},
dashboardFilters,
selection,
organization,
0,
Expand Down
2 changes: 1 addition & 1 deletion static/app/components/modals/widgetViewerModal.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1315,7 +1315,7 @@ describe('Modals -> WidgetViewerModal', function () {
await renderModal({initialData, widget: mockWidget});
expect(screen.getByRole('button', {name: 'Open in Releases'})).toHaveAttribute(
'href',
'/organizations/org-slug/releases/?environment=prod&environment=dev&project=1&project=2&statsPeriod=24h'
'/organizations/org-slug/releases/?environment=prod&environment=dev&project=1&project=2&query=&statsPeriod=24h'
);
});

Expand Down
10 changes: 7 additions & 3 deletions static/app/components/modals/widgetViewerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,7 @@ function WidgetViewerModal(props: Props) {
{widget.widgetType && (
<OpenButton
widget={primaryWidget}
dashboardFilters={dashboardFilters}
organization={organization}
selection={modalSelection}
selectedQueryIndex={selectedQueryIndex}
Expand All @@ -1077,6 +1078,7 @@ function WidgetViewerModal(props: Props) {
}

interface OpenButtonProps {
dashboardFilters: DashboardFilters | undefined;
organization: Organization;
selectedQueryIndex: number;
selection: PageFilters;
Expand All @@ -1087,6 +1089,7 @@ interface OpenButtonProps {

function OpenButton({
widget,
dashboardFilters,
selection,
organization,
selectedQueryIndex,
Expand All @@ -1100,21 +1103,22 @@ function OpenButton({
switch (widget.widgetType) {
case WidgetType.ISSUE:
openLabel = t('Open in Issues');
path = getWidgetIssueUrl(widget, selection, organization);
path = getWidgetIssueUrl(widget, dashboardFilters, selection, organization);
break;
case WidgetType.RELEASE:
openLabel = t('Open in Releases');
path = getWidgetReleasesUrl(widget, selection, organization);
path = getWidgetReleasesUrl(widget, dashboardFilters, selection, organization);
break;
case WidgetType.SPANS:
openLabel = t('Open in Explore');
path = getWidgetExploreUrl(widget, selection, organization);
path = getWidgetExploreUrl(widget, dashboardFilters, selection, organization);
break;
case WidgetType.DISCOVER:
default:
openLabel = t('Open in Discover');
path = getWidgetDiscoverUrl(
{...widget, queries: [widget.queries[selectedQueryIndex]!]},
dashboardFilters,
selection,
organization,
0,
Expand Down
56 changes: 53 additions & 3 deletions static/app/views/dashboards/utils.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,12 @@ describe('Dashboards util', () => {
};
});
it('returns the discover url of the widget query', () => {
const url = getWidgetDiscoverUrl(widget, selection, OrganizationFixture());
const url = getWidgetDiscoverUrl(
widget,
undefined,
selection,
OrganizationFixture()
);
expect(url).toBe(
'/organizations/org-slug/discover/results/?field=count%28%29&name=Test%20Query&query=&statsPeriod=7d&yAxis=count%28%29'
);
Expand All @@ -191,11 +196,45 @@ describe('Dashboards util', () => {
],
},
};
const url = getWidgetDiscoverUrl(widget, selection, OrganizationFixture());
const url = getWidgetDiscoverUrl(
widget,
undefined,
selection,
OrganizationFixture()
);
expect(url).toBe(
'/organizations/org-slug/discover/results/?display=top5&field=error.type&field=count%28%29&name=Test%20Query&query=error.unhandled%3Atrue&sort=-count&statsPeriod=7d&yAxis=count%28%29'
);
});
it('applies the dashboard filters to the query', () => {
widget = {
...widget,
...{
displayType: DisplayType.LINE,
queries: [
{
name: '',
conditions: 'transaction.op:test',
fields: [],
aggregates: [],
columns: [],
orderby: '',
},
],
},
};
const url = getWidgetDiscoverUrl(
widget,
{release: ['1.0.0', '2.0.0']},
selection,
OrganizationFixture()
);
const queryString = url.split('?')[1];
const urlParams = new URLSearchParams(queryString);
expect(urlParams.get('query')).toBe(
'(transaction.op:test) release:["1.0.0","2.0.0"] '
);
});
});
describe('getWidgetIssueUrl', function () {
let widget!: Widget;
Expand All @@ -218,11 +257,22 @@ describe('Dashboards util', () => {
};
});
it('returns the issue url of the widget query', () => {
const url = getWidgetIssueUrl(widget, selection, OrganizationFixture());
const url = getWidgetIssueUrl(widget, undefined, selection, OrganizationFixture());
expect(url).toBe(
'/organizations/org-slug/issues/?query=is%3Aunresolved&sort=date&statsPeriod=7d'
);
});
it('applies the dashboard filters to the query', () => {
const url = getWidgetIssueUrl(
widget,
{release: ['1.0.0', '2.0.0']},
selection,
OrganizationFixture()
);
const queryString = url.split('?')[1];
const urlParams = new URLSearchParams(queryString);
expect(urlParams.get('query')).toBe('(is:unresolved) release:["1.0.0","2.0.0"] ');
});
});

describe('flattenErrors', function () {
Expand Down
24 changes: 23 additions & 1 deletion static/app/views/dashboards/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export function getFieldsFromEquations(fields: string[]): string[] {

export function getWidgetDiscoverUrl(
widget: Widget,
dashboardFilters: DashboardFilters | undefined,
selection: PageFilters,
organization: Organization,
index = 0,
Expand Down Expand Up @@ -304,6 +305,10 @@ export function getWidgetDiscoverUrl(
}
});
discoverLocation.query.field = fields;
discoverLocation.query.query = applyDashboardFilters(
query.conditions,
dashboardFilters
);

if (isMetricsData) {
discoverLocation.query.fromMetric = 'true';
Expand All @@ -318,6 +323,7 @@ export function getWidgetDiscoverUrl(

export function getWidgetIssueUrl(
widget: Widget,
dashboardFilters: DashboardFilters | undefined,
selection: PageFilters,
organization: Organization
) {
Expand All @@ -327,7 +333,7 @@ export function getWidgetIssueUrl(
? {start: getUtcDateString(start), end: getUtcDateString(end), utc}
: {statsPeriod: period};
const issuesLocation = `/organizations/${organization.slug}/issues/?${qs.stringify({
query: widget.queries?.[0]?.conditions,
query: applyDashboardFilters(widget.queries?.[0]?.conditions, dashboardFilters),
sort: widget.queries?.[0]?.orderby,
...datetime,
project: selection.projects,
Expand All @@ -338,6 +344,7 @@ export function getWidgetIssueUrl(

export function getWidgetReleasesUrl(
_widget: Widget,
dashboardFilters: DashboardFilters | undefined,
selection: PageFilters,
organization: Organization
) {
Expand All @@ -348,6 +355,7 @@ export function getWidgetReleasesUrl(
: {statsPeriod: period};
const releasesLocation = `/organizations/${organization.slug}/releases/?${qs.stringify({
...datetime,
query: applyDashboardFilters('', dashboardFilters),
project: selection.projects,
environment: selection.environments,
})}`;
Expand Down Expand Up @@ -622,3 +630,17 @@ function _doesWidgetUsePerformanceScore(query: WidgetQuery) {
}

export const performanceScoreTooltip = t('peformance_score is not supported in Discover');

export function applyDashboardFilters(
baseQuery: string | undefined,
dashboardFilters: DashboardFilters | undefined
): string | undefined {
const dashboardFilterConditions = dashboardFiltersToString(dashboardFilters);
if (dashboardFilterConditions) {
if (baseQuery) {
return `(${baseQuery}) ${dashboardFilterConditions}`;
}
return dashboardFilterConditions;
}
return baseQuery;
}
40 changes: 35 additions & 5 deletions static/app/views/dashboards/utils/getWidgetExploreUrl.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('getWidgetExploreUrl', () => {
],
});

const url = getWidgetExploreUrl(widget, selection, organization);
const url = getWidgetExploreUrl(widget, undefined, selection, organization);

// Note: for table widgets the mode is set to samples and the fields are propagated
expect(url).toBe(
Expand All @@ -47,7 +47,7 @@ describe('getWidgetExploreUrl', () => {
],
});

const url = getWidgetExploreUrl(widget, selection, organization);
const url = getWidgetExploreUrl(widget, undefined, selection, organization);

// Note: for table widgets the mode is set to samples and the fields are propagated
expect(url).toBe(
Expand All @@ -70,7 +70,7 @@ describe('getWidgetExploreUrl', () => {
],
});

const url = getWidgetExploreUrl(widget, selection, organization);
const url = getWidgetExploreUrl(widget, undefined, selection, organization);

// Note: for line widgets the mode is set to aggregate
// The chart type is set to 1 for area charts
Expand All @@ -94,7 +94,7 @@ describe('getWidgetExploreUrl', () => {
],
});

const url = getWidgetExploreUrl(widget, selection, organization);
const url = getWidgetExploreUrl(widget, undefined, selection, organization);

// Note: for line widgets the mode is set to aggregate
// The chart type is set to 1 for area charts
Expand All @@ -119,11 +119,41 @@ describe('getWidgetExploreUrl', () => {
],
});

const url = getWidgetExploreUrl(widget, selection, organization);
const url = getWidgetExploreUrl(widget, undefined, selection, organization);

// The URL should have the sort and another visualize to plot the sort
expect(url).toBe(
'/organizations/org-slug/traces/?groupBy=span.description&interval=30m&mode=aggregate&sort=-count%28span.duration%29&statsPeriod=14d&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22avg%28span.duration%29%22%5D%7D&visualize=%7B%22chartType%22%3A1%2C%22yAxes%22%3A%5B%22count%28span.duration%29%22%5D%7D'
);
});

it('applies the dashboard filters to the query', () => {
const widget = WidgetFixture({
displayType: DisplayType.LINE,
queries: [
{
fields: [],
aggregates: ['avg(span.duration)'],
columns: ['span.description'],
conditions: 'span.description:test',
orderby: '-avg(span.duration)',
name: '',
},
],
});

const url = getWidgetExploreUrl(
widget,
{
release: ['1.0.0', '2.0.0'],
},
selection,
organization
);

// Assert that the query contains the dashboard filters in its resulting URL
expect(url).toContain(
'&query=%28span.description%3Atest%29%20release%3A%5B%221.0.0%22%2C%222.0.0%22%5D%20'
);
});
});
10 changes: 8 additions & 2 deletions static/app/views/dashboards/utils/getWidgetExploreUrl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
isAggregateFieldOrEquation,
} from 'sentry/utils/discover/fields';
import {decodeBoolean, decodeList, decodeScalar} from 'sentry/utils/queryString';
import {DisplayType, type Widget} from 'sentry/views/dashboards/types';
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
import {DisplayType} from 'sentry/views/dashboards/types';
import {
applyDashboardFilters,
eventViewFromWidget,
getFieldsFromEquations,
getWidgetInterval,
Expand All @@ -21,6 +23,7 @@ import {ChartType} from 'sentry/views/insights/common/components/chart';

export function getWidgetExploreUrl(
widget: Widget,
dashboardFilters: DashboardFilters | undefined,
selection: PageFilters,
organization: Organization
) {
Expand Down Expand Up @@ -138,7 +141,10 @@ export function getWidgetExploreUrl(
],
groupBy: exploreMode === Mode.SAMPLES ? undefined : groupBy,
field: exploreMode === Mode.SAMPLES ? decodeList(queryFields) : undefined,
query: decodeScalar(locationQueryParams.query),
query: applyDashboardFilters(
decodeScalar(locationQueryParams.query),
dashboardFilters
),
sort: locationQueryParams.sort || undefined,
interval:
decodeScalar(locationQueryParams.interval) ??
Expand Down
1 change: 1 addition & 0 deletions static/app/views/dashboards/widgetCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ function WidgetCard(props: Props) {

const actions = props.showContextMenu
? getMenuOptions(
dashboardFilters,
organization,
selection,
widget,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('WidgetCardContextMenu', () => {
<MEPSettingProvider>
<DashboardsMEPProvider>
<WidgetCardContextMenu
dashboardFilters={undefined}
location={LocationFixture()}
organization={OrganizationFixture({
features: ['discover-basic'],
Expand Down Expand Up @@ -66,6 +67,7 @@ describe('WidgetCardContextMenu', () => {
<MEPSettingProvider>
<DashboardsMEPProvider>
<WidgetCardContextMenu
dashboardFilters={undefined}
location={LocationFixture()}
organization={OrganizationFixture({
features: ['discover-basic', 'dashboards-edit'],
Expand Down Expand Up @@ -93,6 +95,7 @@ describe('WidgetCardContextMenu', () => {
<MEPSettingProvider>
<DashboardsMEPProvider>
<WidgetCardContextMenu
dashboardFilters={undefined}
location={LocationFixture()}
organization={OrganizationFixture({})}
router={RouterFixture()}
Expand Down
Loading
Loading