@atlaskit/editor-synced-block-provider 15.0.3 → 15.1.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 (38) hide show
  1. package/CHANGELOG.md +22 -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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 15.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 15.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`8062c64320986`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/8062c64320986) -
14
+ EDITOR-9184: Resolve the Jira work item field holding a synced block and where each location sits
15
+ relative to the host document, behind the `editor_synced_blocks_jira_custom_rich_text` experiment.
16
+ `SyncBlockSourceInfo` gains optional `fieldName` and `locationScope`, `ReferenceSyncBlock` and
17
+ `SyncBlockData` gain optional `locationScope`, and the new `SyncBlockLocationScope` type is
18
+ exported from `common/types`; with the experiment off the AGG request and every returned object
19
+ are unchanged.
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+
3
25
  ## 15.0.3
4
26
 
5
27
  ### Patch Changes
@@ -3,7 +3,8 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getProductFromSourceAri = exports.getLocalIdFromBlockResourceId = exports.generateBlockAriFromReference = exports.generateBlockAri = void 0;
6
+ exports.getProductFromSourceAri = exports.getLocationScope = exports.getLocalIdFromBlockResourceId = exports.generateBlockAriFromReference = exports.generateBlockAri = void 0;
7
+ var _ari = require("../jira/ari");
7
8
  /* eslint-disable require-unicode-regexp */
8
9
 
9
10
  var GET_LOCAL_ID_FROM_BLOCK_RESOURCE_ID_REGEX = /ari:cloud:blocks:[^:]+:synced-block\/([a-zA-Z0-9-]+)/;
@@ -52,6 +53,32 @@ var getLocalIdFromBlockResourceId = exports.getLocalIdFromBlockResourceId = func
52
53
  }
53
54
  throw new Error("Invalid block ARI: ".concat(ari));
54
55
  };
56
+
57
+ /**
58
+ * Where `documentAri` sits relative to the document hosting the editor.
59
+ *
60
+ * Jira admits two ARI forms for one work item field: `issuefieldvalue/{issueId}/{fieldId}`
61
+ * names any field, and a plain issue ARI names the description field. Jira's own
62
+ * `isParentAriValid` accepts either as a host, so the two are compared on the field they
63
+ * denote rather than as strings.
64
+ */
65
+ var getLocationScope = exports.getLocationScope = function getLocationScope(_ref3) {
66
+ var documentAri = _ref3.documentAri,
67
+ hostAri = _ref3.hostAri;
68
+ if (documentAri === hostAri) {
69
+ return 'same-document';
70
+ }
71
+ var location = (0, _ari.parseJiraFieldLocation)({
72
+ ari: documentAri
73
+ });
74
+ var hostLocation = (0, _ari.parseJiraFieldLocation)({
75
+ ari: hostAri
76
+ });
77
+ if (!location || !hostLocation || location.issueAri !== hostLocation.issueAri) {
78
+ return 'elsewhere';
79
+ }
80
+ return location.fieldId === hostLocation.fieldId ? 'same-document' : 'same-parent-document';
81
+ };
55
82
  var getProductFromSourceAri = exports.getProductFromSourceAri = function getProductFromSourceAri(ari) {
56
83
  var jiraMatch = ari === null || ari === void 0 ? void 0 : ari.search(JIRA_SOURCE_ARI_REGEX);
57
84
  if (jiraMatch !== -1) {
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getJiraWorkItemIdFromAri = exports.getJiraWorkItemAri = exports.getJiraIssueAriFromSourceAri = void 0;
6
+ exports.parseJiraFieldLocation = exports.getJiraWorkItemIdFromAri = exports.getJiraWorkItemAri = exports.getJiraIssueAriFromSourceAri = void 0;
7
7
  /* eslint-disable require-unicode-regexp */
8
8
 
9
9
  var JIRA_WORK_ITEM_ARI_REGEX = /ari:cloud:jira:([^:]+):issue\/(\d+)/;
@@ -68,4 +68,39 @@ var getJiraIssueAriFromSourceAri = exports.getJiraIssueAriFromSourceAri = functi
68
68
  });
69
69
  }
70
70
  return ari;
