@@ -37,7 +37,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
37
37
import { getUtcDateString } from 'sentry/utils/dates' ;
38
38
import type { TableDataWithTitle } from 'sentry/utils/discover/discoverQuery' ;
39
39
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' ;
41
41
import {
42
42
getAggregateAlias ,
43
43
isAggregateField ,
@@ -51,14 +51,21 @@ import {
51
51
import parseLinkHeader from 'sentry/utils/parseLinkHeader' ;
52
52
import { MetricsCardinalityProvider } from 'sentry/utils/performance/contexts/metricsCardinality' ;
53
53
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' ;
55
60
import useApi from 'sentry/utils/useApi' ;
56
61
import { useLocation } from 'sentry/utils/useLocation' ;
62
+ import type { ReactRouter3Navigate } from 'sentry/utils/useNavigate' ;
57
63
import { useNavigate } from 'sentry/utils/useNavigate' ;
58
64
import useProjects from 'sentry/utils/useProjects' ;
59
65
import { useUser } from 'sentry/utils/useUser' ;
60
66
import { useUserTeams } from 'sentry/utils/useUserTeams' ;
61
67
import withPageFilters from 'sentry/utils/withPageFilters' ;
68
+ import { getDatasetConfig } from 'sentry/views/dashboards/datasetConfig/base' ;
62
69
import { checkUserHasEditAccess } from 'sentry/views/dashboards/detail' ;
63
70
import { DiscoverSplitAlert } from 'sentry/views/dashboards/discoverSplitAlert' ;
64
71
import type {
@@ -96,6 +103,12 @@ import ReleaseWidgetQueries from 'sentry/views/dashboards/widgetCard/releaseWidg
96
103
import { WidgetCardChartContainer } from 'sentry/views/dashboards/widgetCard/widgetCardChartContainer' ;
97
104
import WidgetQueries from 'sentry/views/dashboards/widgetCard/widgetQueries' ;
98
105
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' ;
99
112
import { decodeColumnOrder } from 'sentry/views/discover/utils' ;
100
113
import { MetricsDataSwitcher } from 'sentry/views/performance/landing/metricsDataSwitcher' ;
101
114
@@ -482,6 +495,23 @@ function WidgetViewerModal(props: Props) {
482
495
const { isMetricsData} = useDashboardsMEPContext ( ) ;
483
496
const links = parseLinkHeader ( pageLinks ?? null ) ;
484
497
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
+
485
515
return (
486
516
< Fragment >
487
517
< GridEditable
@@ -558,6 +588,21 @@ function WidgetViewerModal(props: Props) {
558
588
if ( totalResults === undefined && totalCount ) {
559
589
setTotalResults ( totalCount ) ;
560
590
}
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
+ }
561
606
const links = parseLinkHeader ( pageLinks ?? null ) ;
562
607
return (
563
608
< Fragment >
@@ -634,6 +679,21 @@ function WidgetViewerModal(props: Props) {
634
679
loading,
635
680
pageLinks,
636
681
} ) => {
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
+ }
637
697
const links = parseLinkHeader ( pageLinks ?? null ) ;
638
698
const isFirstPage = links . previous ?. results === false ;
639
699
return (
@@ -1189,6 +1249,155 @@ function renderTotalResults(totalResults?: string, widgetType?: WidgetType) {
1189
1249
}
1190
1250
}
1191
1251
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 ( / ^ - ? r e l e a s e $ / ) &&
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
+
1192
1401
export const modalCss = css `
1193
1402
width : 100% ;
1194
1403
max-width : 1200px ;
0 commit comments