@atlaskit/smart-user-picker 8.2.1 → 8.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @atlassian/smart-user-picker
2
2
 
3
+ ## 8.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`56ac0cf74b37d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/56ac0cf74b37d) -
8
+ Migrated teams assets to new service
9
+
10
+ ## 8.3.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [`be2f98085fa9f`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/be2f98085fa9f) -
15
+ [ux] Introduce additional email props to smart user picker (displayEmailInByline,
16
+ enableEmailSearch, allowEmailSelectionWhenEmailMatched) and one type adjustment to user picker
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies
21
+
3
22
  ## 8.2.1
4
23
 
5
24
  ### Patch Changes
@@ -11,7 +11,7 @@ var _uuid = require("uuid");
11
11
  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; }
12
12
  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; }
13
13
  var packageName = "@atlaskit/smart-user-picker";
14
- var packageVersion = "0.0.0-development";
14
+ var packageVersion = "8.3.0";
15
15
  var startSession = exports.startSession = function startSession() {
16
16
  return {
17
17
  id: (0, _uuid.v4)(),
@@ -6,8 +6,9 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.SmartUserPickerWithoutAnalytics = exports.SmartUserPicker = void 0;
9
- var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
9
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
11
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
11
12
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
12
13
  var _toArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toArray"));
13
14
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
@@ -28,8 +29,10 @@ var _userPicker = _interopRequireWildcard(require("@atlaskit/user-picker"));
28
29
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
29
30
  var _analytics = require("../analytics");
30
31
  var _MessagesIntlProvider = _interopRequireDefault(require("./MessagesIntlProvider"));
32
+ var _types = require("../types");
31
33
  var _service = require("../service");
32
34
  var _ufoExperiences = require("../ufoExperiences");
35
+ var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched"];
33
36
  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 _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
34
37
  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; }
35
38
  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; }
@@ -71,6 +74,9 @@ var getUsersForAnalytics = function getUsersForAnalytics(users) {
71
74
  var checkIf500Event = function checkIf500Event(statusCode) {
72
75
  return 500 <= statusCode && statusCode < 600;
73
76
  };
77
+ var isEmailQuery = function isEmailQuery(query) {
78
+ return (0, _userPicker.isValidEmail)(query.trim()) === 'VALID';
79
+ };
74
80
  var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Component) {
75
81
  function SmartUserPickerWithoutAnalytics(props) {
76
82
  var _this$props$debounceT;
@@ -85,6 +91,8 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
85
91
  defaultValue: [],
86
92
  bootstrapOptions: []
87
93
  });
94
+ // Track if the last search was an email search that found matches
95
+ (0, _defineProperty2.default)(_this, "lastEmailSearchFoundMatches", false);
88
96
  (0, _defineProperty2.default)(_this, "abortOptionsShownUfoExperience", function () {
89
97
  if (_this.optionsShownUfoExperienceInstance.state.id === _ufo.UFOExperienceState.STARTED.id) {
90
98
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -110,14 +118,15 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
110
118
  });
111
119
  (0, _defineProperty2.default)(_this, "memoizedFilterOptions", (0, _memoizeOne.default)(_this.filterOptions));
112
120
  (0, _defineProperty2.default)(_this, "getUsers", (0, _debounce.default)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
113
- var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeUsers, includeNonLicensedUsers, intl, maxOptions, objectId, onEmpty, onError, overrideByline, orgId, principalId, productAttributes, productKey, searchQueryFilter, siteId, transformOptions, userResolvers, maxNumberOfResults, startTime, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli;
121
+ var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeUsers, includeNonLicensedUsers, intl, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, orgId, principalId, productAttributes, productKey, searchQueryFilter, siteId, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli;
114
122
  return _regenerator.default.wrap(function _callee$(_context) {
115
123
  while (1) switch (_context.prev = _context.next) {
116
124
  case 0:
117
125
  _this$state = _this.state, query = _this$state.query, sessionId = _this$state.sessionId, closed = _this$state.closed;
118
- _this$props = _this.props, baseUrl = _this$props.baseUrl, childObjectId = _this$props.childObjectId, containerId = _this$props.containerId, fieldId = _this$props.fieldId, includeGroups = _this$props.includeGroups, includeTeams = _this$props.includeTeams, includeUsers = _this$props.includeUsers, includeNonLicensedUsers = _this$props.includeNonLicensedUsers, intl = _this$props.intl, maxOptions = _this$props.maxOptions, objectId = _this$props.objectId, onEmpty = _this$props.onEmpty, onError = _this$props.onError, overrideByline = _this$props.overrideByline, orgId = _this$props.orgId, principalId = _this$props.principalId, productAttributes = _this$props.productAttributes, productKey = _this$props.productKey, searchQueryFilter = _this$props.searchQueryFilter, siteId = _this$props.siteId, transformOptions = _this$props.transformOptions, userResolvers = _this$props.userResolvers;
126
+ _this$props = _this.props, baseUrl = _this$props.baseUrl, childObjectId = _this$props.childObjectId, containerId = _this$props.containerId, fieldId = _this$props.fieldId, includeGroups = _this$props.includeGroups, includeTeams = _this$props.includeTeams, includeUsers = _this$props.includeUsers, includeNonLicensedUsers = _this$props.includeNonLicensedUsers, intl = _this$props.intl, maxOptions = _this$props.maxOptions, objectId = _this$props.objectId, onEmpty = _this$props.onEmpty, onError = _this$props.onError, overrideByline = _this$props.overrideByline, displayEmailInByline = _this$props.displayEmailInByline, orgId = _this$props.orgId, principalId = _this$props.principalId, productAttributes = _this$props.productAttributes, productKey = _this$props.productKey, searchQueryFilter = _this$props.searchQueryFilter, siteId = _this$props.siteId, transformOptions = _this$props.transformOptions, userResolvers = _this$props.userResolvers, enableEmailSearch = _this$props.enableEmailSearch;
119
127
  maxNumberOfResults = maxOptions || 100;
120
- startTime = window.performance.now();
128
+ startTime = window.performance.now(); // Check if this is an email search
129
+ isEmail = enableEmailSearch && isEmailQuery(query);
121
130
  recommendationsRequest = {
122
131
  baseUrl: baseUrl,
123
132
  context: {
@@ -133,18 +142,27 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
133
142
  productAttributes: productAttributes
134
143
  },
135
144
  includeUsers: includeUsers,
136
- includeGroups: includeGroups,
137
- includeTeams: includeTeams,
145
+ // For email searches, disable groups and teams
146
+ includeGroups: isEmail ? false : includeGroups,
147
+ includeTeams: isEmail ? false : includeTeams,
138
148
  includeNonLicensedUsers: includeNonLicensedUsers,
139
149
  maxNumberOfResults: maxNumberOfResults,
140
- query: query,
141
- searchQueryFilter: searchQueryFilter
150
+ // For email searches, leverage customQuery instead of queryString
151
+ query: isEmail ? '' : query,
152
+ customQuery: isEmail ? "(email:\"".concat(query.trim(), "\")") : '',
153
+ /*
154
+ For email-based searches, we have decided to filter out apps.
155
+ Also - because the other 2 filters ((NOT not_mentionable:true) AND (account_status:active)) are included
156
+ when filter is empty, they have been added here to maintain consistency.
157
+ Further ref: https://developer.atlassian.com/platform/user-recommendations/guides/frequently-asked-questions/#filter-behavior
158
+ */
159
+ searchQueryFilter: isEmail && !searchQueryFilter ? '(NOT not_mentionable:true) AND (account_status:active) AND (NOT account_type:app)' : searchQueryFilter
142
160
  };
143
- _context.prev = 5;
161
+ _context.prev = 6;
144
162
  _query = _this.state.query;
145
163
  _this.fireEvent(_analytics.requestUsersEvent);
146
164
  if (!(0, _platformFeatureFlags.fg)('twcg-444-invite-usd-improvements-m2-gate')) {
147
- _context.next = 20;
165
+ _context.next = 21;
148
166
  break;
149
167
  }
150
168
  userRecommendationsPromise = (0, _service.getUserRecommendations)(recommendationsRequest, intl);
@@ -157,22 +175,22 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
157
175
  return [];
158
176
  });
159
177
  });
160
- _context.next = 13;
178
+ _context.next = 14;
161
179
  return Promise.all([userRecommendationsPromise].concat((0, _toConsumableArray2.default)(userResolversPromises)));
162
- case 13:
180
+ case 14:
163
181
  _yield$Promise$all = _context.sent;
164
182
  _yield$Promise$all2 = (0, _toArray2.default)(_yield$Promise$all);
165
183
  mainRecommendations = _yield$Promise$all2[0];
166
184
  userResolverResults = _yield$Promise$all2.slice(1);
167
185
  recommendedUsers = [mainRecommendations].concat((0, _toConsumableArray2.default)(userResolverResults)).flat();
168
- _context.next = 23;
186
+ _context.next = 24;
169
187
  break;
170
- case 20:
171
- _context.next = 22;
188
+ case 21:
189
+ _context.next = 23;
172
190
  return (0, _service.getUserRecommendations)(recommendationsRequest, intl);
173
- case 22:
174
- recommendedUsers = _context.sent;
175
191
  case 23:
192
+ recommendedUsers = _context.sent;
193
+ case 24:
176
194
  if (overrideByline) {
177
195
  _iterator = _createForOfIteratorHelper(recommendedUsers);
178
196
  try {
@@ -188,52 +206,79 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
188
206
  _iterator.f();
189
207
  }
190
208
  }
209
+ if (displayEmailInByline) {
210
+ _iterator2 = _createForOfIteratorHelper(recommendedUsers);
211
+ try {
212
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
213
+ _option = _step2.value;
214
+ if ((0, _userPicker.isUser)(_option) || (0, _userPicker.isExternalUser)(_option)) {
215
+ if (_option.userType === _types.UserEntityType.DEFAULT || _option.userType === _types.UserEntityType.CUSTOMER) {
216
+ // Respect existing byline if present
217
+ if (_option.email && !_option.byline) {
218
+ _option.byline = _option.email;
219
+ }
220
+ }
221
+ }
222
+ }
223
+ } catch (err) {
224
+ _iterator2.e(err);
225
+ } finally {
226
+ _iterator2.f();
227
+ }
228
+ }
229
+
230
+ // Track if email search found matches for conditional allowEmail logic
231
+ if (isEmail) {
232
+ _this.lastEmailSearchFoundMatches = recommendedUsers.length > 0;
233
+ } else {
234
+ _this.lastEmailSearchFoundMatches = false;
235
+ }
191
236
  elapsedTimeMilli = window.performance.now() - startTime;
