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
1 change: 1 addition & 0 deletions static/app/views/dashboards/datasetConfig/spans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const SpansConfig: DatasetConfig<
DisplayType.LINE,
DisplayType.TABLE,
DisplayType.TOP_N,
DisplayType.DETAILS,
],
getTableRequest: (
api: Client,
Expand Down
1 change: 1 addition & 0 deletions static/app/views/dashboards/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export enum DisplayType {
LINE = 'line',
TABLE = 'table',
BIG_NUMBER = 'big_number',
DETAILS = 'details',
TOP_N = 'top_n',
}

Expand Down
9 changes: 9 additions & 0 deletions static/app/views/dashboards/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -702,3 +702,12 @@ export function applyDashboardFilters(
}
return baseQuery;
}

export const isChartDisplayType = (displayType?: DisplayType) => {
if (!displayType) {
return true;
}
return ![DisplayType.BIG_NUMBER, DisplayType.TABLE, DisplayType.DETAILS].includes(
displayType
);
};
Comment on lines 703 to +713

This comment was marked as outdated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pr already up for this

Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export function SortBySelectors({
title={disableSortReason}
disabled={!disableSort || (disableSortDirection && disableSort)}
>
{displayType === DisplayType.TABLE ? (
{displayType === DisplayType.TABLE || displayType === DisplayType.DETAILS ? (
<Select
name="sortBy"
aria-label={t('Sort by')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import {Select} from 'sentry/components/core/select';
import {components} from 'sentry/components/forms/controls/reactSelectWrapper';
import FieldGroup from 'sentry/components/forms/fieldGroup';
import {IconGraph, IconNumber, IconTable} from 'sentry/icons';
import {IconGraph, IconNumber, IconSettings, IconTable} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {trackAnalytics} from 'sentry/utils/analytics';
Expand All @@ -24,15 +24,16 @@ const typeIcons = {
[DisplayType.LINE]: <IconGraph key="line" type="line" />,
[DisplayType.TABLE]: <IconTable key="table" />,
[DisplayType.BIG_NUMBER]: <IconNumber key="number" />,
[DisplayType.DETAILS]: <IconSettings key="details" />,
};

const displayTypes = {
const BASE_DISPLAY_TYPES: Partial<Record<DisplayType, string>> = {
[DisplayType.AREA]: t('Area'),
[DisplayType.BAR]: t('Bar'),
[DisplayType.LINE]: t('Line'),
[DisplayType.TABLE]: t('Table'),
[DisplayType.BIG_NUMBER]: t('Big Number'),
};
} as const;

interface WidgetBuilderTypeSelectorProps {
error?: Record<string, any>;
Expand All @@ -46,6 +47,11 @@ function WidgetBuilderTypeSelector({error, setError}: WidgetBuilderTypeSelectorP
const isEditing = useIsEditingWidget();
const organization = useOrganization();

const displayTypes = {...BASE_DISPLAY_TYPES};
if (organization.features.includes('dashboards-details-widget')) {
displayTypes[DisplayType.DETAILS] = t('Details');
}

return (
<Fragment>
<SectionHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
WidgetType,
type LinkedDashboard,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {SectionHeader} from 'sentry/views/dashboards/widgetBuilder/components/common/sectionHeader';
import SortableVisualizeFieldWrapper from 'sentry/views/dashboards/widgetBuilder/components/common/sortableFieldWrapper';
import {ExploreArithmeticBuilder} from 'sentry/views/dashboards/widgetBuilder/components/exploreArithmeticBuilder';
Expand Down Expand Up @@ -277,9 +278,7 @@ function Visualize({error, setError}: VisualizeProps) {
const isEditing = useIsEditingWidget();
const disableTransactionWidget = useDisableTransactionWidget();

const isChartWidget =
state.displayType !== DisplayType.TABLE &&
state.displayType !== DisplayType.BIG_NUMBER;
const isChartWidget = isChartDisplayType(state.displayType);
const isBigNumberWidget = state.displayType === DisplayType.BIG_NUMBER;
const isTableWidget = state.displayType === DisplayType.TABLE;
const {tags: numericSpanTags} = useTraceItemTags('number');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {AggregationKey} from 'sentry/utils/fields';
import useOrganization from 'sentry/utils/useOrganization';
import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
import {DisplayType, WidgetType} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {
AggregateCompactSelect,
getAggregateValueKey,
Expand Down Expand Up @@ -116,9 +117,7 @@ export function SelectRow({
const datasetConfig = getDatasetConfig(state.dataset);
const columnSelectRef = useRef<HTMLDivElement>(null);

const isChartWidget =
state.displayType !== DisplayType.TABLE &&
state.displayType !== DisplayType.BIG_NUMBER;
const isChartWidget = isChartDisplayType(state.displayType);

const updateAction = isChartWidget
? BuilderStateAction.SET_Y_AXIS
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
type DashboardFilters,
type Widget,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {animationTransitionSettings} from 'sentry/views/dashboards/widgetBuilder/components/common/animationSettings';
import WidgetBuilderDatasetSelector from 'sentry/views/dashboards/widgetBuilder/components/datasetSelector';
import WidgetBuilderFilterBar from 'sentry/views/dashboards/widgetBuilder/components/filtersBar';
Expand Down Expand Up @@ -122,9 +123,9 @@ function WidgetBuilderSlideout({
: isEditing
? t('Edit Widget')
: t('Custom Widget Builder');
const isChartWidget =
state.displayType !== DisplayType.BIG_NUMBER &&
state.displayType !== DisplayType.TABLE;
const isChartWidget = isChartDisplayType(state.displayType);

const showVisualizeSection = state.displayType !== DisplayType.DETAILS;
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Sort UI not shown for DETAILS widgets

The showSortByStep condition doesn't include DisplayType.DETAILS, preventing the sort UI from appearing for DETAILS widgets. However, the SortBySelectors component explicitly handles DETAILS widgets alongside TABLE widgets, and users need sorting to control which example span appears first in the details view.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intentional, we don't want sort UI for detail. we might want it in the future tho, so i made the sort component also work with details in this PR.


const customPreviewRef = useRef<HTMLDivElement>(null);
const templatesPreviewRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -340,9 +341,11 @@ function WidgetBuilderSlideout({
<WidgetBuilderFilterBar releases={dashboard.filters?.release ?? []} />
</Section>
)}
<Section>
<Visualize error={error} setError={setError} />
</Section>
{showVisualizeSection && (
<Section>
<Visualize error={error} setError={setError} />
</Section>
)}
<Section>
<WidgetBuilderQueryFilterBuilder
onQueryConditionChange={onQueryConditionChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type DashboardDetails,
type DashboardFilters,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {useWidgetBuilderContext} from 'sentry/views/dashboards/widgetBuilder/contexts/widgetBuilderContext';
import {BuilderStateAction} from 'sentry/views/dashboards/widgetBuilder/hooks/useWidgetBuilderState';
import {convertBuilderStateToWidget} from 'sentry/views/dashboards/widgetBuilder/utils/convertBuilderStateToWidget';
Expand Down Expand Up @@ -63,9 +64,7 @@ function WidgetPreview({
false,
};

const isChart =
widget.displayType !== DisplayType.TABLE &&
widget.displayType !== DisplayType.BIG_NUMBER;
const isChart = isChartDisplayType(widget.displayType);

// the spans dataset doesn't handle timeseries for duplicate yAxes/aggregates
// automatically, so we need to dedupe them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
WidgetType,
type LinkedDashboard,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import type {ThresholdsConfig} from 'sentry/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds';
import {
DISABLED_SORT,
Expand All @@ -32,12 +33,22 @@ import {
DEFAULT_RESULTS_LIMIT,
getResultsLimit,
} from 'sentry/views/dashboards/widgetBuilder/utils';
import type {DefaultDetailWidgetFields} from 'sentry/views/dashboards/widgets/detailsWidget/types';
import {FieldValueKind} from 'sentry/views/discover/table/types';
import {SpanFields} from 'sentry/views/insights/types';

// For issues dataset, events and users are sorted descending and do not use '-'
// All other issues fields are sorted ascending
const REVERSED_ORDER_FIELD_SORT_LIST = ['freq', 'user'];

const DETAIL_WIDGET_FIELDS: DefaultDetailWidgetFields[] = [
SpanFields.ID,
SpanFields.SPAN_OP,
SpanFields.SPAN_GROUP,
SpanFields.SPAN_DESCRIPTION,
SpanFields.SPAN_CATEGORY,
] as const;

export const MAX_NUM_Y_AXES = 3;

export type WidgetBuilderStateQueryParams = {
Expand Down Expand Up @@ -296,6 +307,16 @@ function useWidgetBuilderState(): {
// Columns are ignored for big number widgets because there is no grouping
setFields([...aggregatesWithoutAlias, ...(yAxisWithoutAlias ?? [])], options);
setQuery(query?.slice(0, 1), options);
} else if (action.payload === DisplayType.DETAILS) {
setLimit(undefined, options);
setSort([], options);
setYAxis([], options);
setLegendAlias([], options);
setFields(
DETAIL_WIDGET_FIELDS.map(field => ({field, kind: FieldValueKind.FIELD})),
options
);
setQuery(query?.slice(0, 1), options);
} else {
setFields(columnsWithoutAlias, options);
const nextAggregates = [
Expand Down Expand Up @@ -344,10 +365,16 @@ function useWidgetBuilderState(): {
config.defaultWidgetQuery.fields?.map(field => explodeField({field})),
options
);
if (
nextDisplayType === DisplayType.TABLE ||
nextDisplayType === DisplayType.BIG_NUMBER
) {
if (isChartDisplayType(nextDisplayType)) {
setFields([], options);
setYAxis(
config.defaultWidgetQuery.aggregates?.map(aggregate =>
explodeField({field: aggregate})
),
options
);
setSort(decodeSorts(config.defaultWidgetQuery.orderby), options);
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: DETAILS display type not reset when switching datasets

When changing datasets via SET_DATASET action while DisplayType.DETAILS is selected, the display type isn't validated against the new dataset's supportedDisplayTypes. Since DETAILS is only supported by the SPANS dataset, switching from SPANS to another dataset leaves the widget in a broken state with DETAILS display type and incompatible fields. The code handles this for ISSUE dataset (forcing TABLE) but not for DETAILS. This causes the widget to use default dataset fields instead of required DETAIL_WIDGET_FIELDS, breaking the details visualization.

Fix in Cursor Fix in Web

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried this locally

  1. Select dataset: spans
  2. Select type: details
  3. Select dataset: errors

After this the type was reset to line

setYAxis([], options);
setFields(
config.defaultWidgetQuery.fields?.map(field => explodeField({field})),
Expand All @@ -359,15 +386,6 @@ function useWidgetBuilderState(): {
: decodeSorts(config.defaultWidgetQuery.orderby),
options
);
} else {
setFields([], options);
setYAxis(
config.defaultWidgetQuery.aggregates?.map(aggregate =>
explodeField({field: aggregate})
),
options
);
setSort(decodeSorts(config.defaultWidgetQuery.orderby), options);
}

setThresholds(undefined, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type Widget,
type WidgetQuery,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {
serializeSorts,
type WidgetBuilderState,
Expand Down Expand Up @@ -39,7 +40,7 @@ export function convertBuilderStateToWidget(state: WidgetBuilderState): Widget {
.filter(Boolean);

const fields =
state.displayType === DisplayType.TABLE
state.displayType === DisplayType.TABLE || state.displayType === DisplayType.DETAILS
? state.fields?.map(generateFieldAsString)
: [...(columns ?? []), ...(aggregates ?? [])];

Expand Down Expand Up @@ -69,12 +70,7 @@ export function convertBuilderStateToWidget(state: WidgetBuilderState): Widget {
};
});

const limit = [DisplayType.BIG_NUMBER, DisplayType.TABLE].includes(
state.displayType ?? DisplayType.TABLE
)
? undefined
: state.limit;

const limit = isChartDisplayType(state.displayType) ? state.limit : undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Undefined displayType handling inconsistent for limit field

The new isChartDisplayType function returns true when displayType is undefined, treating it as a chart type. However, the previous code in convertBuilderStateToWidget.ts explicitly defaulted undefined to DisplayType.TABLE via state.displayType ?? DisplayType.TABLE. This causes inconsistent behavior: when displayType is undefined, the limit field is now set to state.limit (chart behavior), but the widget's displayType property defaults to TABLE on line 77. Previously, undefined would result in limit = undefined (table behavior), matching the displayType fallback.

Additional Locations (1)

Fix in Cursor Fix in Web

return {
title: state.title ?? '',
description: state.description,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type Widget,
type WidgetQuery,
} from 'sentry/views/dashboards/types';
import {isChartDisplayType} from 'sentry/views/dashboards/utils';
import {
serializeFields,
serializeThresholds,
Expand Down Expand Up @@ -37,15 +38,12 @@ export function convertWidgetToBuilderStateParams(
const firstWidgetQuery = widget.queries[0];
let yAxis = firstWidgetQuery ? stringifyFields(firstWidgetQuery, 'aggregates') : [];
let field: string[] = [];
if (
widget.displayType === DisplayType.TABLE ||
widget.displayType === DisplayType.BIG_NUMBER
) {
if (isChartDisplayType(widget.displayType)) {
field = firstWidgetQuery ? stringifyFields(firstWidgetQuery, 'columns') : [];
} else {
field = firstWidgetQuery ? stringifyFields(firstWidgetQuery, 'fields') : [];
yAxis = [];
legendAlias = [];
} else {
field = firstWidgetQuery ? stringifyFields(firstWidgetQuery, 'columns') : [];
}

return {
Expand Down
27 changes: 27 additions & 0 deletions static/app/views/dashboards/widgetCard/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ import type WidgetLegendSelectionState from 'sentry/views/dashboards/widgetLegen
import {BigNumberWidgetVisualization} from 'sentry/views/dashboards/widgets/bigNumberWidget/bigNumberWidgetVisualization';
import {ALLOWED_CELL_ACTIONS} from 'sentry/views/dashboards/widgets/common/settings';
import type {TabularColumn} from 'sentry/views/dashboards/widgets/common/types';
import {DetailsWidgetVisualization} from 'sentry/views/dashboards/widgets/detailsWidget/detailsWidgetVisualization';
import type {DefaultDetailWidgetFields} from 'sentry/views/dashboards/widgets/detailsWidget/types';
import {TableWidgetVisualization} from 'sentry/views/dashboards/widgets/tableWidget/tableWidgetVisualization';
import {
convertTableDataToTabularData,
Expand All @@ -78,6 +80,7 @@ import {
import {Actions} from 'sentry/views/discover/table/cellAction';
import {decodeColumnOrder} from 'sentry/views/discover/utils';
import {ConfidenceFooter} from 'sentry/views/explore/spans/charts/confidenceFooter';
import {type SpanResponse} from 'sentry/views/insights/types';

import type {GenericWidgetQueriesChildrenProps} from './genericWidgetQueries';

Expand Down Expand Up @@ -198,6 +201,15 @@ function WidgetCardChart(props: WidgetCardChartProps) {
);
}

if (widget.displayType === DisplayType.DETAILS) {
return (
<TransitionChart loading={loading} reloading={loading}>
<LoadingScreen loading={loading} showLoadingText={showLoadingText} />
<DetailsComponent tableResults={tableResults} {...props} />
</TransitionChart>
);
}

const {start, end, period, utc} = selection.datetime;
const {projects, environments} = selection;

Expand Down Expand Up @@ -658,6 +670,21 @@ function BigNumberComponent({
});
}

function DetailsComponent(props: TableComponentProps): React.ReactNode {
const {tableResults} = props;

const singleSpan = tableResults?.[0]?.data?.[0] as
| Pick<SpanResponse, DefaultDetailWidgetFields>
| undefined;

// TODO: Handle this case gracefully
if (!singleSpan) {
return null;
}

return <DetailsWidgetVisualization span={singleSpan} />;
}

function getChartComponent(chartProps: any, widget: Widget): React.ReactNode {
const stacked = widget.queries[0]?.columns.length! > 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import type {OnDemandControlContext} from 'sentry/utils/performance/contexts/onD
import type {DatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
import type {DashboardFilters, Widget, WidgetQuery} from 'sentry/views/dashboards/types';
import {DEFAULT_TABLE_LIMIT, DisplayType} from 'sentry/views/dashboards/types';
import {dashboardFiltersToString} from 'sentry/views/dashboards/utils';
import {
dashboardFiltersToString,
isChartDisplayType,
} from 'sentry/views/dashboards/utils';
import type {WidgetQueryQueue} from 'sentry/views/dashboards/utils/widgetQueryQueue';
import type {SamplingMode} from 'sentry/views/explore/hooks/useProgressiveQuery';

Expand Down Expand Up @@ -419,10 +422,10 @@ class GenericWidgetQueries<SeriesResponse, TableResponse> extends Component<
onDataFetchStart?.();

try {
if ([DisplayType.TABLE, DisplayType.BIG_NUMBER].includes(widget.displayType)) {
await this.fetchTableData(queryFetchID);
} else {
if (isChartDisplayType(widget.displayType)) {
await this.fetchSeriesData(queryFetchID);
} else {
await this.fetchTableData(queryFetchID);
}
} catch (err: any) {
if (this._isMounted) {
Expand Down
Loading
Loading