@atlaskit/editor-synced-block-provider 15.0.3 → 15.1.0

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 (38) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/clients/block-service/ari.js +28 -1
  3. package/dist/cjs/clients/jira/ari.js +36 -1
  4. package/dist/cjs/clients/jira/sourceInfo.js +34 -10
  5. package/dist/cjs/common/types.js +5 -0
  6. package/dist/cjs/providers/block-service/blockServiceAPI.js +12 -4
  7. package/dist/cjs/providers/syncBlockProvider.js +9 -2
  8. package/dist/cjs/store-manager/referenceSyncBlockStoreManager.js +17 -5
  9. package/dist/cjs/store-manager/syncBlockStoreManager.js +5 -2
  10. package/dist/cjs/utils/fieldAwareLocations.js +30 -0
  11. package/dist/cjs/utils/resolveSyncBlockInstance.js +8 -2
  12. package/dist/es2019/clients/block-service/ari.js +28 -0
  13. package/dist/es2019/clients/jira/ari.js +35 -0
  14. package/dist/es2019/clients/jira/sourceInfo.js +52 -6
  15. package/dist/es2019/common/types.js +6 -0
  16. package/dist/es2019/providers/block-service/blockServiceAPI.js +11 -3
  17. package/dist/es2019/providers/syncBlockProvider.js +7 -1
  18. package/dist/es2019/store-manager/referenceSyncBlockStoreManager.js +14 -0
  19. package/dist/es2019/store-manager/syncBlockStoreManager.js +3 -0
  20. package/dist/es2019/utils/fieldAwareLocations.js +21 -0
  21. package/dist/es2019/utils/resolveSyncBlockInstance.js +10 -2
  22. package/dist/esm/clients/block-service/ari.js +27 -0
  23. package/dist/esm/clients/jira/ari.js +35 -0
  24. package/dist/esm/clients/jira/sourceInfo.js +35 -11
  25. package/dist/esm/common/types.js +6 -0
  26. package/dist/esm/providers/block-service/blockServiceAPI.js +13 -5
  27. package/dist/esm/providers/syncBlockProvider.js +9 -2
  28. package/dist/esm/store-manager/referenceSyncBlockStoreManager.js +17 -5
  29. package/dist/esm/store-manager/syncBlockStoreManager.js +5 -2
  30. package/dist/esm/utils/fieldAwareLocations.js +24 -0
  31. package/dist/esm/utils/resolveSyncBlockInstance.js +8 -2
  32. package/dist/types/clients/block-service/ari.d.ts +13 -1
  33. package/dist/types/clients/jira/ari.d.ts +15 -0
  34. package/dist/types/common/types.d.ts +12 -0
  35. package/dist/types/entry-points/common-types.d.ts +1 -1
  36. package/dist/types/providers/types.d.ts +6 -1
  37. package/dist/types/utils/fieldAwareLocations.d.ts +16 -0
  38. package/package.json +3 -3
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { useMemo } from 'react';
4
4
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
5
- import { generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
5
+ import { generateBlockAri, generateBlockAriFromReference, getProductFromSourceAri } from '../../clients/block-service/ari';
6
6
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
7
7
  import { SyncBlockError } from '../../common/types';
8
8
  import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
9
+ import { getFieldAwareLocationScope } from '../../utils/fieldAwareLocations';
9
10
  import { createResourceIdForReference } from '../../utils/resourceId';
10
11
  import { convertContentUpdatedAt } from '../../utils/utils';
11
12
  const BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -500,12 +501,18 @@ class BlockServiceADFFetchProvider {
500
501
  const response = await getReferenceSyncedBlocksByBlockAri({
501
502
  blockAri
502
503
  });
504
+ const locationScopeOf = documentAri => getFieldAwareLocationScope({
505
+ documentAri,
506
+ hostAri: this.parentAri,
507
+ productType: getProductFromSourceAri(documentAri)
508
+ });
503
509
  const references = [];
504
510
  response.references.forEach(reference => {
505
511
  references.push({
506
512
  ...reference,
507
513
  hasAccess: true,
508
- onSameDocument: this.parentAri === reference.documentAri
514
+ onSameDocument: this.parentAri === reference.documentAri,
515
+ ...locationScopeOf(reference.documentAri)
509
516
  });
510
517
  });
511
518
  response.errors.forEach(reference => {
@@ -514,7 +521,8 @@ class BlockServiceADFFetchProvider {
514
521
  blockAri: reference.blockAri,
515
522
  documentAri: reference.documentAri,
516
523
  hasAccess: false,
517
- onSameDocument: false
524
+ onSameDocument: false,
525
+ ...locationScopeOf(reference.documentAri)
518
526
  });
519
527
  }
520
528
  });
@@ -5,6 +5,7 @@ import { getPageIdAndTypeFromConfluencePageAri } from '../clients/confluence/ari
5
5
  import { fetchConfluencePageInfo } from '../clients/confluence/sourceInfo';
6
6
  import { fetchJiraWorkItemInfo } from '../clients/jira/sourceInfo';