192
237
  if (!transformOptions) {
193
- _context.next = 31;
238
+ _context.next = 34;
194
239
  break;
195
240
  }
196
- _context.next = 28;
241
+ _context.next = 31;
197
242
  return transformOptions(recommendedUsers, _query);
198
- case 28:
243
+ case 31:
199
244
  _context.t0 = _context.sent;
200
- _context.next = 32;
245
+ _context.next = 35;
201
246
  break;
202
- case 31:
247
+ case 34:
203
248
  _context.t0 = recommendedUsers;
204
- case 32:
249
+ case 35:
205
250
  transformedOptions = _context.t0;
206
251
  if (!(transformedOptions.length === 0 && onEmpty)) {
207
- _context.next = 48;
252
+ _context.next = 51;
208
253
  break;
209
254
  }
210
- _context.next = 36;
255
+ _context.next = 39;
211
256
  return onEmpty(_query);
212
- case 36:
257
+ case 39:
213
258
  _context.t3 = _yield$onEmpty = _context.sent;
214
259
  _context.t2 = _context.t3 !== null;
215
260
  if (!_context.t2) {
216
- _context.next = 40;
261
+ _context.next = 43;
217
262
  break;
218
263
  }
219
264
  _context.t2 = _yield$onEmpty !== void 0;
220
- case 40:
265
+ case 43:
221
266
  if (!_context.t2) {
222
- _context.next = 44;
267
+ _context.next = 47;
223
268
  break;
224
269
  }
225
270
  _context.t4 = _yield$onEmpty;
226
- _context.next = 45;
271
+ _context.next = 48;
227
272
  break;
228
- case 44:
273
+ case 47:
229
274
  _context.t4 = [];
230
- case 45:
275
+ case 48:
231
276
  _context.t1 = _context.t4;
232
- _context.next = 49;
277
+ _context.next = 52;
233
278
  break;
234
- case 48:
279
+ case 51:
235
280
  _context.t1 = transformedOptions;
236
- case 49:
281
+ case 52:
237
282
  displayedUsers = _context.t1;
238
283
  _this.setState(function (state) {
239
284
  var applicable = state.query === _query;
@@ -255,11 +300,11 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
255
300
  loading: loading
256
301
  };
257
302
  });
258
- _context.next = 82;
303
+ _context.next = 85;
259
304
  break;
260
- case 53:
261
- _context.prev = 53;
262
- _context.t5 = _context["catch"](5);
305
+ case 56:
306
+ _context.prev = 56;
307
+ _context.t5 = _context["catch"](6);
263
308
  is5xxEvent = checkIf500Event(_context.t5.statusCode);
264
309
  if (!closed && !onError && is5xxEvent) {
265
310
  // If the user lookup fails while the menu is open, and the consumer is not providing a
@@ -271,35 +316,35 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
271
316
  });
272
317
  onErrorProducedError = false;
273
318
  defaultUsers = [];
274
- _context.prev = 60;
319
+ _context.prev = 63;
275
320
  if (!onError) {
276
- _context.next = 70;
321
+ _context.next = 73;
277
322
  break;
278
323
  }
279
- _context.next = 64;
324
+ _context.next = 67;
280
325
  return onError(_context.t5, recommendationsRequest);
281
- case 64:
326
+ case 67:
282
327
  _context.t7 = _context.sent;
283
328
  if (_context.t7) {
284
- _context.next = 67;
329
+ _context.next = 70;
285
330
  break;
286
331
  }
287
332
  _context.t7 = [];
288
- case 67:
333
+ case 70:
289
334
  _context.t6 = _context.t7;
290
- _context.next = 71;
335
+ _context.next = 74;
291
336
  break;
292
- case 70:
337
+ case 73:
293
338
  _context.t6 = [];
294
- case 71:
339
+ case 74:
295
340
  defaultUsers = _context.t6;
296
- _context.next = 77;
341
+ _context.next = 80;
297
342
  break;
