@atlaskit/link-datasource 5.5.1 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/ui/datasource-table-view/datasource-column-sort.js +60 -0
  3. package/dist/cjs/ui/datasource-table-view/datasourceTableView.js +52 -5
  4. package/dist/cjs/ui/issue-like-table/index.compiled.css +5 -0
  5. package/dist/cjs/ui/issue-like-table/index.js +148 -65
  6. package/dist/cjs/ui/issue-like-table/messages.js +10 -0
  7. package/dist/es2019/ui/datasource-table-view/datasource-column-sort.js +52 -0
  8. package/dist/es2019/ui/datasource-table-view/datasourceTableView.js +46 -6
  9. package/dist/es2019/ui/issue-like-table/index.compiled.css +5 -0
  10. package/dist/es2019/ui/issue-like-table/index.js +87 -8
  11. package/dist/es2019/ui/issue-like-table/messages.js +10 -0
  12. package/dist/esm/ui/datasource-table-view/datasource-column-sort.js +53 -0
  13. package/dist/esm/ui/datasource-table-view/datasourceTableView.js +53 -6
  14. package/dist/esm/ui/issue-like-table/index.compiled.css +5 -0
  15. package/dist/esm/ui/issue-like-table/index.js +148 -65
  16. package/dist/esm/ui/issue-like-table/messages.js +10 -0
  17. package/dist/types/ui/datasource-table-view/datasource-column-sort.d.ts +12 -0
  18. package/dist/types/ui/datasource-table-view/datasourceTableView.d.ts +2 -2
  19. package/dist/types/ui/datasource-table-view/types.d.ts +1 -1
  20. package/dist/types/ui/issue-like-table/index.d.ts +1 -1
  21. package/dist/types/ui/issue-like-table/messages.d.ts +33 -23
  22. package/dist/types/ui/issue-like-table/types.d.ts +10 -0
  23. package/dist/types-ts4.5/ui/datasource-table-view/datasource-column-sort.d.ts +12 -0
  24. package/dist/types-ts4.5/ui/datasource-table-view/datasourceTableView.d.ts +2 -2
  25. package/dist/types-ts4.5/ui/datasource-table-view/types.d.ts +1 -1
  26. package/dist/types-ts4.5/ui/issue-like-table/index.d.ts +1 -1
  27. package/dist/types-ts4.5/ui/issue-like-table/messages.d.ts +33 -23
  28. package/dist/types-ts4.5/ui/issue-like-table/types.d.ts +10 -0
  29. package/package.json +14 -11