71
+ };
72
+ var JIRA_DESCRIPTION_FIELD_ID = 'description';
73
+
74
+ /**
75
+ * One synced block location on a Jira work item, parsed once from its document ARI.
76
+ * Two locations name the same field when both members match, whichever of the two ARI
77
+ * forms each was written in.
78
+ */
79
+
80
+ var toJiraFieldLocation = function toJiraFieldLocation(_ref4) {
81
+ var cloudId = _ref4.cloudId,
82
+ fieldId = _ref4.fieldId,
83
+ issueId = _ref4.issueId;
84
+ return {
85
+ fieldId: fieldId,
86
+ issueAri: getJiraWorkItemAri({
87
+ cloudId: cloudId,
88
+ workItemId: issueId
89
+ })
90
+ };
91
+ };
92
+
93
+ /** A plain issue ARI is the work item's description field. */
94
+ var parseJiraFieldLocation = exports.parseJiraFieldLocation = function parseJiraFieldLocation(_ref5) {
95
+ var ari = _ref5.ari;
96
+ var workItemMatch = ari.match(JIRA_WORK_ITEM_ARI_REGEX);
97
+ if (workItemMatch !== null && workItemMatch !== void 0 && workItemMatch[1] && workItemMatch[2]) {
98
+ return toJiraFieldLocation({
99
+ cloudId: workItemMatch[1],
100
+ fieldId: JIRA_DESCRIPTION_FIELD_ID,
101
+ issueId: workItemMatch[2]
102
+ });
103
+ }
104
+ var fieldValueParts = getFieldValueAriParts(ari);
105
+ return fieldValueParts && toJiraFieldLocation(fieldValueParts);
71
106
  };
@@ -8,6 +8,7 @@ exports.fetchJiraWorkItemInfo = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
10
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
11
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
11
12
  var _retry = require("../../utils/retry");
12
13
  var _ari = require("./ari");
13
14
  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; }
@@ -29,16 +30,33 @@ var GET_SOURCE_INFO_OPERATION_NAME = 'EDITOR_SYNCED_BLOCK_GET_SOURCE_INFO';
29
30
  // `issueType` is requested alongside `summary` and `webUrl` so the
30
31
  // SyncedLocationDropdown can render the correct ADS issue-type icon.
31
32
  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 }}");
33
+ // `fieldsById(ids:)` takes bare field ids, the same shape it returns on `node.fieldId`.
34
+ // Its schema doc gives `issuefieldvalue` ARIs as the example, but AGG resolves those to a
35
+ // null node. Verified against hello.atlassian.net by sending an ARI and a bare id together.
36
+ 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 }}");
37
+
38
+ // AGG does not promise `edges` follows the order of the requested ids.
39
+ var getFieldName = function getFieldName(contentData, fieldId) {
40
+ var _contentData$fieldsBy;
41
+ 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) {
42
+ var _edge$node;
43
+ return (edge === null || edge === void 0 || (_edge$node = edge.node) === null || _edge$node === void 0 ? void 0 : _edge$node.fieldId) === fieldId;
44
+ })) === null || _contentData$fieldsBy === void 0 || (_contentData$fieldsBy = _contentData$fieldsBy.node) === null || _contentData$fieldsBy === void 0 ? void 0 : _contentData$fieldsBy.name;
45
+ return typeof name === 'string' && name.length > 0 ? name : undefined;
46
+ };
32
47
  var getJiraWorkItemSourceInfo = /*#__PURE__*/function () {
33
- var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(issueAri) {
48
+ var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(issueAri, fieldId) {
34
49
  var bodyData, response;
35
50
  return _regenerator.default.wrap(function (_context) {
36
51
  while (1) switch (_context.prev = _context.next) {
37
52
  case 0:
38
53
  bodyData = {
39
- query: GET_SOURCE_INFO_QUERY,
54
+ query: fieldId ? GET_SOURCE_INFO_WITH_FIELD_QUERY : GET_SOURCE_INFO_QUERY,
40
55
  operationName: GET_SOURCE_INFO_OPERATION_NAME,
41
- variables: {
56
+ variables: fieldId ? {
57
+ id: issueAri,
58
+ fieldIds: [fieldId]
59
+ } : {
42
60
  id: issueAri
43
61
  }
44
62
  };
@@ -66,7 +84,7 @@ var getJiraWorkItemSourceInfo = /*#__PURE__*/function () {
66
84
  }
67
85
  }, _callee);
68
86
  }));
69
- return function getJiraWorkItemSourceInfo(_x) {
87
+ return function getJiraWorkItemSourceInfo(_x, _x2) {
70
88
  return _ref.apply(this, arguments);
71
89
  };
72
90
  }();
@@ -112,7 +130,7 @@ var resolveNoAccessWorkItemInfo = /*#__PURE__*/function () {
112
130
  }
113
131
  }, _callee2);