298
- case 74:
299
- _context.prev = 74;
300
- _context.t8 = _context["catch"](60);
301
- onErrorProducedError = true;
302
343
  case 77:
344
+ _context.prev = 77;
345
+ _context.t8 = _context["catch"](63);
346
+ onErrorProducedError = true;
347
+ case 80:
303
348
  if (onErrorProducedError && is5xxEvent) {
304
349
  // Log error from fallback data source `onError` to UFO
305
350
  _this.optionsShownUfoExperienceInstance.failure(ufoEndStateConfig(_this.props.fieldId));
@@ -316,11 +361,11 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
316
361
  elapsedTimeMilli: _elapsedTimeMilli,
317
362
  productAttributes: productAttributes
318
363
  });
319
- case 82:
364
+ case 85:
320
365
  case "end":
321
366
  return _context.stop();
322
367
  }
323
- }, _callee, null, [[5, 53], [60, 74]]);
368
+ }, _callee, null, [[6, 56], [63, 77]]);
324
369
  })), (_this$props$debounceT = _this.props.debounceTime) !== null && _this$props$debounceT !== void 0 ? _this$props$debounceT : 0));
325
370
  (0, _defineProperty2.default)(_this, "onInputChange", function (newQuery, sessionId) {
326
371
  var query = newQuery || '';
@@ -376,7 +421,6 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
376
421
  });
