Skip to content

Commit 85f211a

Browse files
authored
ref(dashboards): use table widget visualization in widget viewer modal (#95437)
### Changes Consolidates all the different tables for datasets in viewer widget modal under `TableWidgetVisualization`. Currently hidden behind a feature flag. Also includes some updates to `TableWidgetVisualization` to accommodate this refactor. There should be no UI change except column aliases are used in the viewer modal now
1 parent 1a70abf commit 85f211a

File tree

2 files changed

+223
-15
lines changed

2 files changed

+223
-15
lines changed

static/app/components/modals/widgetViewerModal.tsx

Lines changed: 211 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
3737
import {getUtcDateString} from 'sentry/utils/dates';
3838
import type {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
3939
import type EventView from 'sentry/utils/discover/eventView';
40-
import type {AggregationOutputType} from 'sentry/utils/discover/fields';
40+
import type {AggregationOutputType, Sort} from 'sentry/utils/discover/fields';
4141
import {
4242
getAggregateAlias,
4343
isAggregateField,
@@ -51,14 +51,21 @@ import {
5151
import parseLinkHeader from 'sentry/utils/parseLinkHeader';
5252
import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality';
5353
import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
54-
import {decodeInteger, decodeList, decodeScalar} from 'sentry/utils/queryString';
54+
import {
55+
decodeInteger,
56+
decodeList,
57+
decodeScalar,
58+
decodeSorts,
59+
} from 'sentry/utils/queryString';
5560
import useApi from 'sentry/utils/useApi';
5661
import {useLocation} from 'sentry/utils/useLocation';
62+
import type {ReactRouter3Navigate} from 'sentry/utils/useNavigate';
5763
import {useNavigate} from 'sentry/utils/useNavigate';
5864
import useProjects from 'sentry/utils/useProjects';
5965
import {useUser} from 'sentry/utils/useUser';
6066
import {useUserTeams} from 'sentry/utils/useUserTeams';
6167
import withPageFilters from 'sentry/utils/withPageFilters';
68+
import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
6269
import {checkUserHasEditAccess} from 'sentry/views/dashboards/detail';
6370
import {DiscoverSplitAlert} from 'sentry/views/dashboards/discoverSplitAlert';
6471
import type {
@@ -96,6 +103,12 @@ import ReleaseWidgetQueries from 'sentry/views/dashboards/widgetCard/releaseWidg
96103
import {WidgetCardChartContainer} from 'sentry/views/dashboards/widgetCard/widgetCardChartContainer';
97104
import WidgetQueries from 'sentry/views/dashboards/widgetCard/widgetQueries';
98105
import type WidgetLegendSelectionState from 'sentry/views/dashboards/widgetLegendSelectionState';
106+
import {TableWidgetVisualization} from 'sentry/views/dashboards/widgets/tableWidget/tableWidgetVisualization';
107+
import {
108+
convertTableDataToTabularData,
109+
decodeColumnAliases,
110+
} from 'sentry/views/dashboards/widgets/tableWidget/utils';
111+
import type {TableColumn} from 'sentry/views/discover/table/types';
99112
import {decodeColumnOrder} from 'sentry/views/discover/utils';
100113
import {MetricsDataSwitcher} from 'sentry/views/performance/landing/metricsDataSwitcher';
101114

@@ -482,6 +495,23 @@ function WidgetViewerModal(props: Props) {
482495
const {isMetricsData} = useDashboardsMEPContext();
483496
const links = parseLinkHeader(pageLinks ?? null);
484497
const isFirstPage = links.previous?.results === false;
498+
499+
if (organization.features.includes('dashboards-use-widget-table-visualization')) {
500+
return ViewerTableV2({
501+
tableResults,
502+
loading,
503+
pageLinks,
504+
columnOrder,
505+
widget,
506+
tableWidget,
507+
setChartUnmodified,
508+
widths,
509+
location,
510+
organization,
511+
navigate,
512+
});
513+
}
514+
485515
return (
486516
<Fragment>
487517
<GridEditable
@@ -558,6 +588,21 @@ function WidgetViewerModal(props: Props) {
558588
if (totalResults === undefined && totalCount) {
559589
setTotalResults(totalCount);
560590
}
591+
if (organization.features.includes('dashboards-use-widget-table-visualization')) {
592+
return ViewerTableV2({
593+
tableResults,
594+
loading,
595+
pageLinks,
596+
columnOrder,
597+
widget,
598+
tableWidget,
599+
setChartUnmodified,
600+
widths,
601+
location,
602+
organization,
603+
navigate,
604+
});
605+
}
561606
const links = parseLinkHeader(pageLinks ?? null);
562607
return (
563608
<Fragment>
@@ -634,6 +679,21 @@ function WidgetViewerModal(props: Props) {
634679
loading,
635680
pageLinks,
636681
}) => {
682+
if (organization.features.includes('dashboards-use-widget-table-visualization')) {
683+
return ViewerTableV2({
684+
tableResults,
685+
loading,
686+
pageLinks,
687+
columnOrder,
688+
widget,
689+
tableWidget,
690+
setChartUnmodified,
691+
widths,
692+
location,
693+
organization,
694+
navigate,
695+
});
696+
}
637697
const links = parseLinkHeader(pageLinks ?? null);
638698
const isFirstPage = links.previous?.results === false;
639699
return (
@@ -1189,6 +1249,155 @@ function renderTotalResults(totalResults?: string, widgetType?: WidgetType) {
11891249
}
11901250
}
11911251

1252+
interface ViewerTableV2Props {
1253+
columnOrder: Array<TableColumn<string>>;
1254+
loading: boolean;
1255+
location: Location;
1256+
navigate: ReactRouter3Navigate;
1257+
organization: Organization;
1258+
setChartUnmodified: React.Dispatch<React.SetStateAction<boolean>>;
1259+
tableWidget: Widget;
1260+
widget: Widget;
1261+
widths: string[];
1262+
pageLinks?: string;
1263+
tableResults?: TableDataWithTitle[];
1264+
}
1265+
1266+
function ViewerTableV2({
1267+
widget,
1268+
tableResults,
1269+
loading,
1270+
pageLinks,
1271+
columnOrder,
1272+
widths,
1273+
setChartUnmodified,
1274+
tableWidget,
1275+
location,
1276+
organization,
1277+
navigate,
1278+
}: ViewerTableV2Props) {
1279+
const page = decodeInteger(location.query[WidgetViewerQueryField.PAGE]) ?? 0;
1280+
const links = parseLinkHeader(pageLinks ?? null);
1281+
1282+
function sortable(key: string) {
1283+
if (tableWidget.widgetType === WidgetType.ISSUE) {
1284+
return false;
1285+
}
1286+
if (tableWidget.widgetType === WidgetType.RELEASE) {
1287+
return isAggregateField(key);
1288+
}
1289+
return true;
1290+
}
1291+
1292+
const tableColumns = columnOrder.map((column, index) => ({
1293+
key: column.key,
1294+
type: column.type === 'never' ? null : column.type,
1295+
sortable: sortable(column.key),
1296+
width: widths[index] ? parseInt(widths[index], 10) || -1 : -1,
1297+
}));
1298+
const aliases = decodeColumnAliases(
1299+
tableColumns,
1300+
tableWidget.queries[0]?.fieldAliases ?? [],
1301+
tableWidget.widgetType === WidgetType.ISSUE
1302+
? getDatasetConfig(tableWidget.widgetType).getFieldHeaderMap?.()
1303+
: {}
1304+
);
1305+
1306+
if (loading) {
1307+
return (
1308+
<TableWidgetVisualization.LoadingPlaceholder
1309+
columns={tableColumns}
1310+
aliases={aliases}
1311+
/>
1312+
);
1313+
}
1314+
1315+
const tableSort = decodeSorts(tableWidget.queries[0]?.orderby)[0];
1316+
const data = convertTableDataToTabularData(tableResults?.[0]);
1317+
1318+
function onChangeSort(newSort: Sort) {
1319+
if (
1320+
[DisplayType.TOP_N, DisplayType.TABLE].includes(widget.displayType) ||
1321+
defined(widget.limit) ||
1322+
tableWidget.widgetType === WidgetType.ISSUE
1323+
) {
1324+
setChartUnmodified(false);
1325+
}
1326+
1327+
trackAnalytics('dashboards_views.widget_viewer.sort', {
1328+
organization,
1329+
widget_type: widget.widgetType ?? WidgetType.DISCOVER,
1330+
display_type: widget.displayType,
1331+
column: newSort.field,
1332+
order: newSort.kind,
1333+
});
1334+
1335+
navigate(
1336+
{
1337+
...location,
1338+
query: {
1339+
...location.query,
1340+
sort: `${newSort.kind === 'desc' ? '-' : ''}${newSort.field}`,
1341+
},
1342+
},
1343+
{replace: true, preventScrollReset: true}
1344+
);
1345+
}
1346+
1347+
return (
1348+
<Fragment>
1349+
<TableWidgetVisualization
1350+
tableData={data}
1351+
columns={tableColumns}
1352+
aliases={aliases}
1353+
sort={tableSort}
1354+
onChangeSort={onChangeSort}
1355+
/>
1356+
{!(
1357+
tableWidget.queries[0]!.orderby.match(/^-?release$/) &&
1358+
tableWidget.widgetType === WidgetType.RELEASE
1359+
) &&
1360+
(links?.previous?.results || links?.next?.results) && (
1361+
<Pagination
1362+
pageLinks={pageLinks}
1363+
onCursor={(nextCursor, _path, _query, delta) => {
1364+
let nextPage = isNaN(page) ? delta : page + delta;
1365+
let newCursor = nextCursor;
1366+
// unset cursor and page when we navigate back to the first page
1367+
// also reset cursor if somehow the previous button is enabled on
1368+
// first page and user attempts to go backwards
1369+
if (nextPage <= 0) {
1370+
newCursor = undefined;
1371+
nextPage = 0;
1372+
}
1373+
navigate(
1374+
{
1375+
pathname: location.pathname,
1376+
query: {
1377+
...location.query,
1378+
[WidgetViewerQueryField.CURSOR]: newCursor,
1379+
[WidgetViewerQueryField.PAGE]: nextPage,
1380+
},
1381+
},
1382+
{replace: true, preventScrollReset: true}
1383+
);
1384+
1385+
if (widget.displayType === DisplayType.TABLE) {
1386+
setChartUnmodified(false);
1387+
}
1388+
1389+
trackAnalytics('dashboards_views.widget_viewer.paginate', {
1390+
organization,
1391+
widget_type: widget.widgetType ?? WidgetType.DISCOVER,
1392+
display_type: widget.displayType,
1393+
});
1394+
}}
1395+
/>
1396+
)}
1397+
</Fragment>
1398+
);
1399+
}
1400+
11921401
export const modalCss = css`
11931402
width: 100%;
11941403
max-width: 1200px;

static/app/views/dashboards/widgets/tableWidget/tableWidgetVisualization.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -211,26 +211,25 @@ export function TableWidgetVisualization(props: TableWidgetVisualizationProps) {
211211
<SortLink
212212
align={align}
213213
canSort={column.sortable ?? false}
214-
onClick={() => {
214+
title={<StyledTooltip title={name}>{name}</StyledTooltip>}
215+
onClick={e => {
216+
if (!onChangeSort) return;
217+
e.preventDefault();
215218
const nextDirection = direction === 'desc' ? 'asc' : 'desc';
216-
217-
onChangeSort?.({
219+
onChangeSort({
218220
field: sortColumn,
219221
kind: nextDirection,
220222
});
221223
}}
222-
title={<StyledTooltip title={name}>{name}</StyledTooltip>}
223224
direction={direction}
224225
generateSortLink={() => {
225-
return onChangeSort
226-
? location
227-
: {
228-
...location,
229-
query: {
230-
...location.query,
231-
sort: `${direction === 'desc' ? '' : '-'}${sortColumn}`,
232-
},
233-
};
226+
return {
227+
...location,
228+
query: {
229+
...location.query,
230+
sort: `${direction === 'desc' ? '' : '-'}${sortColumn}`,
231+
},
232+
};
234233
}}
235234
/>
236235
);

0 commit comments

Comments
 (0)