7
7
  import { SyncBlockError } from '../common/types';
8
+ import { getFieldAwareLocationScope } from '../utils/fieldAwareLocations';
8
9
  import { SyncBlockDataProviderInterface } from './types';
9
10
  export class SyncedBlockProvider extends SyncBlockDataProviderInterface {
10
11
  /**
@@ -217,7 +218,7 @@ export class SyncedBlockProvider extends SyncBlockDataProviderInterface {
217
218
  }
218
219
  case 'jira-work-item':
219
220
  {
220
- var _this$writeProvider4;
221
+ var _this$writeProvider4, _this$writeProvider5;
221
222
  // Note: `subType`, archived URL handling, and `isUnpublished` are intentionally
222
223
  // omitted here — Jira work items have no equivalent page subtype or archived state.
223
224
  // The `localId` param is intentionally not forwarded: the current Jira issue view
@@ -231,6 +232,11 @@ export class SyncedBlockProvider extends SyncBlockDataProviderInterface {
231
232
  return {
232
233
  ...sourceInfo,
233
234
  onSameDocument: ((_this$writeProvider4 = this.writeProvider) === null || _this$writeProvider4 === void 0 ? void 0 : _this$writeProvider4.parentAri) === ari,
235
+ ...getFieldAwareLocationScope({
236
+ documentAri: ari,
237
+ hostAri: (_this$writeProvider5 = this.writeProvider) === null || _this$writeProvider5 === void 0 ? void 0 : _this$writeProvider5.parentAri,
238
+ productType: product
239
+ }),
234
240
  productType: product
235
241
  };
236
242
  }
@@ -309,6 +309,8 @@ export class ReferenceSyncBlockStoreManager {
309
309
  sourceURL,
310
310
  sourceTitle,
311
311
  onSameDocument,
312
+ fieldName,
313
+ locationScope,
312
314
  sourceSubType,
313
315
  issueType
314
316
  } = existingSyncBlock.data || {};
@@ -320,6 +322,12 @@ export class ReferenceSyncBlockStoreManager {
320
322
  subType: sourceSubType,
321
323
  sourceAri: sourceAri || '',
322
324
  onSameDocument,
325
+ ...(fieldName !== undefined && {
326
+ fieldName
327
+ }),
328
+ ...(locationScope !== undefined && {
329
+ locationScope
330
+ }),
323
331
  productType: product,
324
332
  issueType
325
333
  });
@@ -599,6 +607,12 @@ export class ReferenceSyncBlockStoreManager {
599
607
  sourceURL: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url,
600
608
  sourceTitle: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.title,
601
609
  onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument,
610
+ ...((sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.fieldName) !== undefined && {
611
+ fieldName: sourceInfo.fieldName
612
+ }),
613
+ ...((sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.locationScope) !== undefined && {
614
+ locationScope: sourceInfo.locationScope
615
+ }),
602
616
  sourceSubType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.subType,
603
617
  issueType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.issueType
604
618
  };
@@ -64,6 +64,9 @@ export class SyncBlockStoreManager {
64
64
  return {
65
65
  ...sourceInfo,
66
66
  onSameDocument: reference.onSameDocument,
67
+ ...(reference.locationScope !== undefined && {
68
+ locationScope: reference.locationScope
69
+ }),
67
70
  hasAccess: reference.hasAccess,
68
71
  productType: sourceInfo.productType
69
72
  };
@@ -0,0 +1,21 @@
1
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
2
+ import { getLocationScope } from '../clients/block-service/ari';
3
+ /**
4
+ * The product check runs first so a Confluence-only document never reads the experiment.
5
+ */
6
+ export const isFieldAwareJiraLocation = productType => productType === 'jira-work-item' && isExperimentEnabled('editor_synced_blocks_jira_custom_rich_text');
7
+
8
+ /**
9
+ * Spreadable `{ locationScope }` for a location that should carry one, `undefined` otherwise.
10
+ * Without a host ARI there is nothing to compare against, so the experiment is not read.
11
+ */
12
+ export const getFieldAwareLocationScope = ({
13
+ documentAri,
14
+ hostAri,
15
+ productType
16
+ }) => hostAri !== undefined && isFieldAwareJiraLocation(productType) ? {
17
+ locationScope: getLocationScope({
18
+ documentAri,
19
+ hostAri
20
+ })
21
+ } : undefined;
@@ -12,7 +12,7 @@ import { SyncBlockError } from '../common/types';
12
12
  * @returns A merged SyncBlockInstance object.
13
13
  */
14
14
  export const resolveSyncBlockInstance = (oldResult, newResult) => {
15
- var _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
15
+ var _newResult$data$field, _newResult$data$locat, _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
16
16
  // if the old result has no data, we simply return the new result
17
17
  if (!oldResult.data) {
18
18
  return newResult;
@@ -27,6 +27,8 @@ export const resolveSyncBlockInstance = (oldResult, newResult) => {
27
27
  }
28
28
 
29
29
  // otherwise, we merge the two results, preserving the sourceURL and sourceTitle from the old result if it exists
30
+ const fieldName = (_newResult$data$field = newResult.data.fieldName) !== null && _newResult$data$field !== void 0 ? _newResult$data$field : oldResult.data.fieldName;
31
+ const locationScope = (_newResult$data$locat = newResult.data.locationScope) !== null && _newResult$data$locat !== void 0 ? _newResult$data$locat : oldResult.data.locationScope;
30
32
  return {
31
33
  ...newResult,
32
34
  data: {
@@ -35,7 +37,13 @@ export const resolveSyncBlockInstance = (oldResult, newResult) => {
35
37
  sourceTitle: ((_newResult$data2 = newResult.data) === null || _newResult$data2 === void 0 ? void 0 : _newResult$data2.sourceTitle) || ((_oldResult$data2 = oldResult.data) === null || _oldResult$data2 === void 0 ? void 0 : _oldResult$data2.sourceTitle) || undefined,
36
38
  sourceSubType: mergeSubType(oldResult, newResult),
37
39
  onSameDocument: ((_newResult$data3 = newResult.data) === null || _newResult$data3 === void 0 ? void 0 : _newResult$data3.onSameDocument) || ((_oldResult$data3 = oldResult.data) === null || _oldResult$data3 === void 0 ? void 0 : _oldResult$data3.onSameDocument) || undefined,
38
- issueType: ((_newResult$data4 = newResult.data) === null || _newResult$data4 === void 0 ? void 0 : _newResult$data4.issueType) || ((_oldResult$data4 = oldResult.data) === null || _oldResult$data4 === void 0 ? void 0 : _oldResult$data4.issueType) || undefined
40
+ issueType: ((_newResult$data4 = newResult.data) === null || _newResult$data4 === void 0 ? void 0 : _newResult$data4.issueType) || ((_oldResult$data4 = oldResult.data) === null || _oldResult$data4 === void 0 ? void 0 : _oldResult$data4.issueType) || undefined,
41
+ ...(fieldName !== undefined && {
42
+ fieldName
43
+ }),
44
+ ...(locationScope !== undefined && {
45
+ locationScope
46
+ })
39
47
  }
40
48
  };
41
49
  };
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
2
 
3
+ import { parseJiraFieldLocation } from '../jira/ari';
3
4
  var GET_LOCAL_ID_FROM_BLOCK_RESOURCE_ID_REGEX = /ari:cloud:blocks:[^:]+:synced-block\/([a-zA-Z0-9-]+)/;
4
5
  var JIRA_SOURCE_ARI_REGEX = /ari:cloud:jira:.*/;
5
6
  var CONFLUENCE_SOURCE_ARI_REGEX = /ari:cloud:confluence:.*/;
@@ -46,6 +47,32 @@ export var getLocalIdFromBlockResourceId = function getLocalIdFromBlockResourceI
46
47
  }
47
48
  throw new Error("Invalid block ARI: ".concat(ari));
48
49
  };
50
+
51
+ /**
52
+ * Where `documentAri` sits relative to the document hosting the editor.
53
+ *
54
+ * Jira admits two ARI forms for one work item field: `issuefieldvalue/{issueId}/{fieldId}`
55
+ * names any field, and a plain issue ARI names the description field. Jira's own
56
+ * `isParentAriValid` accepts either as a host, so the two are compared on the field they
57
+ * denote rather than as strings.
58
+ */
59
+ export var getLocationScope = function getLocationScope(_ref3) {
60
+ var documentAri = _ref3.documentAri,
61
+ hostAri = _ref3.hostAri;
62
+ if (documentAri === hostAri) {
63
+ return 'same-document';
64
+ }
65
+ var location = parseJiraFieldLocation({
66
+ ari: documentAri
67
+ });
68
+ var hostLocation = parseJiraFieldLocation({
69
+ ari: hostAri
70
+ });
71
+ if (!location || !hostLocation || location.issueAri !== hostLocation.issueAri) {
72
+ return 'elsewhere';
73
+ }
74
+ return location.fieldId === hostLocation.fieldId ? 'same-document' : 'same-parent-document';
75
+ };
49
76
  export var getProductFromSourceAri = function getProductFromSourceAri(ari) {
50
77
  var jiraMatch = ari === null || ari === void 0 ? void 0 : ari.search(JIRA_SOURCE_ARI_REGEX);
51
78
  if (jiraMatch !== -1) {
@@ -62,4 +62,39 @@ export var getJiraIssueAriFromSourceAri = function getJiraIssueAriFromSourceAri(
62
62
  });
63
63
  }
64
64
  return ari;
65
+ };
66
+ var JIRA_DESCRIPTION_FIELD_ID = 'description';
67
+
68
+ /**
69
+ * One synced block location on a Jira work item, parsed once from its document ARI.
70
+ * Two locations name the same field when both members match, whichever of the two ARI
71
+ * forms each was written in.
72
+ */
73
+
74
+ var toJiraFieldLocation = function toJiraFieldLocation(_ref4) {
75
+ var cloudId = _ref4.cloudId,
76
+ fieldId = _ref4.fieldId,
77
+ issueId = _ref4.issueId;
78
+ return {
79
+ fieldId: fieldId,
80
+ issueAri: getJiraWorkItemAri({
81
+ cloudId: cloudId,
82
+ workItemId: issueId
83
+ })
84
+ };
85
+ };
86
+
87
+ /** A plain issue ARI is the work item's description field. */
88
+ export var parseJiraFieldLocation = function parseJiraFieldLocation(_ref5) {
89
+ var ari = _ref5.ari;
90
+ var workItemMatch = ari.match(JIRA_WORK_ITEM_ARI_REGEX);
91
+ if (workItemMatch !== null && workItemMatch !== void 0 && workItemMatch[1] && workItemMatch[2]) {
92
+ return toJiraFieldLocation({
93
+ cloudId: workItemMatch[1],
94
+ fieldId: JIRA_DESCRIPTION_FIELD_ID,
95
+ issueId: workItemMatch[2]
96
+ });
97
+ }
98
+ var fieldValueParts = getFieldValueAriParts(ari);
99
+ return fieldValueParts && toJiraFieldLocation(fieldValueParts);
65
100
  };