377
422
  (0, _defineProperty2.default)(_this, "onFocus", function (sessionId) {
378
423
  var state = {
379
- query: '',
380
424
  closed: false
381
425
  };
382
426
  _this.startOptionsShownUfoExperience();
@@ -488,7 +532,21 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
488
532
  }, {
489
533
  key: "render",
490
534
  value: function render() {
491
- return /*#__PURE__*/_react.default.createElement(_MessagesIntlProvider.default, null, /*#__PURE__*/_react.default.createElement(_userPicker.default, (0, _extends2.default)({}, this.props, {
535
+ var _this$props2 = this.props,
536
+ allowEmail = _this$props2.allowEmail,
537
+ enableEmailSearch = _this$props2.enableEmailSearch,
538
+ allowEmailSelectionWhenEmailMatched = _this$props2.allowEmailSelectionWhenEmailMatched,
539
+ restProps = (0, _objectWithoutProperties2.default)(_this$props2, _excluded);
540
+
541
+ // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
542
+ var shouldAllowEmail = allowEmail;
543
+ if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
544
+ // Only allow email selection if we're in an email search that found no matches
545
+ var isCurrentQueryEmail = isEmailQuery(this.state.query);
546
+ shouldAllowEmail = !isCurrentQueryEmail || !this.lastEmailSearchFoundMatches;
547
+ }
548
+ return /*#__PURE__*/_react.default.createElement(_MessagesIntlProvider.default, null, /*#__PURE__*/_react.default.createElement(_userPicker.default, (0, _extends2.default)({}, restProps, {
549
+ allowEmail: shouldAllowEmail,
492
550
  onInputChange: this.onInputChange,
493
551
  onBlur: this.onBlur,
494
552
  onFocus: this.onFocus,
@@ -505,9 +563,12 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
505
563
  includeGroups: false,
506
564
  includeTeams: false,
507
565
  includeNonLicensedUsers: false,
566
+ displayEmailInByline: false,
508
567
  prefetch: false,
509
568
  principalId: 'Context',
510
569
  debounceTime: DEFAULT_DEBOUNCE_TIME_MS,
511
- userResolvers: []
570
+ userResolvers: [],
571
+ enableEmailSearch: false,
572
+ allowEmailSelectionWhenEmailMatched: true
512
573
  });
513
574
  var SmartUserPicker = exports.SmartUserPicker = (0, _analyticsNext.withAnalyticsEvents)()((0, _reactIntlNext.injectIntl)(SmartUserPickerWithoutAnalytics));
@@ -35,7 +35,7 @@ var getUserRecommendations = function getUserRecommendations(request, intl) {
35
35
  }, ((_request$context = request.context) === null || _request$context === void 0 || (_request$context = _request$context.productAttributes) === null || _request$context === void 0 ? void 0 : _request$context.isEntitledConfluenceExternalCollaborator) && {
36
36
  productAccessPermissionIds: ['write', 'external-collaborator-write']
37
37
  }), {}, {
38
- customQuery: '',
38
+ customQuery: request.customQuery || '',
39
39
  customerDirectoryId: '',
40
40
  filter: request.searchQueryFilter || '',
41
41
  minimumAccessLevel: 'APPLICATION',
@@ -30,6 +30,7 @@ var transformUser = function transformUser(item, intl) {
30
30
  return {
31
31
  id: user.id,
32
32
  type: user.nonLicensedUser ? _userPicker.ExternalUserType : _userPicker.UserType,
33
+ userType: user.userType,
33
34
  avatarUrl: user.avatarUrl,
34
35
  name: user.name,
35
36
  email: user.email,
package/dist/cjs/types.js CHANGED
@@ -3,10 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.EntityType = void 0;
6
+ exports.UserEntityType = exports.EntityType = void 0;
7
7
  var EntityType = exports.EntityType = /*#__PURE__*/function (EntityType) {
8
8
  EntityType["USER"] = "USER";
9
9
  EntityType["TEAM"] = "TEAM";
10
10
  EntityType["GROUP"] = "GROUP";
11
11
  return EntityType;
12
+ }({});
13
+ var UserEntityType = exports.UserEntityType = /*#__PURE__*/function (UserEntityType) {
14
+ UserEntityType["DEFAULT"] = "DEFAULT";
15
+ UserEntityType["APP"] = "APP";
16
+ UserEntityType["CUSTOMER"] = "CUSTOMER";
17
+ UserEntityType["SYSTEM"] = "SYSTEM";
18
+ return UserEntityType;
12
19
  }({}); // Override UserPickerProps below with replacement documentation
@@ -1,7 +1,7 @@
1
1
  import { createAndFireEvent } from '@atlaskit/analytics-next';
2
2
  import { v4 as uuid } from 'uuid';
3
3
  const packageName = "@atlaskit/smart-user-picker";
4
- const packageVersion = "0.0.0-development";
4
+ const packageVersion = "8.3.0";
5
5
  export const startSession = () => ({
6
6
  id: uuid(),
7
7
  start: Date.now(),
@@ -7,10 +7,11 @@ import { withAnalyticsEvents } from '@atlaskit/analytics-next';
7
7
  import memoizeOne from 'memoize-one';
8
8
  import { injectIntl } from 'react-intl-next';
9
9
  import { UFOExperienceState } from '@atlaskit/ufo';
10
- import UserPicker, { isExternalUser, isTeam, isUser } from '@atlaskit/user-picker';
10
+ import UserPicker, { isExternalUser, isTeam, isUser, isValidEmail } from '@atlaskit/user-picker';
11
11
  import { fg } from '@atlaskit/platform-feature-flags';
12
12
  import { requestUsersEvent, filterUsersEvent, preparedUsersLoadedEvent, successfulRequestUsersEvent, failedRequestUsersEvent, mountedWithPrefetchEvent, createAndFireEventInElementsChannel, failedUserResolversEvent } from '../analytics';
13
13
  import MessagesIntlProvider from './MessagesIntlProvider';
14
+ import { UserEntityType } from '../types';
14
15
  import { getUserRecommendations, hydrateDefaultValues } from '../service';
15
16
  import { smartUserPickerOptionsShownUfoExperience } from '../ufoExperiences';
16
17
  const DEFAULT_DEBOUNCE_TIME_MS = 150;
@@ -39,6 +40,9 @@ const getUsersForAnalytics = users => (users || []).map(({
39
40
  type
40
41
  }));
41
42
  const checkIf500Event = statusCode => 500 <= statusCode && statusCode < 600;
43
+ const isEmailQuery = query => {
44
+ return isValidEmail(query.trim()) === 'VALID';
45
+ };
42
46
  export class SmartUserPickerWithoutAnalytics extends React.Component {
43
47
  constructor(props) {
44
48
  var _this$props$debounceT;
@@ -51,6 +55,8 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
51
55
  defaultValue: [],
52
56
  bootstrapOptions: []
53
57
  });
58
+ // Track if the last search was an email search that found matches
59
+ _defineProperty(this, "lastEmailSearchFoundMatches", false);
54
60
  _defineProperty(this, "abortOptionsShownUfoExperience", () => {
55
61
  if (this.optionsShownUfoExperienceInstance.state.id === UFOExperienceState.STARTED.id) {
56
62
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -93,6 +99,7 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
93
99
  onEmpty,
94
100
  onError,
95
101
  overrideByline,
102
+ displayEmailInByline,
96
103
  orgId,
97
104
  principalId,
98
105
  productAttributes,
@@ -100,10 +107,14 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
100
107
  searchQueryFilter,
101
108
  siteId,
102
109
  transformOptions,
103
- userResolvers
110
+ userResolvers,
111
+ enableEmailSearch
104
112
  } = this.props;
105
113
  const maxNumberOfResults = maxOptions || 100;
106
114
  const startTime = window.performance.now();
115
+
116
+ // Check if this is an email search
117
+ const isEmail = enableEmailSearch && isEmailQuery(query);
107
118
  const recommendationsRequest = {
108
119
  baseUrl,
109
120
  context: {
@@ -119,12 +130,21 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
119
130
  productAttributes
120
131
  },
121
132
  includeUsers,
122
- includeGroups,
123
- includeTeams,
133
+ // For email searches, disable groups and teams
134
+ includeGroups: isEmail ? false : includeGroups,
135
+ includeTeams: isEmail ? false : includeTeams,
124
136
  includeNonLicensedUsers,
125
137
  maxNumberOfResults,
126
- query,
127
- searchQueryFilter
138
+ // For email searches, leverage customQuery instead of queryString
139
+ query: isEmail ? '' : query,
140
+ customQuery: isEmail ? `(email:"${query.trim()}")` : '',
141
+ /*
142
+ For email-based searches, we have decided to filter out apps.
143
+ Also - because the other 2 filters ((NOT not_mentionable:true) AND (account_status:active)) are included
144
+ when filter is empty, they have been added here to maintain consistency.
145
+ Further ref: https://developer.atlassian.com/platform/user-recommendations/guides/frequently-asked-questions/#filter-behavior
146
+ */
147
+ searchQueryFilter: isEmail && !searchQueryFilter ? '(NOT not_mentionable:true) AND (account_status:active) AND (NOT account_type:app)' : searchQueryFilter
128
148
  };
129
149
  try {
130
150
  var _await$onEmpty;
@@ -154,6 +174,25 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
154
174
  }
155
175
  }
156
176
  }
177
+ if (displayEmailInByline) {
178
+ for (let option of recommendedUsers) {
179
+ if (isUser(option) || isExternalUser(option)) {
180
+ if (option.userType === UserEntityType.DEFAULT || option.userType === UserEntityType.CUSTOMER) {
181
+ // Respect existing byline if present
182
+ if (option.email && !option.byline) {
183
+ option.byline = option.email;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ }
189
+
190
+ // Track if email search found matches for conditional allowEmail logic
191
+ if (isEmail) {
192
+ this.lastEmailSearchFoundMatches = recommendedUsers.length > 0;
193
+ } else {
194
+ this.lastEmailSearchFoundMatches = false;
195
+ }
157
196
  const elapsedTimeMilli = window.performance.now() - startTime;
158
197
  const transformedOptions = transformOptions ? await transformOptions(recommendedUsers, query) : recommendedUsers;
159
198
  const displayedUsers = transformedOptions.length === 0 && onEmpty ? (_await$onEmpty = await onEmpty(query)) !== null && _await$onEmpty !== void 0 ? _await$onEmpty : [] : transformedOptions;
@@ -266,7 +305,6 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
266
305
  });
267
306
  _defineProperty(this, "onFocus", sessionId => {
268
307
  const state = {
269
- query: '',
270
308
  closed: false
271
309
  };
272
310
  this.startOptionsShownUfoExperience();
@@ -348,7 +386,22 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
348
386
  }
349
387
  }
350
388
  render() {
351
- return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, this.props, {
389
+ const {
390
+ allowEmail,
391
+ enableEmailSearch,
392
+ allowEmailSelectionWhenEmailMatched,
393
+ ...restProps
394
+ } = this.props;
395
+
396
+ // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
397
+ let shouldAllowEmail = allowEmail;
398
+ if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
399
+ // Only allow email selection if we're in an email search that found no matches
400
+ const isCurrentQueryEmail = isEmailQuery(this.state.query);
401
+ shouldAllowEmail = !isCurrentQueryEmail || !this.lastEmailSearchFoundMatches;
402
+ }
403
+ return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, restProps, {
404
+ allowEmail: shouldAllowEmail,
352
405
  onInputChange: this.onInputChange,
353
406
  onBlur: this.onBlur,
354
407
  onFocus: this.onFocus,
@@ -366,9 +419,12 @@ _defineProperty(SmartUserPickerWithoutAnalytics, "defaultProps", {
366
419
  includeGroups: false,
367
420
  includeTeams: false,
368
421
  includeNonLicensedUsers: false,
422
+ displayEmailInByline: false,
369
423
  prefetch: false,
370
424
  principalId: 'Context',
371
425
  debounceTime: DEFAULT_DEBOUNCE_TIME_MS,
372
- userResolvers: []
426
+ userResolvers: [],
427
+ enableEmailSearch: false,
428
+ allowEmailSelectionWhenEmailMatched: true
373
429
  });
374
430
  export const SmartUserPicker = withAnalyticsEvents()(injectIntl(SmartUserPickerWithoutAnalytics));
@@ -25,7 +25,7 @@ const getUserRecommendations = (request, intl) => {
25
25
  ...(((_request$context = request.context) === null || _request$context === void 0 ? void 0 : (_request$context$prod = _request$context.productAttributes) === null || _request$context$prod === void 0 ? void 0 : _request$context$prod.isEntitledConfluenceExternalCollaborator) && {
26
26
  productAccessPermissionIds: ['write', 'external-collaborator-write']
27
27
  }),
28
- customQuery: '',
28
+ customQuery: request.customQuery || '',
29
29
  customerDirectoryId: '',
30
30
  filter: request.searchQueryFilter || '',
31
31
  minimumAccessLevel: 'APPLICATION',
@@ -24,6 +24,7 @@ const transformUser = (item, intl) => {
24
24
  return {
25
25
  id: user.id,
26
26
  type: user.nonLicensedUser ? ExternalUserType : UserType,
27
+ userType: user.userType,
27
28
  avatarUrl: user.avatarUrl,
28
29
  name: user.name,
29
30
  email: user.email,
@@ -4,5 +4,12 @@ export let EntityType = /*#__PURE__*/function (EntityType) {
4
4
  EntityType["GROUP"] = "GROUP";
5
5
  return EntityType;
6
6
  }({});
7
+ export let UserEntityType = /*#__PURE__*/function (UserEntityType) {
8
+ UserEntityType["DEFAULT"] = "DEFAULT";
9
+ UserEntityType["APP"] = "APP";
10
+ UserEntityType["CUSTOMER"] = "CUSTOMER";
11
+ UserEntityType["SYSTEM"] = "SYSTEM";
12
+ return UserEntityType;
13
+ }({});
7
14
 
8
15
  // Override UserPickerProps below with replacement documentation
@@ -4,7 +4,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
4
4
  import { createAndFireEvent } from '@atlaskit/analytics-next';
5
5
  import { v4 as uuid } from 'uuid';
6
6
  var packageName = "@atlaskit/smart-user-picker";
7
- var packageVersion = "0.0.0-development";
7
+ var packageVersion = "8.3.0";
8
8
  export var startSession = function startSession() {
9
9
  return {
10
10
  id: uuid(),
@@ -1,4 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
+ import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
2
3
  import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
3
4
  import _toArray from "@babel/runtime/helpers/toArray";
4
5
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
@@ -8,6 +9,7 @@ import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstruct
8
9
  import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
9
10
  import _inherits from "@babel/runtime/helpers/inherits";
10
11
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
12
+ var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched"];
11
13
  import _regeneratorRuntime from "@babel/runtime/regenerator";
12
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; }
13
15
  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; }
@@ -23,10 +25,11 @@ import { withAnalyticsEvents } from '@atlaskit/analytics-next';
23
25
  import memoizeOne from 'memoize-one';
24
26
  import { injectIntl } from 'react-intl-next';
25
27
  import { UFOExperienceState } from '@atlaskit/ufo';
26
- import UserPicker, { isExternalUser, isTeam, isUser } from '@atlaskit/user-picker';
28
+ import UserPicker, { isExternalUser, isTeam, isUser, isValidEmail } from '@atlaskit/user-picker';
27
29
  import { fg } from '@atlaskit/platform-feature-flags';
28
30
  import { requestUsersEvent, filterUsersEvent, preparedUsersLoadedEvent, successfulRequestUsersEvent, failedRequestUsersEvent, mountedWithPrefetchEvent, createAndFireEventInElementsChannel, failedUserResolversEvent } from '../analytics';
29
31
  import MessagesIntlProvider from './MessagesIntlProvider';
32
+ import { UserEntityType } from '../types';
30
33
  import { getUserRecommendations, hydrateDefaultValues } from '../service';
31
34
  import { smartUserPickerOptionsShownUfoExperience } from '../ufoExperiences';
32
35
  var DEFAULT_DEBOUNCE_TIME_MS = 150;
@@ -62,6 +65,9 @@ var getUsersForAnalytics = function getUsersForAnalytics(users) {
62
65
  var checkIf500Event = function checkIf500Event(statusCode) {
63
66
  return 500 <= statusCode && statusCode < 600;
64
67
  };
68
+ var isEmailQuery = function isEmailQuery(query) {
69
+ return isValidEmail(query.trim()) === 'VALID';
70
+ };
65
71
  export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Component) {
66
72
  function SmartUserPickerWithoutAnalytics(props) {
67
73
  var _this$props$debounceT;
@@ -76,6 +82,8 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
76
82
  defaultValue: [],
77
83
  bootstrapOptions: []
78
84
  });
85
+ // Track if the last search was an email search that found matches
86
+ _defineProperty(_this, "lastEmailSearchFoundMatches", false);
79
87
  _defineProperty(_this, "abortOptionsShownUfoExperience", function () {
80
88
  if (_this.optionsShownUfoExperienceInstance.state.id === UFOExperienceState.STARTED.id) {
81
89
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -101,14 +109,15 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
101
109
  });
102
110
  _defineProperty(_this, "memoizedFilterOptions", memoizeOne(_this.filterOptions));
103
111
  _defineProperty(_this, "getUsers", debounce( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
104
- var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeUsers, includeNonLicensedUsers, intl, maxOptions, objectId, onEmpty, onError, overrideByline, orgId, principalId, productAttributes, productKey, searchQueryFilter, siteId, transformOptions, userResolvers, maxNumberOfResults, startTime, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli;
112
+ var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeUsers, includeNonLicensedUsers, intl, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, orgId, principalId, productAttributes, productKey, searchQueryFilter, siteId, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli;
105
113
  return _regeneratorRuntime.wrap(function _callee$(_context) {
106
114
  while (1) switch (_context.prev = _context.next) {
107
115
  case 0:
108
116
  _this$state = _this.state, query = _this$state.query, sessionId = _this$state.sessionId, closed = _this$state.closed;
109
- _this$props = _this.props, baseUrl = _this$props.baseUrl, childObjectId = _this$props.childObjectId, containerId = _this$props.containerId, fieldId = _this$props.fieldId, includeGroups = _this$props.includeGroups, includeTeams = _this$props.includeTeams, includeUsers = _this$props.includeUsers, includeNonLicensedUsers = _this$props.includeNonLicensedUsers, intl = _this$props.intl, maxOptions = _this$props.maxOptions, objectId = _this$props.objectId, onEmpty = _this$props.onEmpty, onError = _this$props.onError, overrideByline = _this$props.overrideByline, orgId = _this$props.orgId, principalId = _this$props.principalId, productAttributes = _this$props.productAttributes, productKey = _this$props.productKey, searchQueryFilter = _this$props.searchQueryFilter, siteId = _this$props.siteId, transformOptions = _this$props.transformOptions, userResolvers = _this$props.userResolvers;
117
+ _this$props = _this.props, baseUrl = _this$props.baseUrl, childObjectId = _this$props.childObjectId, containerId = _this$props.containerId, fieldId = _this$props.fieldId, includeGroups = _this$props.includeGroups, includeTeams = _this$props.includeTeams, includeUsers = _this$props.includeUsers, includeNonLicensedUsers = _this$props.includeNonLicensedUsers, intl = _this$props.intl, maxOptions = _this$props.maxOptions, objectId = _this$props.objectId, onEmpty = _this$props.onEmpty, onError = _this$props.onError, overrideByline = _this$props.overrideByline, displayEmailInByline = _this$props.displayEmailInByline, orgId = _this$props.orgId, principalId = _this$props.principalId, productAttributes = _this$props.productAttributes, productKey = _this$props.productKey, searchQueryFilter = _this$props.searchQueryFilter, siteId = _this$props.siteId, transformOptions = _this$props.transformOptions, userResolvers = _this$props.userResolvers, enableEmailSearch = _this$props.enableEmailSearch;
110
118
  maxNumberOfResults = maxOptions || 100;
111
- startTime = window.performance.now();
119
+ startTime = window.performance.now(); // Check if this is an email search
120
+ isEmail = enableEmailSearch && isEmailQuery(query);
112
121
  recommendationsRequest = {
113
122
  baseUrl: baseUrl,
114
123
  context: {
@@ -124,18 +133,27 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
124
133
  productAttributes: productAttributes
125
134
  },
126
135
  includeUsers: includeUsers,
127
- includeGroups: includeGroups,
128
- includeTeams: includeTeams,
136
+ // For email searches, disable groups and teams
137
+ includeGroups: isEmail ? false : includeGroups,
138
+ includeTeams: isEmail ? false : includeTeams,
129
139
  includeNonLicensedUsers: includeNonLicensedUsers,
130
140
  maxNumberOfResults: maxNumberOfResults,
131
- query: query,
132
- searchQueryFilter: searchQueryFilter
141
+ // For email searches, leverage customQuery instead of queryString
142
+ query: isEmail ? '' : query,
143
+ customQuery: isEmail ? "(email:\"".concat(query.trim(), "\")") : '',
144
+ /*
145
+ For email-based searches, we have decided to filter out apps.
146
+ Also - because the other 2 filters ((NOT not_mentionable:true) AND (account_status:active)) are included
147
+ when filter is empty, they have been added here to maintain consistency.
148
+ Further ref: https://developer.atlassian.com/platform/user-recommendations/guides/frequently-asked-questions/#filter-behavior
149
+ */
150
+ searchQueryFilter: isEmail && !searchQueryFilter ? '(NOT not_mentionable:true) AND (account_status:active) AND (NOT account_type:app)' : searchQueryFilter
133
151
  };
134
- _context.prev = 5;
152
+ _context.prev = 6;
135
153
  _query = _this.state.query;
136
154
  _this.fireEvent(requestUsersEvent);
137
155
  if (!fg('twcg-444-invite-usd-improvements-m2-gate')) {
138
- _context.next = 20;
156
+ _context.next = 21;
139
157
  break;
140
158
  }
141
159
  userRecommendationsPromise = getUserRecommendations(recommendationsRequest, intl);
@@ -148,22 +166,22 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
148
166
  return [];
149
167
  });
150
168
  });
151
- _context.next = 13;
169
+ _context.next = 14;
152
170
  return Promise.all([userRecommendationsPromise].concat(_toConsumableArray(userResolversPromises)));
153
- case 13:
171
+ case 14:
154
172
  _yield$Promise$all = _context.sent;
155
173
  _yield$Promise$all2 = _toArray(_yield$Promise$all);
156
174
  mainRecommendations = _yield$Promise$all2[0];
157
175
  userResolverResults = _yield$Promise$all2.slice(1);
158
176
  recommendedUsers = [mainRecommendations].concat(_toConsumableArray(userResolverResults)).flat();
159
- _context.next = 23;
177
+ _context.next = 24;
160
178
  break;
161
- case 20:
162
- _context.next = 22;
179
+ case 21:
180
+ _context.next = 23;
163
181
  return getUserRecommendations(recommendationsRequest, intl);
164
- case 22:
165
- recommendedUsers = _context.sent;
166
182
  case 23:
183
+ recommendedUsers = _context.sent;
184
+ case 24:
167
185
  if (overrideByline) {
168
186
  _iterator = _createForOfIteratorHelper(recommendedUsers);
169
187
  try {
@@ -179,52 +197,79 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
179
197
  _iterator.f();
180
198
  }
181
199
  }
200
+ if (displayEmailInByline) {
201
+ _iterator2 = _createForOfIteratorHelper(recommendedUsers);
202
+ try {
203
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
204
+ _option = _step2.value;
205
+ if (isUser(_option) || isExternalUser(_option)) {
206
+ if (_option.userType === UserEntityType.DEFAULT || _option.userType === UserEntityType.CUSTOMER) {
207
+ // Respect existing byline if present
208
+ if (_option.email && !_option.byline) {
209
+ _option.byline = _option.email;
210
+ }
211
+ }
212
+ }
213
+ }
214
+ } catch (err) {
215
+ _iterator2.e(err);
216
+ } finally {
217
+ _iterator2.f();
218
+ }
219
+ }
220
+
221
+ // Track if email search found matches for conditional allowEmail logic
222
+ if (isEmail) {
223
+ _this.lastEmailSearchFoundMatches = recommendedUsers.length > 0;
224
+ } else {
225
+ _this.lastEmailSearchFoundMatches = false;
226
+ }
182
227
  elapsedTimeMilli = window.performance.now() - startTime;
183
228
  if (!transformOptions) {
184
- _context.next = 31;
229
+ _context.next = 34;
185
230
  break;
186
231
  }
187
- _context.next = 28;
232
+ _context.next = 31;
188
233
  return transformOptions(recommendedUsers, _query);
189
- case 28:
234
+ case 31:
190
235
  _context.t0 = _context.sent;
191
- _context.next = 32;
236
+ _context.next = 35;
192
237
  break;
193
- case 31:
238
+ case 34:
194
239
  _context.t0 = recommendedUsers;
195
- case 32:
240
+ case 35:
196
241
  transformedOptions = _context.t0;
197
242
  if (!(transformedOptions.length === 0 && onEmpty)) {
198
- _context.next = 48;
243
+ _context.next = 51;
199
244
  break;
200
245
  }
201
- _context.next = 36;
246
+ _context.next = 39;
202
247
  return onEmpty(_query);
203
- case 36:
248
+ case 39:
204
249
  _context.t3 = _yield$onEmpty = _context.sent;
205
250
  _context.t2 = _context.t3 !== null;
206
251
  if (!_context.t2) {
207
- _context.next = 40;
252
+ _context.next = 43;
208
253
  break;
209
254
  }
210
255
  _context.t2 = _yield$onEmpty !== void 0;
211
- case 40:
256
+ case 43:
212
257
  if (!_context.t2) {
213
- _context.next = 44;
258
+ _context.next = 47;
214
259
  break;
215
260
  }
216
261
  _context.t4 = _yield$onEmpty;
217
- _context.next = 45;
262
+ _context.next = 48;
218
263
  break;
219
- case 44:
264
+ case 47:
220
265
  _context.t4 = [];
221
- case 45:
266
+ case 48:
222
267
  _context.t1 = _context.t4;
223
- _context.next = 49;
268
+ _context.next = 52;
224
269
  break;
225
- case 48:
270
+ case 51:
226
271
  _context.t1 = transformedOptions;
227
- case 49:
272
+ case 52:
228
273
  displayedUsers = _context.t1;
229
274
  _this.setState(function (state) {
230
275
  var applicable = state.query === _query;
@@ -246,11 +291,11 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
246
291
  loading: loading
247
292
  };
248
293
  });
249
- _context.next = 82;
294
+ _context.next = 85;
250
295
  break;
251
- case 53:
252
- _context.prev = 53;
253
- _context.t5 = _context["catch"](5);
296
+ case 56:
297
+ _context.prev = 56;
298
+ _context.t5 = _context["catch"](6);
254
299
  is5xxEvent = checkIf500Event(_context.t5.statusCode);
255
300
  if (!closed && !onError && is5xxEvent) {
256
301
  // If the user lookup fails while the menu is open, and the consumer is not providing a
@@ -262,35 +307,35 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
262
307
  });
263
308
  onErrorProducedError = false;
264
309
  defaultUsers = [];
265
- _context.prev = 60;
310
+ _context.prev = 63;
266
311
  if (!onError) {
267
- _context.next = 70;
312
+ _context.next = 73;
268
313
  break;
269
314
  }
270
- _context.next = 64;
315
+ _context.next = 67;
271
316
  return onError(_context.t5, recommendationsRequest);
272
- case 64:
317
+ case 67:
273
318
  _context.t7 = _context.sent;
274
319
  if (_context.t7) {
275
- _context.next = 67;
320
+ _context.next = 70;
276
321
  break;
277
322
  }
278
323
  _context.t7 = [];
279
- case 67:
324
+ case 70:
280
325
  _context.t6 = _context.t7;
281
- _context.next = 71;
326
+ _context.next = 74;
282
327
  break;
283
- case 70:
328
+ case 73:
284
329
  _context.t6 = [];
285
- case 71:
330
+ case 74:
286
331
  defaultUsers = _context.t6;
287
- _context.next = 77;
332
+ _context.next = 80;
288
333
  break;
289
- case 74:
290
- _context.prev = 74;
291
- _context.t8 = _context["catch"](60);
292
- onErrorProducedError = true;
293
334
  case 77:
335
+ _context.prev = 77;
336
+ _context.t8 = _context["catch"](63);
337
+ onErrorProducedError = true;
338
+ case 80:
294
339
  if (onErrorProducedError && is5xxEvent) {
295
340
  // Log error from fallback data source `onError` to UFO
296
341
  _this.optionsShownUfoExperienceInstance.failure(ufoEndStateConfig(_this.props.fieldId));
@@ -307,11 +352,11 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
307
352
  elapsedTimeMilli: _elapsedTimeMilli,
308
353
  productAttributes: productAttributes
309
354
  });
310
- case 82:
355
+ case 85:
311
356
  case "end":
312
357
  return _context.stop();
313
358
  }
314
- }, _callee, null, [[5, 53], [60, 74]]);
359
+ }, _callee, null, [[6, 56], [63, 77]]);
315
360
  })), (_this$props$debounceT = _this.props.debounceTime) !== null && _this$props$debounceT !== void 0 ? _this$props$debounceT : 0));
316
361
  _defineProperty(_this, "onInputChange", function (newQuery, sessionId) {
317
362
  var query = newQuery || '';
@@ -367,7 +412,6 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
367
412
  });
368
413
  _defineProperty(_this, "onFocus", function (sessionId) {
369
414
  var state = {
370
- query: '',
371
415
  closed: false
372
416
  };
373
417
  _this.startOptionsShownUfoExperience();
@@ -479,7 +523,21 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
479
523
  }, {
480
524
  key: "render",
481
525
  value: function render() {
482
- return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, this.props, {
526
+ var _this$props2 = this.props,
527
+ allowEmail = _this$props2.allowEmail,
528
+ enableEmailSearch = _this$props2.enableEmailSearch,
529
+ allowEmailSelectionWhenEmailMatched = _this$props2.allowEmailSelectionWhenEmailMatched,
530
+ restProps = _objectWithoutProperties(_this$props2, _excluded);
531
+
532
+ // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
533
+ var shouldAllowEmail = allowEmail;
534
+ if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
535
+ // Only allow email selection if we're in an email search that found no matches
536
+ var isCurrentQueryEmail = isEmailQuery(this.state.query);
537
+ shouldAllowEmail = !isCurrentQueryEmail || !this.lastEmailSearchFoundMatches;
538
+ }
539
+ return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, restProps, {
540
+ allowEmail: shouldAllowEmail,
483
541
  onInputChange: this.onInputChange,
484
542
  onBlur: this.onBlur,
485
543
  onFocus: this.onFocus,
@@ -498,9 +556,12 @@ _defineProperty(SmartUserPickerWithoutAnalytics, "defaultProps", {
498
556
  includeGroups: false,
499
557
  includeTeams: false,
500
558
  includeNonLicensedUsers: false,
559
+ displayEmailInByline: false,
501
560
  prefetch: false,
502
561
  principalId: 'Context',
503
562
  debounceTime: DEFAULT_DEBOUNCE_TIME_MS,
504
- userResolvers: []
563
+ userResolvers: [],
564
+ enableEmailSearch: false,
565
+ allowEmailSelectionWhenEmailMatched: true
505
566
  });
506
567
  export var SmartUserPicker = withAnalyticsEvents()(injectIntl(SmartUserPickerWithoutAnalytics));
@@ -28,7 +28,7 @@ var getUserRecommendations = function getUserRecommendations(request, intl) {
28
28
  }, ((_request$context = request.context) === null || _request$context === void 0 || (_request$context = _request$context.productAttributes) === null || _request$context === void 0 ? void 0 : _request$context.isEntitledConfluenceExternalCollaborator) && {
29
29
  productAccessPermissionIds: ['write', 'external-collaborator-write']
30
30
  }), {}, {
31
- customQuery: '',
31
+ customQuery: request.customQuery || '',
32
32
  customerDirectoryId: '',
33
33
  filter: request.searchQueryFilter || '',
34
34
  minimumAccessLevel: 'APPLICATION',
@@ -24,6 +24,7 @@ var transformUser = function transformUser(item, intl) {
24
24
  return {
25
25
  id: user.id,
26
26
  type: user.nonLicensedUser ? ExternalUserType : UserType,
27
+ userType: user.userType,
27
28
  avatarUrl: user.avatarUrl,
28
29
  name: user.name,
29
30
  email: user.email,
package/dist/esm/types.js CHANGED
@@ -4,5 +4,12 @@ export var EntityType = /*#__PURE__*/function (EntityType) {
4
4
  EntityType["GROUP"] = "GROUP";
5
5
  return EntityType;
6
6
  }({});
7
+ export var UserEntityType = /*#__PURE__*/function (UserEntityType) {
8
+ UserEntityType["DEFAULT"] = "DEFAULT";
9
+ UserEntityType["APP"] = "APP";
10
+ UserEntityType["CUSTOMER"] = "CUSTOMER";
11
+ UserEntityType["SYSTEM"] = "SYSTEM";
12
+ return UserEntityType;
13
+ }({});
7
14
 
8
15
  // Override UserPickerProps below with replacement documentation
@@ -5,6 +5,7 @@ import { type OptionData } from '@atlaskit/user-picker';
5
5
  import { type Props, type State, type FilterOptions } from '../types';
6
6
  export declare class SmartUserPickerWithoutAnalytics extends React.Component<Props & WrappedComponentProps, State> {
7
7
  state: State;
8
+ private lastEmailSearchFoundMatches;
8
9
  optionsShownUfoExperienceInstance: UFOExperience;
9
10
  static defaultProps: {
10
11
  baseUrl: string;
@@ -12,10 +13,13 @@ export declare class SmartUserPickerWithoutAnalytics extends React.Component<Pro
12
13
  includeGroups: boolean;
13
14
  includeTeams: boolean;
14
15
  includeNonLicensedUsers: boolean;
16
+ displayEmailInByline: boolean;
15
17
  prefetch: boolean;
16
18
  principalId: string;
17
19
  debounceTime: number;
18
20
  userResolvers: never[];
21
+ enableEmailSearch: boolean;
22
+ allowEmailSelectionWhenEmailMatched: boolean;
19
23
  };
20
24
  constructor(props: Props & WrappedComponentProps);
21
25
  componentDidMount(): Promise<void>;
@@ -17,6 +17,7 @@ export interface RecommendationRequest {
17
17
  context: Context;
18
18
  maxNumberOfResults: number;
19
19
  query?: string;
20
+ customQuery?: string;
20
21
  searchQueryFilter?: string;
21
22
  includeUsers?: boolean;
22
23
  includeGroups?: boolean;
@@ -63,10 +64,18 @@ export declare enum EntityType {
63
64
  TEAM = "TEAM",
64
65
  GROUP = "GROUP"
65
66
  }
67
+ export declare enum UserEntityType {
68
+ DEFAULT = "DEFAULT",
69
+ APP = "APP",
70
+ CUSTOMER = "CUSTOMER",
71
+ SYSTEM = "SYSTEM"
72
+ }
66
73
  export interface RecommendationItem {
67
74
  id: string;
68
75
  name?: string;
76
+ email?: string;
69
77
  entityType: EntityType;
78
+ userType?: UserEntityType;
70
79
  avatarUrl: string;
71
80
  description?: string;
72
81
  teamAri?: string;
@@ -153,6 +162,13 @@ export interface SmartProps {
153
162
  * provided as an argument to the function.
154
163
  */
155
164
  overrideByline?: OverrideByline;
165
+ /**
166
+ * When enabled, displays email addresses for users in the byline of each option, if available.
167
+ * Note - overrideByline will take precedent over displayEmailInByline.
168
+ * Note - only certain user types will have their email displayed.
169
+ * @default false
170
+ */
171
+ displayEmailInByline?: boolean;
156
172
  /**
157
173
  * Prefetch the list of suggested assignees before the user picker is focused.
158
174
  * WARNING: please consider carefully before deciding to prefetch your suggestions
@@ -177,6 +193,19 @@ export interface SmartProps {
177
193
  * If you are still waiting for CPUS, you can use the `people` productKey in the interim.
178
194
  */
179
195
  productKey: string;
196
+ /**
197
+ * When enabled, allows searching for users by email address.
198
+ * Email searches will use customQuery instead of queryString and will set includeGroups and includeTeams to false.
199
+ * @default false
200
+ */
201
+ enableEmailSearch?: boolean;
202
+ /**
203
+ * When both allowEmail and enableEmailSearch are true, this controls whether both email entry
204
+ * and matched user entries can be selected simultaneously.
205
+ * If false, only allows email selection when no users are found.
206
+ * @default true
207
+ */
208
+ allowEmailSelectionWhenEmailMatched?: boolean;
180
209
  /**
181
210
  * Filter to be applied to the eventual query to CPUS for user suggestions.
182
211
  * Example:`account_status:"active" AND (NOT email_domain:"connect.atlassian.com")`
@@ -5,6 +5,7 @@ import { type OptionData } from '@atlaskit/user-picker';
5
5
  import { type Props, type State, type FilterOptions } from '../types';
6
6
  export declare class SmartUserPickerWithoutAnalytics extends React.Component<Props & WrappedComponentProps, State> {
7
7
  state: State;
8
+ private lastEmailSearchFoundMatches;
8
9
  optionsShownUfoExperienceInstance: UFOExperience;
9
10
  static defaultProps: {
10
11
  baseUrl: string;
@@ -12,10 +13,13 @@ export declare class SmartUserPickerWithoutAnalytics extends React.Component<Pro
12
13
  includeGroups: boolean;
13
14
  includeTeams: boolean;
14
15
  includeNonLicensedUsers: boolean;
16
+ displayEmailInByline: boolean;
15
17
  prefetch: boolean;
16
18
  principalId: string;
17
19
  debounceTime: number;
18
20
  userResolvers: never[];
21
+ enableEmailSearch: boolean;
22
+ allowEmailSelectionWhenEmailMatched: boolean;
19
23
  };
20
24
  constructor(props: Props & WrappedComponentProps);
21
25
  componentDidMount(): Promise<void>;
@@ -17,6 +17,7 @@ export interface RecommendationRequest {
17
17
  context: Context;
18
18
  maxNumberOfResults: number;
19
19
  query?: string;
20
+ customQuery?: string;
20
21
  searchQueryFilter?: string;
21
22
  includeUsers?: boolean;
22
23
  includeGroups?: boolean;
@@ -63,10 +64,18 @@ export declare enum EntityType {
63
64
  TEAM = "TEAM",
64
65
  GROUP = "GROUP"
65
66
  }
67
+ export declare enum UserEntityType {
68
+ DEFAULT = "DEFAULT",
69
+ APP = "APP",
70
+ CUSTOMER = "CUSTOMER",
71
+ SYSTEM = "SYSTEM"
72
+ }
66
73
  export interface RecommendationItem {
67
74
  id: string;
68
75
  name?: string;
76
+ email?: string;
69
77
  entityType: EntityType;
78
+ userType?: UserEntityType;
70
79
  avatarUrl: string;
71
80
  description?: string;
72
81
  teamAri?: string;
@@ -153,6 +162,13 @@ export interface SmartProps {
153
162
  * provided as an argument to the function.
154
163
  */
155
164
  overrideByline?: OverrideByline;
165
+ /**
166
+ * When enabled, displays email addresses for users in the byline of each option, if available.
167
+ * Note - overrideByline will take precedent over displayEmailInByline.
168
+ * Note - only certain user types will have their email displayed.
169
+ * @default false
170
+ */
171
+ displayEmailInByline?: boolean;
156
172
  /**
157
173
  * Prefetch the list of suggested assignees before the user picker is focused.
158
174
  * WARNING: please consider carefully before deciding to prefetch your suggestions
@@ -177,6 +193,19 @@ export interface SmartProps {
177
193
  * If you are still waiting for CPUS, you can use the `people` productKey in the interim.
178
194
  */
179
195
  productKey: string;
196
+ /**
197
+ * When enabled, allows searching for users by email address.
198
+ * Email searches will use customQuery instead of queryString and will set includeGroups and includeTeams to false.
199
+ * @default false
200
+ */
201
+ enableEmailSearch?: boolean;
202
+ /**
203
+ * When both allowEmail and enableEmailSearch are true, this controls whether both email entry
204
+ * and matched user entries can be selected simultaneously.
205
+ * If false, only allows email selection when no users are found.
206
+ * @default true
207
+ */
208
+ allowEmailSelectionWhenEmailMatched?: boolean;
180
209
  /**
181
210
  * Filter to be applied to the eventual query to CPUS for user suggestions.
182
211
  * Example:`account_status:"active" AND (NOT email_domain:"connect.atlassian.com")`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-user-picker",
3
- "version": "8.2.1",
3
+ "version": "8.3.1",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -38,7 +38,7 @@
38
38
  "@atlaskit/analytics-next": "^11.1.0",
39
39
  "@atlaskit/platform-feature-flags": "^1.1.0",
40
40
  "@atlaskit/ufo": "^0.4.0",
41
- "@atlaskit/user-picker": "^11.6.0",
41
+ "@atlaskit/user-picker": "^11.7.0",
42
42
  "@babel/runtime": "^7.0.0",
43
43
  "lodash": "^4.17.21",
44
44
  "memoize-one": "^6.0.0",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@atlaskit/select": "^21.3.0",
54
- "@atlaskit/util-data-test": "^18.2.0",
54
+ "@atlaskit/util-data-test": "^18.3.0",
55
55
  "@testing-library/dom": "^10.1.0",
56
56
  "@testing-library/react": "^13.4.0",
57
57
  "@testing-library/user-event": "^14.4.3",