@@ -0,0 +1,52 @@
1
+ import { JIRA_LIST_OF_LINKS_DATASOURCE_ID } from '../jira-issues-modal';
2
+ const getNextSortDirection = (columnKey, currentSort) => {
3
+ if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.key) !== columnKey) {
4
+ return 'ASC';
5
+ }
6
+ if (currentSort.direction === 'ASC') {
7
+ return 'DESC';
8
+ }
9
+ return undefined;
10
+ };
11
+ // Match and remove an existing trailing ORDER BY clause before appending the next sort:
12
+ // - (?:^|\\s+) allows ORDER BY at the start of the JQL or after whitespace.
13
+ // - ORDER\\s+BY matches ORDER BY with flexible spacing and is case-insensitive (/i).
14
+ // - [\\s\\S]*$ consumes everything after ORDER BY to the end of the JQL.
15
+ // Examples:
16
+ // - "project = TEST ORDER BY created DESC" -> "project = TEST"
17
+ // - "ORDER BY priority ASC" -> ""
18
+ const JIRA_ORDER_BY_PATTERN = /(?:^|\s+)ORDER\s+BY\s+[\s\S]*$/i;
19
+ const getJiraColumnSortParameters = ({
20
+ parameters,
21
+ columnKey,
22
+ currentSort
23
+ }) => {
24
+ const direction = getNextSortDirection(columnKey, currentSort);
25
+ if (!direction) {
26
+ return {
27
+ parameters,
28
+ sort: undefined
29
+ };
30
+ }
31
+ if (typeof parameters.jql !== 'string') {
32
+ return undefined;
33
+ }
34
+ const jqlWithoutOrder = parameters.jql.replace(JIRA_ORDER_BY_PATTERN, '').trim();
35
+ const nextOrderByClause = `ORDER BY ${columnKey} ${direction}`;
36
+ return {
37
+ parameters: {
38
+ ...parameters,
39
+ jql: [jqlWithoutOrder, nextOrderByClause].filter(Boolean).join(' ')
40
+ },
41
+ sort: {
42
+ key: columnKey,
43
+ direction
44
+ }
45
+ };
46
+ };
47
+ export const getDatasourceColumnSortGetter = datasourceId => {
48
+ if (datasourceId === JIRA_LIST_OF_LINKS_DATASOURCE_ID) {
49
+ return getJiraColumnSortParameters;
50
+ }
51
+ return undefined;
52
+ };
@@ -1,8 +1,10 @@
1
1
  /* datasourceTableView.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import _extends from "@babel/runtime/helpers/extends";
2
3
  import "./datasourceTableView.compiled.css";
3
4
  import * as React from 'react';
4
5
  import { ax, ix } from "@compiled/react/runtime";
5
- import { useCallback, useEffect, useRef } from 'react';
6
+ import { useCallback, useEffect, useRef, useState } from 'react';
7
+ import isEqual from 'lodash/isEqual';
6
8
  import { withAnalyticsContext } from '@atlaskit/analytics-next';
7
9
  import { IntlMessagesProvider } from '@atlaskit/intl-messages-provider';
8
10
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -26,6 +28,7 @@ import { ProviderAuthRequired } from '../common/error-state/provider-auth-requir
26
28
  import { IssueLikeDataTableView } from '../issue-like-table';
27
29
  import EmptyState from '../issue-like-table/empty-state';
28
30
  import { TableFooter } from '../table-footer';
31
+ import { getDatasourceColumnSortGetter } from './datasource-column-sort';
29
32
  const containerStyles = null;
30
33
  const DefaultScrollableContainerHeight = 590;
31
34
  const DatasourceTableViewWithoutAnalytics = ({
@@ -40,6 +43,26 @@ const DatasourceTableViewWithoutAnalytics = ({
40
43
  onWrappedColumnChange,
41
44
  scrollableContainerHeight = DefaultScrollableContainerHeight
42
45
  }) => {
46
+ // Local copy lets us apply in-session sort mutations without mutating external parameters.
47
+ const [sessionParameters, setSessionParameters] = useState(parameters);
48
+ // Tracks the external parameters that the current session/sort state was derived from.
49
+ const sessionBaseParametersRef = useRef(parameters);
50
+ const [sortState, setSortState] = useState();
51
+ const columnSortGetter = getDatasourceColumnSortGetter(datasourceId);
52
+ // Sorting is only owned by this view when parent callbacks are absent (read-only rendering mode).
53
+ const isReadOnlyDatasourceTable = !onVisibleColumnKeysChange && !onColumnResize && !onWrappedColumnChange;
54
+ // Keep sort UI hidden unless datasource + ownership + feature gate all allow it.
55
+ const shouldEnableColumnSort = !!columnSortGetter && isReadOnlyDatasourceTable && fg('platform_lp_jira_sllv_renderer_column_sorting');
56
+ useDeepEffect(() => {
57
+ // External parameter updates should always reset local sort/session state back to source-of-truth.
58
+ sessionBaseParametersRef.current = parameters;
59
+ setSessionParameters(parameters);
60
+ setSortState(undefined);
61
+ }, [parameters]);
62
+ const isSessionBasedOnCurrentParameters = isEqual(sessionBaseParametersRef.current, parameters);
63
+ // Use session parameters only when they are known to be based on the current external parameters.
64
+ // This avoids a one-render stale read during the deep-effect update cycle above.
65
+ const activeParameters = isSessionBasedOnCurrentParameters && fg('platform_lp_jira_sllv_renderer_column_sorting') ? sessionParameters : parameters;
43
66
  const {
44
67
  reset,
45
68
  status,
@@ -57,7 +80,7 @@ const DatasourceTableViewWithoutAnalytics = ({
57
80
  authDetails
58
81
  } = useDatasourceTableState({
59
82
  datasourceId,
60
- parameters,
83
+ parameters: activeParameters,
61
84
  fieldKeys: visibleColumnKeys
62
85
  });
63
86
  const isInPDFRender = useIsInPDFRender();
@@ -95,7 +118,7 @@ const DatasourceTableViewWithoutAnalytics = ({
95
118
  reset();
96
119
  }
97
120
  isInitialRender.current = false;
98
- }, [reset, parameters]);
121
+ }, [reset, activeParameters]);
99
122
  useEffect(() => {
100
123
  if (onVisibleColumnKeysChange && (visibleColumnKeys || []).length === 0 && defaultVisibleColumnKeys.length > 0) {
101
124
  onVisibleColumnKeysChange(defaultVisibleColumnKeys);
@@ -141,6 +164,19 @@ const DatasourceTableViewWithoutAnalytics = ({
141
164
  });
142
165
  forcedReset();
143
166
  }, [destinationObjectTypes, extensionKey, fireEvent, forcedReset]);
167
+ const onColumnSort = useCallback(columnKey => {
168
+ // Build next params/sort atomically so UI state and query state stay in sync.
169
+ const result = columnSortGetter === null || columnSortGetter === void 0 ? void 0 : columnSortGetter({
170
+ parameters: sessionBaseParametersRef.current,
171
+ columnKey,
172
+ currentSort: sortState
173
+ });
174
+ if (!result) {
175
+ return;
176
+ }
177
+ setSortState(result.sort);
178
+ setSessionParameters(result.parameters);
179
+ }, [columnSortGetter, sortState]);
144
180
  const handleErrorRefresh = useCallback(() => {
145
181
  reset({
146
182
  shouldForceRequest: true
@@ -172,7 +208,7 @@ const DatasourceTableViewWithoutAnalytics = ({
172
208
  loaderFn: fetchMessagesForLocale
173
209
  }, /*#__PURE__*/React.createElement("div", {
174
210
  className: ax(["_2rko1kw7", "datasource-table"])
175
- }, hasColumns ? /*#__PURE__*/React.createElement(IssueLikeDataTableView, {
211
+ }, hasColumns ? /*#__PURE__*/React.createElement(IssueLikeDataTableView, _extends({
176
212
  testId: 'datasource-table-view',
177
213
  hasNextPage: hasNextPage,
178
214
  items: responseItems,
@@ -184,12 +220,16 @@ const DatasourceTableViewWithoutAnalytics = ({
184
220
  visibleColumnKeys: hasStaleVisibleColumnKeys ? defaultVisibleColumnKeys : visibleColumnKeys || defaultVisibleColumnKeys,
185
221
  onVisibleColumnKeysChange: onVisibleColumnKeysChange,
186
222
  columnCustomSizes: columnCustomSizes,
187
- onColumnResize: onColumnResize,
223
+ onColumnResize: onColumnResize
224
+ }, shouldEnableColumnSort && fg('platform_lp_jira_sllv_renderer_column_sorting') ? {
225
+ onColumnSort,
226
+ sortState
227
+ } : {}, {
188
228
  wrappedColumnKeys: wrappedColumnKeys,
189
229
  onWrappedColumnChange: onWrappedColumnChange,
190
230
  scrollableContainerHeight: isInPDFRender ? undefined : fg('lp_enable_datasource-table-view_height_override') ? scrollableContainerHeight : DefaultScrollableContainerHeight,
191
231
  extensionKey: extensionKey
192
- }) : /*#__PURE__*/React.createElement(EmptyState, {
232
+ })) : /*#__PURE__*/React.createElement(EmptyState, {
193
233
  testId: "datasource-table-view-skeleton",
194
234
  isCompact: true
195
235
  }), /*#__PURE__*/React.createElement(TableFooter, {
@@ -14,6 +14,7 @@
14
14
  .pm-table-wrapper>table thead ._1rmlidpf:last-of-type{border:0}
15
15
  .pm-table-wrapper>table thead ._1u3bidpf{border:0}
16
16
  .pm-table-wrapper>table thead ._ex0g13hi:last-of-type{background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,var(--ds-elevation-surface-current,#fff) 10%)}
17
+ ._1wtnze3t button{padding-block:var(--ds-space-0,0)}
17
18
  .ProseMirror .pm-table-wrapper>table tbody ._15lria51{border-bottom:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}
18
19
  .ProseMirror .pm-table-wrapper>table tbody ._1ho1idpf:last-of-type{border-right:0}
19
20
  .ProseMirror .pm-table-wrapper>table tbody ._1xqpia51{border-right:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}
@@ -66,6 +67,7 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
66
67
  .ProseMirror .pm-table-wrapper>table thead ._zjk41if8:last-of-type{position:sticky}
67
68
  ._11681tcg [data-testid=datasource-header-content--container]{line-height:24px}
68
69
  ._11j11b66 [data-testid=datasource-header-content--container]{padding-right:var(--ds-space-050,4px)}
70
+ ._11lvze3t button{padding-left:var(--ds-space-0,0)}
69
71
  ._124in7od [data-testid=inline-card-icon-and-title]{white-space:unset}
70
72
  ._12pn15vq [data-testid=datasource-header-content--container]{overflow-x:hidden}
71
73
  ._12ruusvi:last-of-type{box-sizing:border-box}
@@ -108,7 +110,9 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
108
110
  ._btyzidpf{border-spacing:0}
109
111
  ._ca0qv77o{padding-top:var(--ds-space-025,2px)}
110
112
  ._ca0qze3t{padding-top:var(--ds-space-0,0)}
113
+ ._ficf1e5h button{text-align:left}
111
114
  ._iscccj1k [data-testid=datasource-header-content--container]{display:-webkit-box}
115
+ ._jq8g1wug button{height:auto}
112
116
  ._k48p1wq8{font-weight:var(--ds-font-weight-medium,500)}
113
117
  ._kqsw1if8{position:sticky}
114
118
  ._kqswh2mm{position:relative}
@@ -130,6 +134,7 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
130
134
  ._vchh1ntv{box-sizing:content-box}
131
135
  ._vchhusvi{box-sizing:border-box}
132
136
  ._vwz41tcg{line-height:24px}
137
+ ._yhjm1b66 button{padding-right:var(--ds-space-050,4px)}
133
138
  ._yq5hus1c{border-collapse:separate}
134
139
  .pm-table-wrapper>table tbody ._18a21kw7{vertical-align:inherit}
135
140
  .pm-table-wrapper>table tbody ._1h12ze3t{padding-right:var(--ds-space-0,0)}
@@ -7,9 +7,14 @@ import { ax, ix } from "@compiled/react/runtime";
7
7
  /* eslint-disable @atlaskit/design-system/use-tokens-typography */
8
8
 
9
9
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
10
+ import { useIntl } from 'react-intl';
10
11
  import invariant from 'tiny-invariant';
12
+ import Button from '@atlaskit/button/new';
11
13
  import { FlagsProvider } from '@atlaskit/flag';
14
+ import SortAscendingIcon from '@atlaskit/icon/core/sort-ascending';
15
+ import SortDescendingIcon from '@atlaskit/icon/core/sort-descending';
12
16
  import { Skeleton } from '@atlaskit/linking-common';
17
+ import { fg } from '@atlaskit/platform-feature-flags';
13
18
  import { extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
14
19
  import { reorderWithEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/util/reorder-with-edge';
15
20
  import { autoScroller } from '@atlaskit/pragmatic-drag-and-drop-react-beautiful-dnd-autoscroll';
@@ -24,6 +29,7 @@ import { ColumnPicker } from './column-picker';
24
29
  import { DragColumnPreview } from './drag-column-preview';
25
30
  import { DraggableTableHeading } from './draggable-table-heading';
26
31
  import TableEmptyState from './empty-state';
32
+ import { issueLikeTableMessages } from './messages';
27
33
  import { renderType } from './render-type';
28
34
  import { TableCellContent } from './table-cell-content';
29
35
  import { useIsOnScreen } from './useIsOnScreen';
@@ -115,6 +121,22 @@ const tableStyles = null;
115
121
  const noDefaultBorderStyles = null;
116
122
  const headerStyles = null;
117
123
  const headingHoverEffectStyles = null;
124
+
125
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-styled -- Matches DraggableTableHeading button normalization.
126
+ const SortableHeaderParent = forwardRef(({
127
+ as: C = "div",
128
+ style: __cmpls,
129
+ ...__cmplp
130
+ }, __cmplr) => {
131
+ return /*#__PURE__*/React.createElement(C, _extends({}, __cmplp, {
132
+ style: __cmpls,
133
+ ref: __cmplr,
134
+ className: ax(["_1e0c1txw _4cvr1h6o _o5721q9c _1wtnze3t _ficf1e5h _jq8g1wug _11lvze3t _yhjm1b66", __cmplp.className])
135
+ }));
136
+ });
137
+ if (process.env.NODE_ENV !== 'production') {
138
+ SortableHeaderParent.displayName = 'SortableHeaderParent';
139
+ }
118
140
  function extractIndex(data) {
119
141
  const {
120
142
  index
@@ -171,14 +193,19 @@ export const IssueLikeDataTableView = ({
171
193
  onVisibleColumnKeysChange,
172
194
  columnCustomSizes,
173
195
  onColumnResize,
196
+ onColumnSort,
174
197
  wrappedColumnKeys,
175
198
  onWrappedColumnChange,
199
+ sortState,
176
200
  status,
177
201
  hasNextPage,
178
202
  scrollableContainerHeight,
179
203
  extensionKey
180
204
  }) => {
181
205
  var _containerRef$current;
206
+ const {
207
+ formatMessage
208
+ } = useIntl();
182
209
  const tableId = useMemo(() => Symbol('unique-id'), []);
183
210
  const experienceId = useDatasourceExperienceId();
184
211
  const tableHeaderRowRef = useRef(null);
@@ -241,7 +268,8 @@ export const IssueLikeDataTableView = ({
241
268
  height: 14,
242
269
  testId: "issues-table-row-loading"
243
270
  })),
244
- key: column.key
271
+ key: column.key,
272
+ width: column.width
245
273
  }))
246
274
  }), [headerColumns]);
247
275
  useEffect(() => {
@@ -363,10 +391,25 @@ export const IssueLikeDataTableView = ({
363
391
  ref: rowIndex === items.length - 1 ? el => setLastRowElement(el) : undefined
364
392
  };
365
393
  }), [items, itemIds, renderItem, wrappedColumnKeys, visibleSortedColumns, getColumnWidth]);
394
+ const previousRealRowsCountRef = useRef(undefined);
395
+ useEffect(() => {
396
+ if (tableRows.length > 0) {
397
+ previousRealRowsCountRef.current = tableRows.length;
398
+ }
399
+ }, [tableRows.length]);
366
400
  const rows = useMemo(() => {
367
- if (status !== 'loading') {
368
- return tableRows;
401
+ if (fg('platform_lp_jira_sllv_renderer_column_sorting')) {
402
+ const hasPreviousRealRows = !!previousRealRowsCountRef.current;
403
+ const isLoadingState = status === 'loading' || status === 'empty' && hasPreviousRealRows;
404
+ if (!isLoadingState) {
405
+ return tableRows;
406
+ }
407
+ } else {
408
+ if (status !== 'loading') {
409
+ return tableRows;
410
+ }
369
411
  }
412
+
370
413
  // if there are table rows, only add 1 loading row
371
414
  if (tableRows.length > 0) {
372
415
  return [...tableRows, {
@@ -376,7 +419,15 @@ export const IssueLikeDataTableView = ({
376
419
  }
377
420
  // if there are no table rows add 14 rows if it is compact (has scrollableContainerHeight or non-modal)
378
421
  // add 10 rows if it is modal (no scrollableContainerHeight)
379
- const loadingRowsCount = scrollableContainerHeight ? 14 : 10;
422
+ let loadingRowsCount = scrollableContainerHeight ? 14 : 10;
423
+ if (fg('platform_lp_jira_sllv_renderer_column_sorting')) {
424
+ const defaultLoadingRowsCount = scrollableContainerHeight ? 14 : 10;
425
+ const previousRealRowsCount = previousRealRowsCountRef.current;
426
+ loadingRowsCount = defaultLoadingRowsCount;
427
+ if (previousRealRowsCount && previousRealRowsCount < defaultLoadingRowsCount) {
428
+ loadingRowsCount = previousRealRowsCount;
429
+ }
430
+ }
380
431
  return [...Array(loadingRowsCount)].map((_, index) => ({
381
432
  ...loadingRow,
382
433
  key: `loading-${index}`
@@ -405,6 +456,9 @@ export const IssueLikeDataTableView = ({
405
456
  }
406
457
  }, [experienceId, extensionKey, hasFullSchema, onLoadDatasourceDetails]);
407
458
  const isEditable = onVisibleColumnKeysChange && hasData;
459
+ // Sorting is enabled only when the table owns the header interaction (read-only mode).
460
+ // Editable tables reserve header interactions for drag/resize/wrap controls.
461
+ const shouldEnableColumnSort = !!onColumnSort && !onVisibleColumnKeysChange && !onColumnResize && !onWrappedColumnChange;
408
462
  const orderedColumnsAreUpToDate = orderedColumns.length === columns.length;
409
463
  const shouldDisplayColumnsInPicker = hasFullSchema && orderedColumnsAreUpToDate;
410
464
  const view = /*#__PURE__*/React.createElement("div", {
@@ -449,7 +503,30 @@ export const IssueLikeDataTableView = ({
449
503
  className: ax(["_11c8wadc _k48p1wq8"])
450
504
  }, content));
451
505
  const isHeadingOutsideButton = !isEditable || !onWrappedColumnChange;
452
- if (isHeadingOutsideButton) {
506
+ if (shouldEnableColumnSort) {
507
+ const sortedDirection = (sortState === null || sortState === void 0 ? void 0 : sortState.key) === key ? sortState.direction : undefined;
508
+ // Use directional icon only for the active sort column; keep width stable otherwise.
509
+ const SortIcon = sortedDirection ? sortedDirection === 'ASC' ? SortAscendingIcon : SortDescendingIcon : null;
510
+ const sortLabel = typeof content === 'string' ? content : key;
511
+ const sortButtonAriaLabel = sortedDirection === 'ASC' ? formatMessage(issueLikeTableMessages.sortByColumnDescendingAction, {
512
+ column: sortLabel
513
+ }) : formatMessage(issueLikeTableMessages.sortByColumnAscendingAction, {
514
+ column: sortLabel
515
+ });
516
+ heading = /*#__PURE__*/React.createElement(SortableHeaderParent, null, /*#__PURE__*/React.createElement(Button, _extends({
517
+ appearance: "subtle",
518
+ spacing: "compact",
519
+ shouldFitContainer: true,
520
+ testId: `${key}-column-sort-button`,
521
+ "aria-label": sortButtonAriaLabel
522
+ }, SortIcon ? {
523
+ iconAfter: iconProps => /*#__PURE__*/React.createElement(SortIcon, _extends({}, iconProps, {
524
+ size: "small"
525
+ }))
526
+ } : {}, {
527
+ onClick: () => onColumnSort === null || onColumnSort === void 0 ? void 0 : onColumnSort(key)
528
+ }), heading));
529
+ } else if (isHeadingOutsideButton) {
453
530
  heading = /*#__PURE__*/React.createElement("div", {
454
531
  className: ax(["_1e0c1txw _4cvr1h6o _o5721q9c _8vu418qm _irr3l4ek"])
455
532
  }, heading);
@@ -483,16 +560,18 @@ export const IssueLikeDataTableView = ({
483
560
  onIsWrappedChange: onWrappedColumnChange === null || onWrappedColumnChange === void 0 ? void 0 : onWrappedColumnChange.bind(null, key)
484
561
  }, heading);
485
562
  } else {
486
- return /*#__PURE__*/React.createElement(TableHeading, {
563
+ return /*#__PURE__*/React.createElement(TableHeading, _extends({
487
564
  key: key,
488
565
  "data-testid": `${key}-column-heading`
566
+ }, fg('platform_lp_jira_sllv_renderer_column_sorting') ? {
567
+ 'aria-sort': (sortState === null || sortState === void 0 ? void 0 : sortState.key) === key && sortState.direction === 'ASC' ? 'ascending' : (sortState === null || sortState === void 0 ? void 0 : sortState.key) === key && sortState.direction === 'DESC' ? 'descending' : undefined
568
+ } : {}, {
489
569
  // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
490
- ,
491
570
  style: getWidthCss({
492
571
  shouldUseWidth,
493
572
  width
494
573
  })
495
- }, heading);
574
+ }), heading);
496
575
  }
497
576
  }), onVisibleColumnKeysChange && /*#__PURE__*/React.createElement(ColumnPickerHeader, null, /*#__PURE__*/React.createElement(ColumnPicker, {
498
577
  columns: shouldDisplayColumnsInPicker ? orderedColumns : [],
@@ -40,6 +40,16 @@ export const issueLikeTableMessages = defineMessages({
40
40
  description: 'Table header Dropdown item for making whole column to not wrap text',
41
41
  defaultMessage: 'Unwrap text'
42
42
  },
43
+ sortByColumnAscendingAction: {
44
+ id: 'linkDataSource.issue-line-table.sort-by-column-ascending-action',
45
+ description: 'Accessible label for sorting a table column in ascending order',
46
+ defaultMessage: 'Sort by {column} ascending.'
47
+ },
48
+ sortByColumnDescendingAction: {
49
+ id: 'linkDataSource.issue-line-table.sort-by-column-descending-action',
50
+ description: 'Accessible label for sorting a table column in descending order',
51
+ defaultMessage: 'Sort by {column} descending.'
52
+ },
43
53
  fetchActionErrorGenericDescriptionGalaxia: {
44
54
  id: 'linkDataSource.issue-line-table.fetch-action-error-generic-description-galaxia',
45
55
  description: 'Generic error message description shown when fetching inline edit dropdown field fails',
@@ -0,0 +1,53 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
+ import { JIRA_LIST_OF_LINKS_DATASOURCE_ID } from '../jira-issues-modal';
5
+ var getNextSortDirection = function getNextSortDirection(columnKey, currentSort) {
6
+ if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.key) !== columnKey) {
7
+ return 'ASC';
8
+ }
9
+ if (currentSort.direction === 'ASC') {
10
+ return 'DESC';
11
+ }
12
+ return undefined;
13
+ };
14
+ // Match and remove an existing trailing ORDER BY clause before appending the next sort:
15
+ // - (?:^|\\s+) allows ORDER BY at the start of the JQL or after whitespace.
16
+ // - ORDER\\s+BY matches ORDER BY with flexible spacing and is case-insensitive (/i).
17
+ // - [\\s\\S]*$ consumes everything after ORDER BY to the end of the JQL.
18
+ // Examples:
19
+ // - "project = TEST ORDER BY created DESC" -> "project = TEST"
20
+ // - "ORDER BY priority ASC" -> ""
21
+ var JIRA_ORDER_BY_PATTERN = /(?:^|\s+)ORDER\s+BY\s+[\s\S]*$/i;
22
+ var getJiraColumnSortParameters = function getJiraColumnSortParameters(_ref) {
23
+ var parameters = _ref.parameters,
24
+ columnKey = _ref.columnKey,
25
+ currentSort = _ref.currentSort;
26
+ var direction = getNextSortDirection(columnKey, currentSort);
27
+ if (!direction) {
28
+ return {
29
+ parameters: parameters,
30
+ sort: undefined
31
+ };
32
+ }
33
+ if (typeof parameters.jql !== 'string') {
34
+ return undefined;
35
+ }
36
+ var jqlWithoutOrder = parameters.jql.replace(JIRA_ORDER_BY_PATTERN, '').trim();
37
+ var nextOrderByClause = "ORDER BY ".concat(columnKey, " ").concat(direction);
38
+ return {
39
+ parameters: _objectSpread(_objectSpread({}, parameters), {}, {
40
+ jql: [jqlWithoutOrder, nextOrderByClause].filter(Boolean).join(' ')
41
+ }),
42
+ sort: {
43
+ key: columnKey,
44
+ direction: direction
45
+ }
46
+ };
47
+ };
48
+ export var getDatasourceColumnSortGetter = function getDatasourceColumnSortGetter(datasourceId) {
49
+ if (datasourceId === JIRA_LIST_OF_LINKS_DATASOURCE_ID) {
50
+ return getJiraColumnSortParameters;
51
+ }
52
+ return undefined;
53
+ };
@@ -1,8 +1,11 @@
1
1
  /* datasourceTableView.tsx generated by @compiled/babel-plugin v0.39.1 */
2
+ import _extends from "@babel/runtime/helpers/extends";
3
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
4
  import "./datasourceTableView.compiled.css";
3
5
  import * as React from 'react';
4
6
  import { ax, ix } from "@compiled/react/runtime";
5
- import { useCallback, useEffect, useRef } from 'react';
7
+ import { useCallback, useEffect, useRef, useState } from 'react';
8
+ import isEqual from 'lodash/isEqual';
6
9
  import { withAnalyticsContext } from '@atlaskit/analytics-next';
7
10
  import { IntlMessagesProvider } from '@atlaskit/intl-messages-provider';
8
11
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -26,6 +29,7 @@ import { ProviderAuthRequired } from '../common/error-state/provider-auth-requir
26
29
  import { IssueLikeDataTableView } from '../issue-like-table';
27
30
  import EmptyState from '../issue-like-table/empty-state';
28
31
  import { TableFooter } from '../table-footer';
32
+ import { getDatasourceColumnSortGetter } from './datasource-column-sort';
29
33
  var containerStyles = null;
30
34
  var DefaultScrollableContainerHeight = 590;
31
35
  var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAnalytics(_ref) {
@@ -40,9 +44,35 @@ var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAna
40
44
  onWrappedColumnChange = _ref.onWrappedColumnChange,
41
45
  _ref$scrollableContai = _ref.scrollableContainerHeight,
42
46
  scrollableContainerHeight = _ref$scrollableContai === void 0 ? DefaultScrollableContainerHeight : _ref$scrollableContai;
47
+ // Local copy lets us apply in-session sort mutations without mutating external parameters.
48
+ var _useState = useState(parameters),
49
+ _useState2 = _slicedToArray(_useState, 2),
50
+ sessionParameters = _useState2[0],
51
+ setSessionParameters = _useState2[1];
52
+ // Tracks the external parameters that the current session/sort state was derived from.
53
+ var sessionBaseParametersRef = useRef(parameters);
54
+ var _useState3 = useState(),
55
+ _useState4 = _slicedToArray(_useState3, 2),
56
+ sortState = _useState4[0],
57
+ setSortState = _useState4[1];
58
+ var columnSortGetter = getDatasourceColumnSortGetter(datasourceId);
59
+ // Sorting is only owned by this view when parent callbacks are absent (read-only rendering mode).
60
+ var isReadOnlyDatasourceTable = !onVisibleColumnKeysChange && !onColumnResize && !onWrappedColumnChange;
61
+ // Keep sort UI hidden unless datasource + ownership + feature gate all allow it.
62
+ var shouldEnableColumnSort = !!columnSortGetter && isReadOnlyDatasourceTable && fg('platform_lp_jira_sllv_renderer_column_sorting');
63
+ useDeepEffect(function () {
64
+ // External parameter updates should always reset local sort/session state back to source-of-truth.
65
+ sessionBaseParametersRef.current = parameters;
66
+ setSessionParameters(parameters);
67
+ setSortState(undefined);
68
+ }, [parameters]);
69
+ var isSessionBasedOnCurrentParameters = isEqual(sessionBaseParametersRef.current, parameters);
70
+ // Use session parameters only when they are known to be based on the current external parameters.
71
+ // This avoids a one-render stale read during the deep-effect update cycle above.
72
+ var activeParameters = isSessionBasedOnCurrentParameters && fg('platform_lp_jira_sllv_renderer_column_sorting') ? sessionParameters : parameters;
43
73
  var _useDatasourceTableSt = useDatasourceTableState({
44
74
  datasourceId: datasourceId,
45
- parameters: parameters,
75
+ parameters: activeParameters,
46
76
  fieldKeys: visibleColumnKeys
47
77
  }),
48
78
  reset = _useDatasourceTableSt.reset,
@@ -98,7 +128,7 @@ var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAna
98
128
  reset();
99
129
  }
100
130
  isInitialRender.current = false;
101
- }, [reset, parameters]);
131
+ }, [reset, activeParameters]);
102
132
  useEffect(function () {
103
133
  if (onVisibleColumnKeysChange && (visibleColumnKeys || []).length === 0 && defaultVisibleColumnKeys.length > 0) {
104
134
  onVisibleColumnKeysChange(defaultVisibleColumnKeys);
@@ -144,6 +174,19 @@ var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAna
144
174
  });
145
175
  forcedReset();
146
176
  }, [destinationObjectTypes, extensionKey, fireEvent, forcedReset]);
177
+ var onColumnSort = useCallback(function (columnKey) {
178
+ // Build next params/sort atomically so UI state and query state stay in sync.
179
+ var result = columnSortGetter === null || columnSortGetter === void 0 ? void 0 : columnSortGetter({
180
+ parameters: sessionBaseParametersRef.current,
181
+ columnKey: columnKey,
182
+ currentSort: sortState
183
+ });
184
+ if (!result) {
185
+ return;
186
+ }
187
+ setSortState(result.sort);
188
+ setSessionParameters(result.parameters);
189
+ }, [columnSortGetter, sortState]);
147
190
  var handleErrorRefresh = useCallback(function () {
148
191
  reset({
149
192
  shouldForceRequest: true
@@ -175,7 +218,7 @@ var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAna
175
218
  loaderFn: fetchMessagesForLocale
176
219
  }, /*#__PURE__*/React.createElement("div", {
177
220
  className: ax(["_2rko1kw7", "datasource-table"])
178
- }, hasColumns ? /*#__PURE__*/React.createElement(IssueLikeDataTableView, {
221
+ }, hasColumns ? /*#__PURE__*/React.createElement(IssueLikeDataTableView, _extends({
179
222
  testId: 'datasource-table-view',
180
223
  hasNextPage: hasNextPage,
181
224
  items: responseItems,
@@ -187,12 +230,16 @@ var DatasourceTableViewWithoutAnalytics = function DatasourceTableViewWithoutAna
187
230
  visibleColumnKeys: hasStaleVisibleColumnKeys ? defaultVisibleColumnKeys : visibleColumnKeys || defaultVisibleColumnKeys,
188
231
  onVisibleColumnKeysChange: onVisibleColumnKeysChange,
189
232
  columnCustomSizes: columnCustomSizes,
190
- onColumnResize: onColumnResize,
233
+ onColumnResize: onColumnResize
234
+ }, shouldEnableColumnSort && fg('platform_lp_jira_sllv_renderer_column_sorting') ? {
235
+ onColumnSort: onColumnSort,
236
+ sortState: sortState
237
+ } : {}, {
191
238
  wrappedColumnKeys: wrappedColumnKeys,
192
239
  onWrappedColumnChange: onWrappedColumnChange,
193
240
  scrollableContainerHeight: isInPDFRender ? undefined : fg('lp_enable_datasource-table-view_height_override') ? scrollableContainerHeight : DefaultScrollableContainerHeight,
194
241
  extensionKey: extensionKey
195
- }) : /*#__PURE__*/React.createElement(EmptyState, {
242
+ })) : /*#__PURE__*/React.createElement(EmptyState, {
196
243
  testId: "datasource-table-view-skeleton",
197
244
  isCompact: true
198
245
  }), /*#__PURE__*/React.createElement(TableFooter, {
@@ -14,6 +14,7 @@
14
14
  .pm-table-wrapper>table thead ._1rmlidpf:last-of-type{border:0}
15
15
  .pm-table-wrapper>table thead ._1u3bidpf{border:0}
16
16
  .pm-table-wrapper>table thead ._ex0g13hi:last-of-type{background:linear-gradient(90deg,hsla(0,0%,100%,0) 0,var(--ds-elevation-surface-current,#fff) 10%)}
17
+ ._1wtnze3t button{padding-block:var(--ds-space-0,0)}
17
18
  .ProseMirror .pm-table-wrapper>table tbody ._15lria51{border-bottom:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}
18
19
  .ProseMirror .pm-table-wrapper>table tbody ._1ho1idpf:last-of-type{border-right:0}
19
20
  .ProseMirror .pm-table-wrapper>table tbody ._1xqpia51{border-right:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}
@@ -66,6 +67,7 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
66
67
  .ProseMirror .pm-table-wrapper>table thead ._zjk41if8:last-of-type{position:sticky}
67
68
  ._11681tcg [data-testid=datasource-header-content--container]{line-height:24px}
68
69
  ._11j11b66 [data-testid=datasource-header-content--container]{padding-right:var(--ds-space-050,4px)}
70
+ ._11lvze3t button{padding-left:var(--ds-space-0,0)}
69
71
  ._124in7od [data-testid=inline-card-icon-and-title]{white-space:unset}
70
72
  ._12pn15vq [data-testid=datasource-header-content--container]{overflow-x:hidden}
71
73
  ._12ruusvi:last-of-type{box-sizing:border-box}
@@ -108,7 +110,9 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
108
110
  ._btyzidpf{border-spacing:0}
109
111
  ._ca0qv77o{padding-top:var(--ds-space-025,2px)}
110
112
  ._ca0qze3t{padding-top:var(--ds-space-0,0)}
113
+ ._ficf1e5h button{text-align:left}
111
114
  ._iscccj1k [data-testid=datasource-header-content--container]{display:-webkit-box}
115
+ ._jq8g1wug button{height:auto}
112
116
  ._k48p1wq8{font-weight:var(--ds-font-weight-medium,500)}
113
117
  ._kqsw1if8{position:sticky}
114
118
  ._kqswh2mm{position:relative}
@@ -130,6 +134,7 @@ thead.has-column-picker ._10i2idpf:nth-last-of-type(2){border-right:0}.ProseMirr
130
134
  ._vchh1ntv{box-sizing:content-box}
131
135
  ._vchhusvi{box-sizing:border-box}
132
136
  ._vwz41tcg{line-height:24px}
137
+ ._yhjm1b66 button{padding-right:var(--ds-space-050,4px)}
133
138
  ._yq5hus1c{border-collapse:separate}
134
139
  .pm-table-wrapper>table tbody ._18a21kw7{vertical-align:inherit}
135
140
  .pm-table-wrapper>table tbody ._1h12ze3t{padding-right:var(--ds-space-0,0)}