@@ -5,8 +5,9 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
5
5
  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; }
6
6
  /* eslint-disable require-unicode-regexp */
7
7
 
8
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
8
9
  import { fetchWithRetry } from '../../utils/retry';
9
- import { getJiraIssueAriFromSourceAri } from './ari';
10
+ import { getJiraIssueAriFromSourceAri, parseJiraFieldLocation } from './ari';
10
11
  var COMMON_HEADERS = {
11
12
  'Content-Type': 'application/json',
12
13
  Accept: 'application/json'
@@ -24,16 +25,33 @@ var GET_SOURCE_INFO_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_GET_SOURCE_INFO';
24
25
  // `issueType` is requested alongside `summary` and `webUrl` so the
25
26
  // SyncedLocationDropdown can render the correct ADS issue-type icon.
26
27
  var GET_SOURCE_INFO_QUERY = "query ".concat(GET_SOURCE_INFO_OPERATION_NAME, " ($id: ID!) {\n jira {\n\tissueById(id: $id) {\n\tid\n\twebUrl\n\tsummary\n\tissueType {\n\t\tname\n\t\tavatar {\n\t\txsmall\n\t\t}\n\t}\n\t}\n }}");
28
+ // `fieldsById(ids:)` takes bare field ids, the same shape it returns on `node.fieldId`.
29
+ // Its schema doc gives `issuefieldvalue` ARIs as the example, but AGG resolves those to a
30
+ // null node. Verified against hello.atlassian.net by sending an ARI and a bare id together.
31
+ var GET_SOURCE_INFO_WITH_FIELD_QUERY = "query ".concat(GET_SOURCE_INFO_OPERATION_NAME, " ($id: ID!, $fieldIds: [ID!]!) {\n jira {\n\tissueById(id: $id) {\n\tid\n\twebUrl\n\tsummary\n\tissueType {\n\t\tname\n\t\tavatar {\n\t\txsmall\n\t\t}\n\t}\n\tfieldsById(ids: $fieldIds) {\n\t\tedges {\n\t\tnode {\n\t\t\tfieldId\n\t\t\tname\n\t\t}\n\t\t}\n\t}\n\t}\n }}");
32
+
33
+ // AGG does not promise `edges` follows the order of the requested ids.
34
+ var getFieldName = function getFieldName(contentData, fieldId) {
35
+ var _contentData$fieldsBy;
36
+ var name = contentData === null || contentData === void 0 || (_contentData$fieldsBy = contentData.fieldsById) === null || _contentData$fieldsBy === void 0 || (_contentData$fieldsBy = _contentData$fieldsBy.edges) === null || _contentData$fieldsBy === void 0 || (_contentData$fieldsBy = _contentData$fieldsBy.find(function (edge) {
37
+ var _edge$node;
38
+ return (edge === null || edge === void 0 || (_edge$node = edge.node) === null || _edge$node === void 0 ? void 0 : _edge$node.fieldId) === fieldId;
39
+ })) === null || _contentData$fieldsBy === void 0 || (_contentData$fieldsBy = _contentData$fieldsBy.node) === null || _contentData$fieldsBy === void 0 ? void 0 : _contentData$fieldsBy.name;
40
+ return typeof name === 'string' && name.length > 0 ? name : undefined;
41
+ };
27
42
  var getJiraWorkItemSourceInfo = /*#__PURE__*/function () {
28
- var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(issueAri) {
43
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(issueAri, fieldId) {
29
44
  var bodyData, response;
30
45
  return _regeneratorRuntime.wrap(function (_context) {
31
46
  while (1) switch (_context.prev = _context.next) {
32
47
  case 0:
33
48
  bodyData = {
34
- query: GET_SOURCE_INFO_QUERY,
49
+ query: fieldId ? GET_SOURCE_INFO_WITH_FIELD_QUERY : GET_SOURCE_INFO_QUERY,
35
50
  operationName: GET_SOURCE_INFO_OPERATION_NAME,
36
- variables: {
51
+ variables: fieldId ? {
52
+ id: issueAri,
53
+ fieldIds: [fieldId]
54
+ } : {
37
55
  id: issueAri
38
56
  }
39
57
  };
@@ -61,7 +79,7 @@ var getJiraWorkItemSourceInfo = /*#__PURE__*/function () {
61
79
  }
62
80
  }, _callee);
63
81
  }));
64
- return function getJiraWorkItemSourceInfo(_x) {
82
+ return function getJiraWorkItemSourceInfo(_x, _x2) {
65
83
  return _ref.apply(this, arguments);
66
84
  };
67
85
  }();
@@ -107,7 +125,7 @@ var resolveNoAccessWorkItemInfo = /*#__PURE__*/function () {
107
125
  }
108
126
  }, _callee2);
109
127
  }));