114
132
  }));
115
- return function resolveNoAccessWorkItemInfo(_x2, _x3) {
133
+ return function resolveNoAccessWorkItemInfo(_x3, _x4) {
116
134
  return _ref2.apply(this, arguments);
117
135
  };
118
136
  }();
@@ -139,7 +157,7 @@ var resolveNoAccessWorkItemInfo = /*#__PURE__*/function () {
139
157
  */
140
158
  var fetchJiraWorkItemInfo = exports.fetchJiraWorkItemInfo = /*#__PURE__*/function () {
141
159
  var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(workItemAri, hasAccess) {
142
- var issueAri, _response$data, _contentData$issueTyp, _contentData$issueTyp2, response, contentData, webUrl, summary, issueTypeName, issueType;
160
+ var issueAri, _response$data, _contentData$issueTyp, _contentData$issueTyp2, fieldLocation, response, contentData, webUrl, summary, issueTypeName, issueType, fieldName;
143
161
  return _regenerator.default.wrap(function (_context3) {
144
162
  while (1) switch (_context3.prev = _context3.next) {
145
163
  case 0:
@@ -152,8 +170,11 @@ var fetchJiraWorkItemInfo = exports.fetchJiraWorkItemInfo = /*#__PURE__*/functio
152
170
  _context3.next = 2;
153
171
  break;
154
172
  }
173
+ fieldLocation = (0, _isExperimentEnabled.isExperimentEnabled)('editor_synced_blocks_jira_custom_rich_text') ? (0, _ari.parseJiraFieldLocation)({
174
+ ari: workItemAri
175
+ }) : undefined;
155
176
  _context3.next = 1;
156
- return getJiraWorkItemSourceInfo(issueAri);
177
+ return getJiraWorkItemSourceInfo(issueAri, fieldLocation === null || fieldLocation === void 0 ? void 0 : fieldLocation.fieldId);
157
178
  case 1:
158
179
  response = _context3.sent;
159
180
  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;
@@ -166,12 +187,15 @@ var fetchJiraWorkItemInfo = exports.fetchJiraWorkItemInfo = /*#__PURE__*/functio
166
187
  name: issueTypeName,
167
188
  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
168
189
  } : undefined;
169
- return _context3.abrupt("return", Promise.resolve({
190
+ fieldName = fieldLocation && getFieldName(contentData, fieldLocation.fieldId);
191
+ return _context3.abrupt("return", Promise.resolve(_objectSpread({
170
192
  url: webUrl,
171
193
  sourceAri: workItemAri,
172
194
  title: summary,
173
195
  issueType: issueType
174
- }));
196
+ }, fieldName !== undefined && {
197
+ fieldName: fieldName
198
+ })));
175
199
  case 2:
176
200
  _context3.next = 3;
177
201
  return resolveNoAccessWorkItemInfo(workItemAri, issueAri);
@@ -183,7 +207,7 @@ var fetchJiraWorkItemInfo = exports.fetchJiraWorkItemInfo = /*#__PURE__*/functio
183
207
  }
184
208
  }, _callee3);
185
209
  }));
186
- return function fetchJiraWorkItemInfo(_x4, _x5) {
210
+ return function fetchJiraWorkItemInfo(_x5, _x6) {
187
211
  return _ref3.apply(this, arguments);
188
212
  };
189
213
  }();
@@ -15,6 +15,11 @@ var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/w
15
15
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
16
16
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
17
17
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
18
+ /**
19
+ * Where a synced block location sits relative to the document hosting the editor.
20
+ * `same-document` is the host document itself, the same page or the same work item field.
21
+ * `same-parent-document` is another field of the host work item.
22
+ */
18
23
  var SyncBlockError = exports.SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
19
24
  SyncBlockError["Errored"] = "errored";
20
25
  SyncBlockError["NotFound"] = "not_found";
