diff --git a/static/app/views/dashboards/types.tsx b/static/app/views/dashboards/types.tsx
index d4f4ba45f8589b..2f0298193fbf46 100644
--- a/static/app/views/dashboards/types.tsx
+++ b/static/app/views/dashboards/types.tsx
@@ -94,11 +94,24 @@ export type WidgetQuery = {
selectedAggregate?: number;
};
+type WidgetChangedReason = {
+ equations: Array<{
+ equation: string;
+ reason: string | string[];
+ }> | null;
+ orderby: Array<{
+ orderby: string;
+ reason: string;
+ }> | null;
+ selected_columns: string[];
+};
+
export type Widget = {
displayType: DisplayType;
interval: string;
queries: WidgetQuery[];
title: string;
+ changedReason?: WidgetChangedReason[];
dashboardId?: string;
datasetSource?: DatasetSource;
description?: string;
diff --git a/static/app/views/dashboards/widgetCard/index.tsx b/static/app/views/dashboards/widgetCard/index.tsx
index d678fde93d631d..718ed5deff7078 100644
--- a/static/app/views/dashboards/widgetCard/index.tsx
+++ b/static/app/views/dashboards/widgetCard/index.tsx
@@ -47,6 +47,7 @@ import {WidgetViewerContext} from 'sentry/views/dashboards/widgetViewer/widgetVi
import {useDashboardsMEPContext} from './dashboardsMEPContext';
import {
getMenuOptions,
+ useDroppedColumnsWarning,
useIndexedEventsWarning,
useTransactionsDeprecationWarning,
} from './widgetCardContextMenu';
@@ -195,6 +196,7 @@ function WidgetCard(props: Props) {
widget,
selection,
});
+ const droppedColumnsWarning = useDroppedColumnsWarning(widget);
const sessionDurationWarning = hasSessionDuration ? SESSION_DURATION_ALERT_TEXT : null;
const spanTimeRangeWarning = useTimeRangeWarning({widget});
@@ -266,6 +268,7 @@ function WidgetCard(props: Props) {
sessionDurationWarning,
spanTimeRangeWarning,
transactionsDeprecationWarning,
+ droppedColumnsWarning,
].filter(Boolean) as string[];
const actionsDisabled = Boolean(props.isPreview);
diff --git a/static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx b/static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx
index 561b2e1b21a825..cba09e92566b0a 100644
--- a/static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx
+++ b/static/app/views/dashboards/widgetCard/widgetCardContextMenu.tsx
@@ -1,4 +1,5 @@
import {useMemo} from 'react';
+import styled from '@emotion/styled';
import type {Location} from 'history';
import qs from 'query-string';
@@ -8,6 +9,7 @@ import {
} from 'sentry/actionCreators/modal';
import {openConfirmModal} from 'sentry/components/confirm';
import {Link} from 'sentry/components/core/link';
+import {Text} from 'sentry/components/core/text';
import type {MenuItemProps} from 'sentry/components/dropdownMenu';
import {t, tct} from 'sentry/locale';
import type {PageFilters} from 'sentry/types/core';
@@ -106,6 +108,70 @@ const createExploreUrl = (
return getExploreUrl({organization, selection, ...queryParams});
};
+export const useDroppedColumnsWarning = (widget: Widget): React.JSX.Element | null => {
+ if (!widget.changedReason) {
+ return null;
+ }
+
+ const baseWarning = t(
+ "This widget may look different from its original query. Here's why:"
+ );
+ const columnsWarning = [];
+ const equationsWarning = [];
+ const orderbyWarning = [];
+ for (const changedReason of widget.changedReason) {
+ if (changedReason.selected_columns.length > 0) {
+ columnsWarning.push(
+ tct(`The following fields were dropped: [columns].`, {
+ columns: changedReason.selected_columns.join(', '),
+ })
+ );
+ }
+ if (changedReason.equations) {
+ equationsWarning.push(
+ ...changedReason.equations.map(equation =>
+ tct(`[equation] was dropped because [reason] is unsupported.`, {
+ equation: equation.equation,
+ reason:
+ typeof equation.reason === 'string'
+ ? equation.reason
+ : equation.reason.join(', '),
+ })
+ )
+ );
+ }
+ if (changedReason.orderby) {
+ orderbyWarning.push(
+ ...changedReason.orderby.map(equation =>
+ tct(`[orderby] was dropped because [reason].`, {
+ orderby: equation.orderby,
+ reason: equation.reason,
+ })
+ )
+ );
+ }
+ }
+
+ const allWarnings = [...columnsWarning, ...equationsWarning, ...orderbyWarning];
+
+ if (allWarnings.length > 0) {
+ return (
+
+ {baseWarning}
+ {allWarnings.map((warning, index) => (
+ {warning}
+ ))}
+
+ );
+ }
+
+ return null;
+};
+
+const StyledText = styled(Text)`
+ padding-bottom: ${p => p.theme.space.xs};
+`;
+
export function getMenuOptions(
dashboardFilters: DashboardFilters | undefined,
organization: Organization,
diff --git a/static/app/views/explore/hooks/useGetSavedQueries.tsx b/static/app/views/explore/hooks/useGetSavedQueries.tsx
index ba4a2696bf90e8..c6c54b6acefe19 100644
--- a/static/app/views/explore/hooks/useGetSavedQueries.tsx
+++ b/static/app/views/explore/hooks/useGetSavedQueries.tsx
@@ -6,6 +6,7 @@ import {defined} from 'sentry/utils';
import {useApiQuery, useQueryClient} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';
import type {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
+import type {ExploreQueryChangedReason} from 'sentry/views/explore/hooks/useSaveQuery';
import {TraceItemDataset} from 'sentry/views/explore/types';
export type RawGroupBy = {
@@ -100,6 +101,7 @@ type ReadableSavedQuery = {
projects: number[];
query: [ReadableQuery, ...ReadableQuery[]];
starred: boolean;
+ changedReason?: ExploreQueryChangedReason | null;
createdBy?: User;
end?: string;
environment?: string[];
@@ -120,6 +122,7 @@ export class SavedQuery {
query: [SavedQueryQuery, ...SavedQueryQuery[]];
dataset: ReadableSavedQuery['dataset'];
starred: boolean;
+ changedReason?: ExploreQueryChangedReason | null;
createdBy?: User;
end?: string | DateString;
environment?: string[];
@@ -128,6 +131,7 @@ export class SavedQuery {
start?: string | DateString;
constructor(savedQuery: ReadableSavedQuery) {
+ this.changedReason = savedQuery.changedReason;
this.dateAdded = savedQuery.dateAdded;
this.dateUpdated = savedQuery.dateUpdated;
this.id = savedQuery.id;
diff --git a/static/app/views/explore/hooks/useSaveQuery.tsx b/static/app/views/explore/hooks/useSaveQuery.tsx
index 7f49987bd96456..4eda739f9a6ca4 100644
--- a/static/app/views/explore/hooks/useSaveQuery.tsx
+++ b/static/app/views/explore/hooks/useSaveQuery.tsx
@@ -22,11 +22,24 @@ import {isGroupBy} from 'sentry/views/explore/queryParams/groupBy';
import type {ReadableQueryParams} from 'sentry/views/explore/queryParams/readableQueryParams';
import {isVisualize} from 'sentry/views/explore/queryParams/visualize';
+export type ExploreQueryChangedReason = {
+ columns: string[];
+ equations: Array<{
+ equation: string;
+ reason: string | string[];
+ }> | null;
+ orderby: Array<{
+ orderby: string;
+ reason: string;
+ }> | null;
+};
+
// Request payload type that matches the backend ExploreSavedQuerySerializer
type ExploreSavedQueryRequest = {
dataset: 'logs' | 'spans' | 'segment_spans';
name: string;
projects: number[];
+ changedReason?: ExploreQueryChangedReason;
end?: DateString;
environment?: string[];
interval?: string;
diff --git a/static/app/views/explore/spans/droppedFieldsAlert.tsx b/static/app/views/explore/spans/droppedFieldsAlert.tsx
new file mode 100644
index 00000000000000..b3fb4b9863a89b
--- /dev/null
+++ b/static/app/views/explore/spans/droppedFieldsAlert.tsx
@@ -0,0 +1,86 @@
+import styled from '@emotion/styled';
+
+import {Alert} from 'sentry/components/core/alert';
+import {Text} from 'sentry/components/core/text';
+import List from 'sentry/components/list';
+import ListItem from 'sentry/components/list/listItem';
+import {t, tct} from 'sentry/locale';
+import {useExploreId} from 'sentry/views/explore/contexts/pageParamsContext';
+import {useGetSavedQuery} from 'sentry/views/explore/hooks/useGetSavedQueries';
+
+export function DroppedFieldsAlert(): React.JSX.Element | null {
+ const id = useExploreId();
+ const {data: savedQuery} = useGetSavedQuery(id);
+
+ if (!savedQuery) {
+ return null;
+ }
+
+ if (!savedQuery.changedReason) {
+ return null;
+ }
+
+ const baseWarning = t(
+ "This query may look different from the original query. Here's why:"
+ );
+ const columnsWarning = [];
+ const equationsWarning = [];
+ const orderbyWarning = [];
+
+ const changedReason = savedQuery.changedReason;
+ if (changedReason.columns.length > 0) {
+ columnsWarning.push(
+ tct(`The following fields were dropped: [columns]`, {
+ columns: changedReason.columns.join(', '),
+ })
+ );
+ }
+ if (changedReason.equations) {
+ equationsWarning.push(
+ ...changedReason.equations.map(equation =>
+ tct(`[equation] was dropped because [reason] is unsupported`, {
+ equation: equation.equation,
+ reason:
+ typeof equation.reason === 'string'
+ ? equation.reason
+ : equation.reason.join(', '),
+ })
+ )
+ );
+ }
+ if (changedReason.orderby) {
+ orderbyWarning.push(
+ ...changedReason.orderby.map(equation =>
+ tct(`[orderby] was dropped because [reason]`, {
+ orderby: equation.orderby,
+ reason: equation.reason,
+ })
+ )
+ );
+ }
+
+ const allWarnings = [...columnsWarning, ...equationsWarning, ...orderbyWarning];
+
+ if (allWarnings.length > 0) {
+ return (
+
+ {baseWarning}
+
+ {allWarnings.map((warning, index) => (
+ {warning}
+ ))}
+
+
+ );
+ }
+
+ return null;
+}
+
+const StyledText = styled(Text)`
+ padding-bottom: ${p => p.theme.space.xs};
+`;
+
+const StyledAlert = styled(Alert)`
+ margin-bottom: ${p => p.theme.space.md};
+`;
diff --git a/static/app/views/explore/spans/spansTab.tsx b/static/app/views/explore/spans/spansTab.tsx
index b9acdbbf2dd425..5c6466a9049b2f 100644
--- a/static/app/views/explore/spans/spansTab.tsx
+++ b/static/app/views/explore/spans/spansTab.tsx
@@ -67,6 +67,7 @@ import {
useSetQueryParamsVisualizes,
} from 'sentry/views/explore/queryParams/context';
import {ExploreCharts} from 'sentry/views/explore/spans/charts';
+import {DroppedFieldsAlert} from 'sentry/views/explore/spans/droppedFieldsAlert';
import {ExtrapolationEnabledAlert} from 'sentry/views/explore/spans/extrapolationEnabledAlert';
import {SettingsDropdown} from 'sentry/views/explore/spans/settingsDropdown';
import {SpansExport} from 'sentry/views/explore/spans/spansExport';
@@ -369,6 +370,7 @@ function SpanTabContentSection({
const visualizes = useQueryParamsVisualizes();
const setVisualizes = useSetQueryParamsVisualizes();
const extrapolate = useQueryParamsExtrapolate();
+ const id = useExploreId();
const [tab, setTab] = useTab();
const [caseInsensitive] = useCaseInsensitivity();
@@ -487,6 +489,7 @@ function SpanTabContentSection({
+ {defined(id) && }
{!resultsLoading && !hasResults && (
)}