110
- return function resolveNoAccessWorkItemInfo(_x2, _x3) {
128
+ return function resolveNoAccessWorkItemInfo(_x3, _x4) {
111
129
  return _ref2.apply(this, arguments);
112
130
  };
113
131
  }();
@@ -134,7 +152,7 @@ var resolveNoAccessWorkItemInfo = /*#__PURE__*/function () {
134
152
  */
135
153
  export var fetchJiraWorkItemInfo = /*#__PURE__*/function () {
136
154
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3(workItemAri, hasAccess) {
137
- var issueAri, _response$data, _contentData$issueTyp, _contentData$issueTyp2, response, contentData, webUrl, summary, issueTypeName, issueType;
155
+ var issueAri, _response$data, _contentData$issueTyp, _contentData$issueTyp2, fieldLocation, response, contentData, webUrl, summary, issueTypeName, issueType, fieldName;
138
156
  return _regeneratorRuntime.wrap(function (_context3) {
139
157
  while (1) switch (_context3.prev = _context3.next) {
140
158
  case 0:
@@ -147,8 +165,11 @@ export var fetchJiraWorkItemInfo = /*#__PURE__*/function () {
147
165
  _context3.next = 2;
148
166
  break;
149
167
  }
168
+ fieldLocation = isExperimentEnabled('editor_synced_blocks_jira_custom_rich_text') ? parseJiraFieldLocation({
169
+ ari: workItemAri
170
+ }) : undefined;
150
171
  _context3.next = 1;
151
- return getJiraWorkItemSourceInfo(issueAri);
172
+ return getJiraWorkItemSourceInfo(issueAri, fieldLocation === null || fieldLocation === void 0 ? void 0 : fieldLocation.fieldId);
152
173
  case 1:
153
174
  response = _context3.sent;
154
175
  contentData = (_response$data = response.data) === null || _response$data === void 0 || (_response$data = _response$data.jira) === null || _response$data === void 0 ? void 0 : _response$data.issueById;
@@ -161,12 +182,15 @@ export var fetchJiraWorkItemInfo = /*#__PURE__*/function () {
161
182
  name: issueTypeName,
162
183
  iconUrl: typeof (contentData === null || contentData === void 0 || (_contentData$issueTyp2 = contentData.issueType) === null || _contentData$issueTyp2 === void 0 || (_contentData$issueTyp2 = _contentData$issueTyp2.avatar) === null || _contentData$issueTyp2 === void 0 ? void 0 : _contentData$issueTyp2.xsmall) === 'string' ? contentData.issueType.avatar.xsmall : undefined
163
184
  } : undefined;
164
- return _context3.abrupt("return", Promise.resolve({
185
+ fieldName = fieldLocation && getFieldName(contentData, fieldLocation.fieldId);
186
+ return _context3.abrupt("return", Promise.resolve(_objectSpread({
165
187
  url: webUrl,
166
188
  sourceAri: workItemAri,
167
189
  title: summary,
168
190
  issueType: issueType
169
- }));
191
+ }, fieldName !== undefined && {
192
+ fieldName: fieldName
193
+ })));
170
194
  case 2:
171
195
  _context3.next = 3;
172
196
  return resolveNoAccessWorkItemInfo(workItemAri, issueAri);
@@ -178,7 +202,7 @@ export var fetchJiraWorkItemInfo = /*#__PURE__*/function () {
178
202
  }
179
203
  }, _callee3);
180
204
  }));
181
- return function fetchJiraWorkItemInfo(_x4, _x5) {
205
+ return function fetchJiraWorkItemInfo(_x5, _x6) {
182
206
  return _ref3.apply(this, arguments);
183
207
  };
184
208
  }();
@@ -8,6 +8,12 @@ import _wrapNativeSuper from "@babel/runtime/helpers/wrapNativeSuper";
8
8
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
9
9
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
10
10
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
11
+ /**
12
+ * Where a synced block location sits relative to the document hosting the editor.
13
+ * `same-document` is the host document itself, the same page or the same work item field.
14
+ * `same-parent-document` is another field of the host work item.
15
+ */
16
+
11
17
  export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
12
18
  SyncBlockError["Errored"] = "errored";
13
19
  SyncBlockError["NotFound"] = "not_found";
@@ -13,10 +13,11 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
13
13
 
14
14
  import { useMemo } from 'react';
15
15
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
16
- import { generateBlockAri as _generateBlockAri, generateBlockAriFromReference } from '../../clients/block-service/ari';
16
+ import { generateBlockAri as _generateBlockAri, generateBlockAriFromReference, getProductFromSourceAri } from '../../clients/block-service/ari';
17
17
  import { batchRetrieveSyncedBlocks, BlockError, BlockNotFoundError, BlockTimeoutError, createSyncedBlock, deleteSyncedBlock, getReferenceSyncedBlocks, getReferenceSyncedBlocksByBlockAri, getSyncedBlockContent, updateReferenceSyncedBlockOnDocument, updateSyncedBlock, updateSyncedBlocks } from '../../clients/block-service/blockService';
18
18
  import { SyncBlockError } from '../../common/types';
19
19
  import { getPiiSafeOriginalError, stringifyError } from '../../utils/errorHandling';
20
+ import { getFieldAwareLocationScope } from '../../utils/fieldAwareLocations';
20
21
  import { createResourceIdForReference } from '../../utils/resourceId';
21
22
  import { convertContentUpdatedAt } from '../../utils/utils';
22
23
  var BLOCK_ARI_TO_RESOURCE_ID_REGEX = /^ari:cloud:blocks:.*:synced-block\/(.+)$/;
@@ -695,7 +696,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
695
696
  value: function () {
696
697
  var _fetchReferences = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee5(referenceResourceId) {
697
698
  var _this = this;
698
- var blockAri, response, references, _t8;
699
+ var blockAri, response, locationScopeOf, references, _t8;
699
700
  return _regeneratorRuntime.wrap(function (_context6) {
700
701
  while (1) switch (_context6.prev = _context6.next) {
701
702
  case 0:
@@ -710,21 +711,28 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
710
711
  });
711
712
  case 1:
712
713
  response = _context6.sent;
714
+ locationScopeOf = function locationScopeOf(documentAri) {
715
+ return getFieldAwareLocationScope({
716
+ documentAri: documentAri,
717
+ hostAri: _this.parentAri,
718
+ productType: getProductFromSourceAri(documentAri)
719
+ });
720
+ };
713
721
  references = [];
714
722
  response.references.forEach(function (reference) {
715
723
  references.push(_objectSpread(_objectSpread({}, reference), {}, {
716
724
  hasAccess: true,
717
725
  onSameDocument: _this.parentAri === reference.documentAri
718
- }));
726
+ }, locationScopeOf(reference.documentAri)));
719
727
  });
720
728
  response.errors.forEach(function (reference) {
721
729
  if (reference.code === 'FORBIDDEN') {
722
- references.push({
730
+ references.push(_objectSpread({
723
731
  blockAri: reference.blockAri,
724
732
  documentAri: reference.documentAri,
725
733
  hasAccess: false,
726
734
  onSameDocument: false
727
- });
735
+ }, locationScopeOf(reference.documentAri)));
728
736
  }
729
737
  });
730
738
  return _context6.abrupt("return", {
@@ -17,6 +17,7 @@ import { getPageIdAndTypeFromConfluencePageAri } from '../clients/confluence/ari
17
17
  import { fetchConfluencePageInfo } from '../clients/confluence/sourceInfo';
18
18
  import { fetchJiraWorkItemInfo } from '../clients/jira/sourceInfo';
19
19
  import { SyncBlockError } from '../common/types';
20
+ import { getFieldAwareLocationScope } from '../utils/fieldAwareLocations';
20
21
  import { SyncBlockDataProviderInterface } from './types';
21
22
  export var SyncedBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
22
23
  /**
@@ -313,6 +314,7 @@ export var SyncedBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
313
314
  _this$writeProvider2,
314
315
  sourceInfo,
315
316
  _this$writeProvider3,
317
+ _this$writeProvider4,
316
318
  _sourceInfo,
317
319
  _args4 = arguments,
318
320
  _t2;
@@ -357,8 +359,13 @@ export var SyncedBlockProvider = /*#__PURE__*/function (_SyncBlockDataProvide) {
357
359
  }
358
360
  return _context4.abrupt("return", Promise.resolve(undefined));
359
361
  case 7:
360
- return _context4.abrupt("return", _objectSpread(_objectSpread({}, _sourceInfo), {}, {
361
- onSameDocument: ((_this$writeProvider3 = this.writeProvider) === null || _this$writeProvider3 === void 0 ? void 0 : _this$writeProvider3.parentAri) === ari,
362
+ return _context4.abrupt("return", _objectSpread(_objectSpread(_objectSpread({}, _sourceInfo), {}, {
363
+ onSameDocument: ((_this$writeProvider3 = this.writeProvider) === null || _this$writeProvider3 === void 0 ? void 0 : _this$writeProvider3.parentAri) === ari
364
+ }, getFieldAwareLocationScope({
365
+ documentAri: ari,
366
+ hostAri: (_this$writeProvider4 = this.writeProvider) === null || _this$writeProvider4 === void 0 ? void 0 : _this$writeProvider4.parentAri,
367
+ productType: product
368
+ })), {}, {
362
369
  productType: product
363
370
  }));
364
371
  case 8:
@@ -394,19 +394,26 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
394
394
  sourceURL = _ref.sourceURL,
395
395
  sourceTitle = _ref.sourceTitle,
396
396
  onSameDocument = _ref.onSameDocument,
397
+ fieldName = _ref.fieldName,
398
+ locationScope = _ref.locationScope,
397
399
  sourceSubType = _ref.sourceSubType,
398
400
  issueType = _ref.issueType;
399
401
  // skip if source URL and title are already present
400
402
  if (sourceURL && sourceTitle) {
401
- return Promise.resolve({
403
+ return Promise.resolve(_objectSpread(_objectSpread(_objectSpread({
402
404
  title: sourceTitle,
403
405
  url: sourceURL,
404
406
  subType: sourceSubType,
405
407
  sourceAri: sourceAri || '',
406
- onSameDocument: onSameDocument,
408
+ onSameDocument: onSameDocument
409
+ }, fieldName !== undefined && {
410
+ fieldName: fieldName
411
+ }), locationScope !== undefined && {
412
+ locationScope: locationScope
413
+ }), {}, {
407
414
  productType: product,
408
415
  issueType: issueType
409
- });
416
+ }));
410
417
  }
411
418
 
412
419
  // Derive once per call so we don't re-parse on every analytics event below.
@@ -725,10 +732,15 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
725
732
  }
726
733
  if (existingSyncBlock && existingSyncBlock.data) {
727
734
  existingSyncBlock.data.sourceURL = sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url;
728
- existingSyncBlock.data = _objectSpread(_objectSpread({}, existingSyncBlock.data), {}, {
735
+ existingSyncBlock.data = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, existingSyncBlock.data), {}, {
729
736
  sourceURL: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url,
730
737
  sourceTitle: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.title,
731
- onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument,
738
+ onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument
739
+ }, (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.fieldName) !== undefined && {
740
+ fieldName: sourceInfo.fieldName
741
+ }), (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.locationScope) !== undefined && {
742
+ locationScope: sourceInfo.locationScope
743
+ }), {}, {
732
744
  sourceSubType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.subType,
733
745
  issueType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.issueType
734
746
  });
@@ -99,8 +99,11 @@ export var SyncBlockStoreManager = /*#__PURE__*/function () {
99
99
  return _context.abrupt("return", undefined);
100
100
  case 2:
101
101
  (_this$fetchSourceInfo3 = _this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo3 === void 0 || _this$fetchSourceInfo3.success();
102
- return _context.abrupt("return", _objectSpread(_objectSpread({}, sourceInfo), {}, {
103
- onSameDocument: reference.onSameDocument,
102
+ return _context.abrupt("return", _objectSpread(_objectSpread(_objectSpread({}, sourceInfo), {}, {
103
+ onSameDocument: reference.onSameDocument
104
+ }, reference.locationScope !== undefined && {
105
+ locationScope: reference.locationScope
106
+ }), {}, {
104
107
  hasAccess: reference.hasAccess,
105
108
  productType: sourceInfo.productType
106
109
  }));
@@ -0,0 +1,24 @@
1
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
2
+ import { getLocationScope } from '../clients/block-service/ari';
3
+ /**
4
+ * The product check runs first so a Confluence-only document never reads the experiment.
5
+ */
6
+ export var isFieldAwareJiraLocation = function isFieldAwareJiraLocation(productType) {
7
+ return productType === 'jira-work-item' && isExperimentEnabled('editor_synced_blocks_jira_custom_rich_text');
8
+ };
9
+
10
+ /**
11
+ * Spreadable `{ locationScope }` for a location that should carry one, `undefined` otherwise.
12
+ * Without a host ARI there is nothing to compare against, so the experiment is not read.
13
+ */
14
+ export var getFieldAwareLocationScope = function getFieldAwareLocationScope(_ref) {
15
+ var documentAri = _ref.documentAri,
16
+ hostAri = _ref.hostAri,
17
+ productType = _ref.productType;
18
+ return hostAri !== undefined && isFieldAwareJiraLocation(productType) ? {
19
+ locationScope: getLocationScope({
20
+ documentAri: documentAri,
21
+ hostAri: hostAri
22
+ })
23
+ } : undefined;
24
+ };
@@ -15,7 +15,7 @@ import { SyncBlockError } from '../common/types';
15
15
  * @returns A merged SyncBlockInstance object.
16
16
  */
17
17
  export var resolveSyncBlockInstance = function resolveSyncBlockInstance(oldResult, newResult) {
18
- var _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
18
+ var _newResult$data$field, _newResult$data$locat, _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
19
19
  // if the old result has no data, we simply return the new result
20
20
  if (!oldResult.data) {
21
21
  return newResult;
@@ -30,13 +30,19 @@ export var resolveSyncBlockInstance = function resolveSyncBlockInstance(oldResul
30
30
  }
31
31
 
32
32
  // otherwise, we merge the two results, preserving the sourceURL and sourceTitle from the old result if it exists
33
+ var fieldName = (_newResult$data$field = newResult.data.fieldName) !== null && _newResult$data$field !== void 0 ? _newResult$data$field : oldResult.data.fieldName;
34
+ var locationScope = (_newResult$data$locat = newResult.data.locationScope) !== null && _newResult$data$locat !== void 0 ? _newResult$data$locat : oldResult.data.locationScope;
33
35
  return _objectSpread(_objectSpread({}, newResult), {}, {
34
- data: _objectSpread(_objectSpread({}, newResult.data), {}, {
36
+ data: _objectSpread(_objectSpread(_objectSpread({}, newResult.data), {}, {
35
37
  sourceURL: ((_newResult$data = newResult.data) === null || _newResult$data === void 0 ? void 0 : _newResult$data.sourceURL) || ((_oldResult$data = oldResult.data) === null || _oldResult$data === void 0 ? void 0 : _oldResult$data.sourceURL) || undefined,
36
38
  sourceTitle: ((_newResult$data2 = newResult.data) === null || _newResult$data2 === void 0 ? void 0 : _newResult$data2.sourceTitle) || ((_oldResult$data2 = oldResult.data) === null || _oldResult$data2 === void 0 ? void 0 : _oldResult$data2.sourceTitle) || undefined,
37
39
  sourceSubType: mergeSubType(oldResult, newResult),
38
40
  onSameDocument: ((_newResult$data3 = newResult.data) === null || _newResult$data3 === void 0 ? void 0 : _newResult$data3.onSameDocument) || ((_oldResult$data3 = oldResult.data) === null || _oldResult$data3 === void 0 ? void 0 : _oldResult$data3.onSameDocument) || undefined,
39
41
  issueType: ((_newResult$data4 = newResult.data) === null || _newResult$data4 === void 0 ? void 0 : _newResult$data4.issueType) || ((_oldResult$data4 = oldResult.data) === null || _oldResult$data4 === void 0 ? void 0 : _oldResult$data4.issueType) || undefined
42
+ }, fieldName !== undefined && {
43
+ fieldName: fieldName
44
+ }), locationScope !== undefined && {
45
+ locationScope: locationScope
40
46
  })
41
47
  });
42
48
  };
@@ -1,4 +1,4 @@
1
- import type { SyncBlockProduct } from '../../common/types';
1
+ import type { SyncBlockLocationScope, SyncBlockProduct } from '../../common/types';
2
2
  /**
3
3
  * Generates the block ARI from the source page ARI and the source block's resource ID.
4
4
  * @param cloudId - the cloudId of the block. E.G the cloudId of the confluence page, or the cloudId of the Jira instance
@@ -31,4 +31,16 @@ export declare const generateBlockAriFromReference: ({ cloudId, resourceId, }: {
31
31
  * @returns the localId of the block node. A randomly generated UUID
32
32
  */
33
33
  export declare const getLocalIdFromBlockResourceId: (ari: string) => string;
34
+ /**
35
+ * Where `documentAri` sits relative to the document hosting the editor.
36
+ *
37
+ * Jira admits two ARI forms for one work item field: `issuefieldvalue/{issueId}/{fieldId}`
38
+ * names any field, and a plain issue ARI names the description field. Jira's own
39
+ * `isParentAriValid` accepts either as a host, so the two are compared on the field they
40
+ * denote rather than as strings.
41
+ */
42
+ export declare const getLocationScope: ({ documentAri, hostAri, }: {
43
+ documentAri: string;
44
+ hostAri: string;
45
+ }) => SyncBlockLocationScope;
34
46
  export declare const getProductFromSourceAri: (ari?: string) => SyncBlockProduct | undefined;
@@ -26,3 +26,18 @@ export declare const getJiraWorkItemIdFromAri: ({ ari }: {
26
26
  export declare const getJiraIssueAriFromSourceAri: ({ ari }: {
27
27
  ari: string;
28
28
  }) => string;
29
+ /**
30
+ * One synced block location on a Jira work item, parsed once from its document ARI.
31
+ * Two locations name the same field when both members match, whichever of the two ARI
32
+ * forms each was written in.
33
+ */
34
+ export type JiraFieldLocation = Readonly<{
35
+ /** Bare field id, the shape AGG `fieldsById(ids:)` takes and returns. */
36
+ fieldId: string;
37
+ /** Parent issue ARI. `issueById` and the object resolver take this one. */
38
+ issueAri: string;
39
+ }>;
40
+ /** A plain issue ARI is the work item's description field. */
41
+ export declare const parseJiraFieldLocation: ({ ari }: {
42
+ ari: string;
43
+ }) => JiraFieldLocation | undefined;