@@ -18,6 +18,7 @@ var _ari = require("../../clients/block-service/ari");
18
18
  var _blockService = require("../../clients/block-service/blockService");
19
19
  var _types = require("../../common/types");
20
20
  var _errorHandling = require("../../utils/errorHandling");
21
+ var _fieldAwareLocations = require("../../utils/fieldAwareLocations");
21
22
  var _resourceId2 = require("../../utils/resourceId");
22
23
  var _utils = require("../../utils/utils");
23
24
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t19 in e) "default" !== _t19 && {}.hasOwnProperty.call(e, _t19) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t19)) && (i.get || i.set) ? o(f, _t19, i) : f[_t19] = e[_t19]); return f; })(e, t); }
@@ -701,7 +702,7 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
701
702
  value: function () {
702
703
  var _fetchReferences = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5(referenceResourceId) {
703
704
  var _this = this;
704
- var blockAri, response, references, _t8;
705
+ var blockAri, response, locationScopeOf, references, _t8;
705
706
  return _regenerator.default.wrap(function (_context6) {
706
707
  while (1) switch (_context6.prev = _context6.next) {
707
708
  case 0:
@@ -716,21 +717,28 @@ var BlockServiceADFFetchProvider = /*#__PURE__*/function () {
716
717
  });
717
718
  case 1:
718
719
  response = _context6.sent;
720
+ locationScopeOf = function locationScopeOf(documentAri) {
721
+ return (0, _fieldAwareLocations.getFieldAwareLocationScope)({
722
+ documentAri: documentAri,
723
+ hostAri: _this.parentAri,
724
+ productType: (0, _ari.getProductFromSourceAri)(documentAri)
725
+ });
726
+ };
719
727
  references = [];
720
728
  response.references.forEach(function (reference) {
721
729
  references.push(_objectSpread(_objectSpread({}, reference), {}, {
722
730
  hasAccess: true,
723
731
  onSameDocument: _this.parentAri === reference.documentAri
724
- }));
732
+ }, locationScopeOf(reference.documentAri)));
725
733
  });
726
734
  response.errors.forEach(function (reference) {
727
735
  if (reference.code === 'FORBIDDEN') {
728
- references.push({
736
+ references.push(_objectSpread({
729
737
  blockAri: reference.blockAri,
730
738
  documentAri: reference.documentAri,
731
739
  hasAccess: false,
732
740
  onSameDocument: false
733
- });
741
+ }, locationScopeOf(reference.documentAri)));
734
742
  }
735
743
  });
736
744
  return _context6.abrupt("return", {
@@ -20,6 +20,7 @@ var _ari2 = require("../clients/confluence/ari");
20
20
  var _sourceInfo2 = require("../clients/confluence/sourceInfo");
21
21
  var _sourceInfo3 = require("../clients/jira/sourceInfo");
22
22
  var _types = require("../common/types");
23
+ var _fieldAwareLocations = require("../utils/fieldAwareLocations");
23
24
  var _types2 = require("./types");
24
25
  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; }
25
26
  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) { (0, _defineProperty2.default)(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; }
@@ -320,6 +321,7 @@ var SyncedBlockProvider = exports.SyncedBlockProvider = /*#__PURE__*/function (_
320
321
  _this$writeProvider2,
321
322
  sourceInfo,
322
323
  _this$writeProvider3,
324
+ _this$writeProvider4,
323
325
  _sourceInfo,
324
326
  _args4 = arguments,
325
327
  _t2;
@@ -364,8 +366,13 @@ var SyncedBlockProvider = exports.SyncedBlockProvider = /*#__PURE__*/function (_
364
366
  }
365
367
  return _context4.abrupt("return", Promise.resolve(undefined));
366
368
  case 7:
367
- return _context4.abrupt("return", _objectSpread(_objectSpread({}, _sourceInfo), {}, {
368
- onSameDocument: ((_this$writeProvider3 = this.writeProvider) === null || _this$writeProvider3 === void 0 ? void 0 : _this$writeProvider3.parentAri) === ari,
369
+ return _context4.abrupt("return", _objectSpread(_objectSpread(_objectSpread({}, _sourceInfo), {}, {
370
+ onSameDocument: ((_this$writeProvider3 = this.writeProvider) === null || _this$writeProvider3 === void 0 ? void 0 : _this$writeProvider3.parentAri) === ari
371
+ }, (0, _fieldAwareLocations.getFieldAwareLocationScope)({
372
+ documentAri: ari,
373
+ hostAri: (_this$writeProvider4 = this.writeProvider) === null || _this$writeProvider4 === void 0 ? void 0 : _this$writeProvider4.parentAri,
374
+ productType: product
375
+ })), {}, {
369
376
  productType: product
370
377
  }));
371
378
  case 8:
@@ -401,19 +401,26 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
401
401
  sourceURL = _ref.sourceURL,
402
402
  sourceTitle = _ref.sourceTitle,
403
403
  onSameDocument = _ref.onSameDocument,
404
+ fieldName = _ref.fieldName,
405
+ locationScope = _ref.locationScope,
404
406
  sourceSubType = _ref.sourceSubType,
405
407
  issueType = _ref.issueType;
406
408
  // skip if source URL and title are already present
407
409
  if (sourceURL && sourceTitle) {
408
- return Promise.resolve({
410
+ return Promise.resolve(_objectSpread(_objectSpread(_objectSpread({
409
411
  title: sourceTitle,
410
412
  url: sourceURL,
411
413
  subType: sourceSubType,
412
414
  sourceAri: sourceAri || '',
413
- onSameDocument: onSameDocument,
415
+ onSameDocument: onSameDocument
416
+ }, fieldName !== undefined && {
417
+ fieldName: fieldName
418
+ }), locationScope !== undefined && {
419
+ locationScope: locationScope
420
+ }), {}, {
414
421
  productType: product,
415
422
  issueType: issueType
416
- });
423
+ }));
417
424
  }
418
425
 
419
426
  // Derive once per call so we don't re-parse on every analytics event below.
@@ -732,10 +739,15 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
732
739
  }
733
740
  if (existingSyncBlock && existingSyncBlock.data) {
734
741
  existingSyncBlock.data.sourceURL = sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url;
735
- existingSyncBlock.data = _objectSpread(_objectSpread({}, existingSyncBlock.data), {}, {
742
+ existingSyncBlock.data = _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, existingSyncBlock.data), {}, {
736
743
  sourceURL: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.url,
737
744
  sourceTitle: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.title,
738
- onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument,
745
+ onSameDocument: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.onSameDocument
746
+ }, (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.fieldName) !== undefined && {
747
+ fieldName: sourceInfo.fieldName
748
+ }), (sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.locationScope) !== undefined && {
749
+ locationScope: sourceInfo.locationScope
750
+ }), {}, {
739
751
  sourceSubType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.subType,
740
752
  issueType: sourceInfo === null || sourceInfo === void 0 ? void 0 : sourceInfo.issueType
741
753
  });
@@ -104,8 +104,11 @@ var SyncBlockStoreManager = exports.SyncBlockStoreManager = /*#__PURE__*/functio
104
104
  return _context.abrupt("return", undefined);
105
105
  case 2:
106
106
  (_this$fetchSourceInfo3 = _this.fetchSourceInfoExperience) === null || _this$fetchSourceInfo3 === void 0 || _this$fetchSourceInfo3.success();
107
- return _context.abrupt("return", _objectSpread(_objectSpread({}, sourceInfo), {}, {
108
- onSameDocument: reference.onSameDocument,
107
+ return _context.abrupt("return", _objectSpread(_objectSpread(_objectSpread({}, sourceInfo), {}, {
108
+ onSameDocument: reference.onSameDocument
109
+ }, reference.locationScope !== undefined && {
110
+ locationScope: reference.locationScope
111
+ }), {}, {
109
112
  hasAccess: reference.hasAccess,
110
113
  productType: sourceInfo.productType
111
114
  }));
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isFieldAwareJiraLocation = exports.getFieldAwareLocationScope = void 0;
7
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
8
+ var _ari = require("../clients/block-service/ari");
9
+ /**
10
+ * The product check runs first so a Confluence-only document never reads the experiment.
11
+ */
12
+ var isFieldAwareJiraLocation = exports.isFieldAwareJiraLocation = function isFieldAwareJiraLocation(productType) {
13
+ return productType === 'jira-work-item' && (0, _isExperimentEnabled.isExperimentEnabled)('editor_synced_blocks_jira_custom_rich_text');
14
+ };
15
+
16
+ /**
17
+ * Spreadable `{ locationScope }` for a location that should carry one, `undefined` otherwise.
18
+ * Without a host ARI there is nothing to compare against, so the experiment is not read.
19
+ */
20
+ var getFieldAwareLocationScope = exports.getFieldAwareLocationScope = function getFieldAwareLocationScope(_ref) {
21
+ var documentAri = _ref.documentAri,
22
+ hostAri = _ref.hostAri,
23
+ productType = _ref.productType;
24
+ return hostAri !== undefined && isFieldAwareJiraLocation(productType) ? {
25
+ locationScope: (0, _ari.getLocationScope)({
26
+ documentAri: documentAri,
27
+ hostAri: hostAri
28
+ })
29
+ } : undefined;
30
+ };
@@ -22,7 +22,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
22
22
  * @returns A merged SyncBlockInstance object.
23
23
  */
24
24
  var resolveSyncBlockInstance = exports.resolveSyncBlockInstance = function resolveSyncBlockInstance(oldResult, newResult) {
25
- var _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
25
+ var _newResult$data$field, _newResult$data$locat, _newResult$data, _oldResult$data, _newResult$data2, _oldResult$data2, _newResult$data3, _oldResult$data3, _newResult$data4, _oldResult$data4;
26
26
  // if the old result has no data, we simply return the new result
27
27
  if (!oldResult.data) {
28
28
  return newResult;
@@ -37,13 +37,19 @@ var resolveSyncBlockInstance = exports.resolveSyncBlockInstance = function resol
37
37
  }
38
38
 
39
39
  // otherwise, we merge the two results, preserving the sourceURL and sourceTitle from the old result if it exists
40
+ var fieldName = (_newResult$data$field = newResult.data.fieldName) !== null && _newResult$data$field !== void 0 ? _newResult$data$field : oldResult.data.fieldName;
41
+ var locationScope = (_newResult$data$locat = newResult.data.locationScope) !== null && _newResult$data$locat !== void 0 ? _newResult$data$locat : oldResult.data.locationScope;
40
42
  return _objectSpread(_objectSpread({}, newResult), {}, {
41
- data: _objectSpread(_objectSpread({}, newResult.data), {}, {
43
+ data: _objectSpread(_objectSpread(_objectSpread({}, newResult.data), {}, {
42
44
  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,
43
45
  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,
44
46
  sourceSubType: mergeSubType(oldResult, newResult),
45
47
  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,
46
48
  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
49
+ }, fieldName !== undefined && {
50
+ fieldName: fieldName
51
+ }), locationScope !== undefined && {
52
+ locationScope: locationScope
47
53
  })
48
54
  });
49
55
  };
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
2
 
3
+ import { parseJiraFieldLocation } from '../jira/ari';
3
4
  const GET_LOCAL_ID_FROM_BLOCK_RESOURCE_ID_REGEX = /ari:cloud:blocks:[^:]+:synced-block\/([a-zA-Z0-9-]+)/;
4
5
  const JIRA_SOURCE_ARI_REGEX = /ari:cloud:jira:.*/;
5
6
  const CONFLUENCE_SOURCE_ARI_REGEX = /ari:cloud:confluence:.*/;
@@ -48,6 +49,33 @@ export const getLocalIdFromBlockResourceId = ari => {
48
49
  }
49
50
  throw new Error(`Invalid block ARI: ${ari}`);
50
51
  };
52
+
53
+ /**
54
+ * Where `documentAri` sits relative to the document hosting the editor.
55
+ *
56
+ * Jira admits two ARI forms for one work item field: `issuefieldvalue/{issueId}/{fieldId}`
57
+ * names any field, and a plain issue ARI names the description field. Jira's own
58
+ * `isParentAriValid` accepts either as a host, so the two are compared on the field they
59
+ * denote rather than as strings.
60
+ */
61
+ export const getLocationScope = ({
62
+ documentAri,
63
+ hostAri
64
+ }) => {
65
+ if (documentAri === hostAri) {
66
+ return 'same-document';
67
+ }
68
+ const location = parseJiraFieldLocation({
69
+ ari: documentAri
70
+ });
71
+ const hostLocation = parseJiraFieldLocation({
72
+ ari: hostAri
73
+ });
74
+ if (!location || !hostLocation || location.issueAri !== hostLocation.issueAri) {
75
+ return 'elsewhere';
76
+ }
77
+ return location.fieldId === hostLocation.fieldId ? 'same-document' : 'same-parent-document';
78
+ };
51
79
  export const getProductFromSourceAri = ari => {
52
80
  const jiraMatch = ari === null || ari === void 0 ? void 0 : ari.search(JIRA_SOURCE_ARI_REGEX);
53
81
  if (jiraMatch !== -1) {
@@ -65,4 +65,39 @@ export const getJiraIssueAriFromSourceAri = ({
65
65
  });
66
66
  }
67
67
  return ari;
68
+ };
69
+ const JIRA_DESCRIPTION_FIELD_ID = 'description';
70
+
71
+ /**
72
+ * One synced block location on a Jira work item, parsed once from its document ARI.
73
+ * Two locations name the same field when both members match, whichever of the two ARI
74
+ * forms each was written in.
75
+ */
76
+
77
+ const toJiraFieldLocation = ({
78
+ cloudId,
79
+ fieldId,
80
+ issueId
81
+ }) => ({
82
+ fieldId,
83
+ issueAri: getJiraWorkItemAri({
84
+ cloudId,
85
+ workItemId: issueId
86
+ })
87
+ });
88
+
89
+ /** A plain issue ARI is the work item's description field. */
90
+ export const parseJiraFieldLocation = ({
91
+ ari
92
+ }) => {
93
+ const workItemMatch = ari.match(JIRA_WORK_ITEM_ARI_REGEX);
94
+ if (workItemMatch !== null && workItemMatch !== void 0 && workItemMatch[1] && workItemMatch[2]) {
95
+ return toJiraFieldLocation({
96
+ cloudId: workItemMatch[1],
97
+ fieldId: JIRA_DESCRIPTION_FIELD_ID,
98
+ issueId: workItemMatch[2]
99
+ });
100
+ }
101
+ const fieldValueParts = getFieldValueAriParts(ari);
102
+ return fieldValueParts && toJiraFieldLocation(fieldValueParts);
68
103
  };
@@ -1,7 +1,8 @@
1
1
  /* eslint-disable require-unicode-regexp */
2
2
 
3
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
4
  import { fetchWithRetry } from '../../utils/retry';
4
- import { getJiraIssueAriFromSourceAri } from './ari';
5
+ import { getJiraIssueAriFromSourceAri, parseJiraFieldLocation } from './ari';
5
6
  const COMMON_HEADERS = {
6
7
  'Content-Type': 'application/json',
7
8
  Accept: 'application/json'
@@ -32,11 +33,49 @@ const GET_SOURCE_INFO_QUERY = `query ${GET_SOURCE_INFO_OPERATION_NAME} ($id: ID!
32
33
  }
33
34
  }
34
35
  }}`;
35
- const getJiraWorkItemSourceInfo = async issueAri => {
36
+ // `fieldsById(ids:)` takes bare field ids, the same shape it returns on `node.fieldId`.
37
+ // Its schema doc gives `issuefieldvalue` ARIs as the example, but AGG resolves those to a
38
+ // null node. Verified against hello.atlassian.net by sending an ARI and a bare id together.
39
+ const GET_SOURCE_INFO_WITH_FIELD_QUERY = `query ${GET_SOURCE_INFO_OPERATION_NAME} ($id: ID!, $fieldIds: [ID!]!) {
40
+ jira {
41
+ issueById(id: $id) {
42
+ id
43
+ webUrl
44
+ summary
45
+ issueType {
46
+ name
47
+ avatar {
48
+ xsmall
49
+ }
50
+ }
51
+ fieldsById(ids: $fieldIds) {
52
+ edges {
53
+ node {
54
+ fieldId
55
+ name
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }}`;
61
+
62
+ // AGG does not promise `edges` follows the order of the requested ids.
63
+ const getFieldName = (contentData, fieldId) => {
64
+ var _contentData$fieldsBy, _contentData$fieldsBy2, _contentData$fieldsBy3, _contentData$fieldsBy4;
65
+ const name = contentData === null || contentData === void 0 ? void 0 : (_contentData$fieldsBy = contentData.fieldsById) === null || _contentData$fieldsBy === void 0 ? void 0 : (_contentData$fieldsBy2 = _contentData$fieldsBy.edges) === null || _contentData$fieldsBy2 === void 0 ? void 0 : (_contentData$fieldsBy3 = _contentData$fieldsBy2.find(edge => {
66
+ var _edge$node;
67
+ return (edge === null || edge === void 0 ? void 0 : (_edge$node = edge.node) === null || _edge$node === void 0 ? void 0 : _edge$node.fieldId) === fieldId;
68
+ })) === null || _contentData$fieldsBy3 === void 0 ? void 0 : (_contentData$fieldsBy4 = _contentData$fieldsBy3.node) === null || _contentData$fieldsBy4 === void 0 ? void 0 : _contentData$fieldsBy4.name;
69
+ return typeof name === 'string' && name.length > 0 ? name : undefined;
70
+ };
71
+ const getJiraWorkItemSourceInfo = async (issueAri, fieldId) => {
36
72
  const bodyData = {
37
- query: GET_SOURCE_INFO_QUERY,
73
+ query: fieldId ? GET_SOURCE_INFO_WITH_FIELD_QUERY : GET_SOURCE_INFO_QUERY,
38
74
  operationName: GET_SOURCE_INFO_OPERATION_NAME,
39
- variables: {
75
+ variables: fieldId ? {
76
+ id: issueAri,
77
+ fieldIds: [fieldId]
78
+ } : {
40
79
  id: issueAri
41
80
  }
42
81
  };
@@ -107,7 +146,10 @@ export const fetchJiraWorkItemInfo = async (workItemAri, hasAccess) => {
107
146
  });
108
147
  if (hasAccess) {
109
148
  var _response$data, _response$data$jira, _contentData$issueTyp, _contentData$issueTyp2, _contentData$issueTyp3;
110
- const response = await getJiraWorkItemSourceInfo(issueAri);
149
+ const fieldLocation = isExperimentEnabled('editor_synced_blocks_jira_custom_rich_text') ? parseJiraFieldLocation({
150
+ ari: workItemAri
151
+ }) : undefined;
152
+ const response = await getJiraWorkItemSourceInfo(issueAri, fieldLocation === null || fieldLocation === void 0 ? void 0 : fieldLocation.fieldId);
111
153
  const contentData = (_response$data = response.data) === null || _response$data === void 0 ? void 0 : (_response$data$jira = _response$data.jira) === null || _response$data$jira === void 0 ? void 0 : _response$data$jira.issueById;
112
154
  const webUrl = typeof (contentData === null || contentData === void 0 ? void 0 : contentData.webUrl) === 'string' ? contentData.webUrl : undefined;
113
155
  const summary = typeof (contentData === null || contentData === void 0 ? void 0 : contentData.summary) === 'string' ? contentData.summary : undefined;
@@ -120,11 +162,15 @@ export const fetchJiraWorkItemInfo = async (workItemAri, hasAccess) => {
120
162
  name: issueTypeName,
121
163
  iconUrl: typeof (contentData === null || contentData === void 0 ? void 0 : (_contentData$issueTyp2 = contentData.issueType) === null || _contentData$issueTyp2 === void 0 ? void 0 : (_contentData$issueTyp3 = _contentData$issueTyp2.avatar) === null || _contentData$issueTyp3 === void 0 ? void 0 : _contentData$issueTyp3.xsmall) === 'string' ? contentData.issueType.avatar.xsmall : undefined
122
164
  } : undefined;
165
+ const fieldName = fieldLocation && getFieldName(contentData, fieldLocation.fieldId);
123
166
  return Promise.resolve({
124
167
  url: webUrl,
125
168
  sourceAri: workItemAri,
126
169
  title: summary,
127
- issueType
170
+ issueType,
171
+ ...(fieldName !== undefined && {
172
+ fieldName
173
+ })
128
174
  });
129
175
  } else {
130
176
  return await resolveNoAccessWorkItemInfo(workItemAri, issueAri);
@@ -1,4 +1,10 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /**
3
+ * Where a synced block location sits relative to the document hosting the editor.
4
+ * `same-document` is the host document itself, the same page or the same work item field.
5
+ * `same-parent-document` is another field of the host work item.
6
+ */
7
+
2
8
  export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
3
9
  SyncBlockError["Errored"] = "errored";
4
10
  SyncBlockError["NotFound"] = "not_found";