@atlaskit/smart-card 44.23.2 → 44.23.4

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 (51) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/extractors/action/extract-invoke-view-action.js +11 -1
  3. package/dist/cjs/extractors/flexible/actions/index.js +5 -2
  4. package/dist/cjs/extractors/flexible/extract-state.js +8 -5
  5. package/dist/cjs/extractors/flexible/index.js +6 -3
  6. package/dist/cjs/state/hooks-external/useSmartLinkActions.js +17 -2
  7. package/dist/cjs/utils/analytics/analytics.js +1 -1
  8. package/dist/cjs/utils/click-helpers.js +30 -1
  9. package/dist/cjs/view/CardWithUrl/component.js +106 -42
  10. package/dist/cjs/view/FlexibleCard/index.js +12 -2
  11. package/dist/cjs/view/HoverCard/components/HoverCardContent.js +24 -1
  12. package/dist/cjs/view/LinkUrl/index.js +1 -1
  13. package/dist/es2019/extractors/action/extract-invoke-view-action.js +10 -1
  14. package/dist/es2019/extractors/flexible/actions/index.js +5 -1
  15. package/dist/es2019/extractors/flexible/extract-state.js +8 -4
  16. package/dist/es2019/extractors/flexible/index.js +6 -2
  17. package/dist/es2019/state/hooks-external/useSmartLinkActions.js +13 -2
  18. package/dist/es2019/utils/analytics/analytics.js +1 -1
  19. package/dist/es2019/utils/click-helpers.js +29 -0
  20. package/dist/es2019/view/CardWithUrl/component.js +108 -43
  21. package/dist/es2019/view/FlexibleCard/index.js +10 -3
  22. package/dist/es2019/view/HoverCard/components/HoverCardContent.js +24 -1
  23. package/dist/es2019/view/LinkUrl/index.js +1 -1
  24. package/dist/esm/extractors/action/extract-invoke-view-action.js +11 -1
  25. package/dist/esm/extractors/flexible/actions/index.js +5 -2
  26. package/dist/esm/extractors/flexible/extract-state.js +8 -5
  27. package/dist/esm/extractors/flexible/index.js +6 -3
  28. package/dist/esm/state/hooks-external/useSmartLinkActions.js +17 -2
  29. package/dist/esm/utils/analytics/analytics.js +1 -1
  30. package/dist/esm/utils/click-helpers.js +29 -0
  31. package/dist/esm/view/CardWithUrl/component.js +107 -43
  32. package/dist/esm/view/FlexibleCard/index.js +13 -3
  33. package/dist/esm/view/HoverCard/components/HoverCardContent.js +24 -1
  34. package/dist/esm/view/LinkUrl/index.js +1 -1
  35. package/dist/types/extractors/action/extract-invoke-preview-action.d.ts +2 -1
  36. package/dist/types/extractors/action/extract-invoke-view-action.d.ts +5 -2
  37. package/dist/types/extractors/action/types.d.ts +1 -0
  38. package/dist/types/extractors/flexible/actions/index.d.ts +3 -1
  39. package/dist/types/extractors/flexible/extract-state.d.ts +2 -1
  40. package/dist/types/extractors/flexible/index.d.ts +1 -1
  41. package/dist/types/utils/click-helpers.d.ts +20 -0
  42. package/dist/types/view/FlexibleCard/types.d.ts +2 -0
  43. package/dist/types-ts4.5/extractors/action/extract-invoke-preview-action.d.ts +2 -1
  44. package/dist/types-ts4.5/extractors/action/extract-invoke-view-action.d.ts +5 -2
  45. package/dist/types-ts4.5/extractors/action/types.d.ts +1 -0
  46. package/dist/types-ts4.5/extractors/flexible/actions/index.d.ts +3 -1
  47. package/dist/types-ts4.5/extractors/flexible/extract-state.d.ts +2 -1
  48. package/dist/types-ts4.5/extractors/flexible/index.d.ts +1 -1
  49. package/dist/types-ts4.5/utils/click-helpers.d.ts +20 -0
  50. package/dist/types-ts4.5/view/FlexibleCard/types.d.ts +2 -0
  51. package/package.json +5 -5
@@ -1,4 +1,5 @@
1
1
  import { extractLink } from '@atlaskit/link-extractors';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
2
3
  import { CardAction } from '../../constants';
3
4
  import { getExtensionKey } from '../../state/helpers';
4
5
  import { canShowAction } from '../../utils/actions/can-show-action';
@@ -18,7 +19,7 @@ const toInvokeRequest = (extensionKey, resourceIdentifiers, actionType, details)
18
19
  details
19
20
  };
20
21
  };
21
- const extractAction = (response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel) => {
22
+ const extractAction = (response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, transformUrl) => {
22
23
  var _action$dataRetrieval, _extractInvokePreview, _action$dataUpdateAct;
23
24
  const extensionKey = getExtensionKey(response);
24
25
  const data = response === null || response === void 0 ? void 0 : response.data;
@@ -41,7 +42,10 @@ const extractAction = (response, id, actionOptions, appearance, origin, fireEven
41
42
  origin,
42
43
  response,
43
44
  isPreviewPanelAvailable,
44
- openPreviewPanel
45
+ openPreviewPanel,
46
+ ...(fg('platform_smartlink_xpc_url_wrapping') ? {
47
+ transformUrl
48
+ } : undefined)
45
49
  })) === null || _extractInvokePreview === void 0 ? void 0 : _extractInvokePreview.invokeAction : undefined;
46
50
  const details = {
47
51
  id,
@@ -54,7 +58,7 @@ const extractAction = (response, id, actionOptions, appearance, origin, fireEven
54
58
  update
55
59
  } : undefined;
56
60
  };
57
- const extractState = (response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel) => {
61
+ const extractState = (response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, transformUrl) => {
58
62
  if (!response || !response.data) {
59
63
  return;
60
64
  }
@@ -65,7 +69,7 @@ const extractState = (response, actionOptions, id, appearance, origin, fireEvent
65
69
  if (!canShowAction(CardAction.ChangeStatusAction, actionOptions)) {
66
70
  return lozenge;
67
71
  }
68
- const action = extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel);
72
+ const action = extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, fg('platform_smartlink_xpc_url_wrapping') ? transformUrl : undefined);
69
73
  return {
70
74
  ...lozenge,
71
75
  action
@@ -28,6 +28,7 @@ const extractFlexibleUiContext = ({
28
28
  aiSummaryConfig,
29
29
  isPreviewPanelAvailable,
30
30
  openPreviewPanel,
31
+ transformUrl,
31
32
  ...props
32
33
  } = {}) => {
33
34
  if (!response) {
@@ -52,7 +53,10 @@ const extractFlexibleUiContext = ({
52
53
  url: props.url,
53
54
  // Use the original URL in edge cases, such as short links for AI summary and copy link actions.
54
55
  isPreviewPanelAvailable,
55
- openPreviewPanel
56
+ openPreviewPanel,
57
+ ...(fg('platform_smartlink_xpc_url_wrapping') ? {
58
+ transformUrl
59
+ } : undefined)
56
60
  }),
57
61
  appliedToComponentsCount: extractAppliedToComponentsCount(data),
58
62
  assignedToGroup: extractPersonAssignedToAsArray(data),
@@ -85,7 +89,7 @@ const extractFlexibleUiContext = ({
85
89
  snippet: extractSmartLinkSummary(response) || undefined,
86
90
  // Explicitly set here to remove an empty string
87
91
  sourceBranch: extractSourceBranch(data),
88
- state: extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel),
92
+ state: extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, fg('platform_smartlink_xpc_url_wrapping') ? transformUrl : undefined),
89
93
  subscriberCount: extractSubscriberCount(data),
90
94
  subTasksProgress: extractSubTasksProgress(data),
91
95
  storyPoints: extractStoryPoints(data),
@@ -3,6 +3,7 @@ import { useMemo } from 'react';
3
3
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
4
4
  import uuid from 'uuid';
5
5
  import { useSmartLinkContext } from '@atlaskit/link-provider';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
6
7
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
7
8
  import { useAnalyticsEvents } from '../../common/analytics/generated/use-analytics-events';
8
9
  import { extractInvokeDownloadAction } from '../../extractors/action/extract-invoke-download-action';
@@ -12,6 +13,7 @@ import { messages } from '../../messages';
12
13
  import { toAction } from '../../utils/actions/to-action';
13
14
  import useInvokeClientAction from '../hooks/use-invoke-client-action';
14
15
  import useResolve from '../hooks/use-resolve';
16
+ import { useSmartLinkCrossProductUrlWrapperGated } from '../hooks/use-smart-link-cross-product-url-wrapper';
15
17
  import { useSmartCardState as useLinkState } from '../store';
16
18
  export function useSmartLinkActions({
17
19
  url,
@@ -34,6 +36,9 @@ export function useSmartLinkActions({
34
36
  fireEvent
35
37
  });
36
38
  const resolve = useResolve();
39
+ const appendCrossProductAnalyticsParams = useSmartLinkCrossProductUrlWrapperGated({
40
+ details: linkState.details
41
+ });
37
42
  if (expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') && prefetch && !linkState.details) {
38
43
  resolve(url);
39
44
  }
@@ -49,7 +54,10 @@ export function useSmartLinkActions({
49
54
  if (downloadActionProps) {
50
55
  actions.push(toAction(downloadActionProps, invokeClientAction, messages.download, 'download-content'));
51
56
  }
52
- const viewActionProps = extractInvokeViewAction(invokeParam);
57
+ const viewActionProps = fg('platform_smartlink_xpc_url_wrapping') ? extractInvokeViewAction({
58
+ ...invokeParam,
59
+ transformUrl: (destinationUrl = url) => appendCrossProductAnalyticsParams(destinationUrl)
60
+ }) : extractInvokeViewAction(invokeParam);
53
61
  if (viewActionProps) {
54
62
  actions.push(toAction(viewActionProps, invokeClientAction, messages.view, 'view-content'));
55
63
  }
@@ -58,7 +66,10 @@ export function useSmartLinkActions({
58
66
  fireEvent,
59
67
  origin,
60
68
  isPreviewPanelAvailable,
61
- openPreviewPanel
69
+ openPreviewPanel,
70
+ ...(fg('platform_smartlink_xpc_url_wrapping') ? {
71
+ transformUrl: (destinationUrl = url) => appendCrossProductAnalyticsParams(destinationUrl)
72
+ } : undefined)
62
73
  });
63
74
  if (previewActionProps) {
64
75
  actions.push(toAction(previewActionProps.invokeAction, invokeClientAction, messages.preview_improved, 'preview-content'));
@@ -2,7 +2,7 @@ export const ANALYTICS_CHANNEL = 'media';
2
2
  export const context = {
3
3
  componentName: 'smart-cards',
4
4
  packageName: "@atlaskit/smart-card" || '',
5
- packageVersion: "44.23.1" || ''
5
+ packageVersion: "44.23.3" || ''
6
6
  };
7
7
  export let TrackQuickActionType = /*#__PURE__*/function (TrackQuickActionType) {
8
8
  TrackQuickActionType["StatusUpdate"] = "StatusUpdate";
@@ -5,4 +5,33 @@
5
5
  */
6
6
  export const isAuxClick = e => {
7
7
  return e.button === 1;
8
+ };
9
+
10
+ /**
11
+ * Extracts `href` and `target` from the anchor element that is the event's `currentTarget`.
12
+ *
13
+ * Smart Link click handlers are attached to multiple card renderers (InlineCard, BlockCard,
14
+ * EmbedCard, FlexibleCard). When the handler needs to manually open a link — for example,
15
+ * when native anchor navigation has been prevented — it uses this helper to read the
16
+ * anchor's resolved URL and intended target from the event rather than re-deriving them.
17
+ *
18
+ * Returns `{ href: undefined, target: '_self' }` when `currentTarget` is not an anchor
19
+ * element (e.g. a button or wrapper div), so callers can safely fall back to a default target.
20
+ *
21
+ * @param event - A React mouse or keyboard event whose `currentTarget` may be an anchor.
22
+ * @returns The resolved absolute `href` and `target` attribute of the anchor, or safe
23
+ * defaults if `currentTarget` is not an anchor.
24
+ */
25
+ export const getAnchorAttributesFromEvent = event => {
26
+ const currentTarget = event.currentTarget;
27
+ if (!(currentTarget instanceof HTMLAnchorElement)) {
28
+ return {
29
+ href: undefined,
30
+ target: '_self'
31
+ };
32
+ }
33
+ return {
34
+ href: currentTarget.href,
35
+ target: currentTarget.target || '_self'
36
+ };
8
37
  };
@@ -16,7 +16,7 @@ import { isSpecialClick, isSpecialEvent, isSpecialKey } from '../../utils';
16
16
  import { combineActionOptions } from '../../utils/actions/combine-action-options';
17
17
  import { fireLinkClickedEvent } from '../../utils/analytics/click';
18
18
  import { SmartLinkAnalyticsContext } from '../../utils/analytics/SmartLinkAnalyticsContext';
19
- import { isAuxClick } from '../../utils/click-helpers';
19
+ import { getAnchorAttributesFromEvent, isAuxClick } from '../../utils/click-helpers';
20
20
  import { isFlexibleUiCard } from '../../utils/flexible';
21
21
  import * as measure from '../../utils/performance';
22
22
  import { BlockCard } from '../BlockCard';
@@ -83,7 +83,7 @@ function Component({
83
83
  const services = getServices(state.details);
84
84
  const thirdPartyARI = getThirdPartyARI(state.details);
85
85
  const firstPartyIdentifier = getFirstPartyIdentifier();
86
- const wrapUrlWithCrossProductAnalytics = useSmartLinkCrossProductUrlWrapperGated({
86
+ const appendCrossProductAnalyticsParams = useSmartLinkCrossProductUrlWrapperGated({
87
87
  details: state.details
88
88
  });
89
89
  const actionOptions = combineActionOptions({
@@ -117,56 +117,121 @@ function Component({
117
117
  const isDisablePreviewPanel = disablePreviewPanel && editorExperiment('platform_editor_preview_panel_linking_exp', true, {
118
118
  exposure: true
119
119
  });
120
+ if (fg('platform_smartlink_xpc_url_wrapping')) {
121
+ var _appendCrossProductAn;
122
+ // FIXME: InlineCard, BlockCard and EmbedCard call event.preventDefault() internally
123
+ // before the event bubbles up to this handler. This forces us to snapshot
124
+ // event.defaultPrevented before calling onClick to detect whether the consumer
125
+ // specifically prevented navigation. Ideally those components should not call
126
+ // preventDefault so this workaround can be removed.
127
+ const isEventDefaultPrevented = event.defaultPrevented;
128
+ const canOpenPreviewPanel = !isModifierKeyPressed && ari && name && openPreviewPanel && (isPreviewPanelAvailable === null || isPreviewPanelAvailable === void 0 ? void 0 : isPreviewPanelAvailable({
129
+ ari
130
+ })) && !isDisablePreviewPanel;
120
131
 
121
- // If preview panel is available and the user clicked on the link,
122
- // delegate the click to the preview panel handler
123
- if (!isModifierKeyPressed && ari && name && openPreviewPanel && isPreviewPanelAvailable !== null && isPreviewPanelAvailable !== void 0 && isPreviewPanelAvailable({
124
- ari
125
- }) && !isDisablePreviewPanel) {
126
- var _extractSmartLinkEmbe;
127
- event.preventDefault();
128
- event.stopPropagation();
129
- openPreviewPanel({
130
- url,
131
- ari,
132
- name,
133
- iconUrl: getObjectIconUrl(state.details),
134
- panelData: {
135
- embedUrl: expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') ? (_extractSmartLinkEmbe = extractSmartLinkEmbed(state.details)) === null || _extractSmartLinkEmbe === void 0 ? void 0 : _extractSmartLinkEmbe.src : undefined
136
- }
137
- });
138
- fireLinkClickedEvent(createAnalyticsEvent)(event, {
139
- attributes: {
140
- clickOutcome: 'previewPanel'
141
- }
142
- });
143
- return;
144
- } else if (!onClick && !isFlexibleUi) {
145
- const clickUrl = getClickUrl(url, state.details);
132
+ // Preview panel takes priority over link navigation when available.
133
+ if (canOpenPreviewPanel) {
134
+ var _extractSmartLinkEmbe;
135
+ event.preventDefault();
136
+ event.stopPropagation();
137
+ openPreviewPanel({
138
+ url,
139
+ ari,
140
+ name,
141
+ iconUrl: getObjectIconUrl(state.details),
142
+ panelData: {
143
+ embedUrl: expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') ? (_extractSmartLinkEmbe = extractSmartLinkEmbed(state.details)) === null || _extractSmartLinkEmbe === void 0 ? void 0 : _extractSmartLinkEmbe.src : undefined
144
+ }
145
+ });
146
+ fireLinkClickedEvent(createAnalyticsEvent)(event, {
147
+ attributes: {
148
+ clickOutcome: 'previewPanel'
149
+ }
150
+ });
151
+ return;
152
+ }
146
153
 
147
- // Ctrl+left click on mac typically doesn't trigger onClick
148
- // The event could have potentially had `e.preventDefault()` called on it by now
149
- // event by smart card internally
150
- // If it has been called then only then can `isSpecialEvent` be true.
151
- const target = isSpecialEvent(event) ? '_blank' : '_self';
152
- if (fg('platform_smartlink_xpc_url_wrapping')) {
153
- const wrappedUrl = wrapUrlWithCrossProductAnalytics(clickUrl);
154
- window.open(wrappedUrl, target);
155
- } else {
156
- window.open(clickUrl, target);
154
+ // For FlexibleCard, read target from the clicked anchor element (e.g. _blank for links
155
+ // rendered with explicit target). For classic cards, default to _self
156
+ const {
157
+ target: anchorTarget
158
+ } = getAnchorAttributesFromEvent(event);
159
+ const target = isSpecialEvent(event) ? '_blank' : isFlexibleUi ? anchorTarget : '_self';
160
+
161
+ // FIXME: preferredUrl should be rendered in the DOM anchor href instead of derived at click time
162
+ const preferredUrl = getClickUrl(url, state.details);
163
+ const destinationUrl = (_appendCrossProductAn = appendCrossProductAnalyticsParams(preferredUrl)) !== null && _appendCrossProductAn !== void 0 ? _appendCrossProductAn : preferredUrl;
164
+
165
+ // FIXME: Consumer that handle click even themselves via callback won't have the decorated URL
166
+ onClick === null || onClick === void 0 ? void 0 : onClick(event);
167
+
168
+ // Check if the event is prevented via onClick callback
169
+ const consumerPreventedNavigation = event.defaultPrevented && !isEventDefaultPrevented;
170
+
171
+ // Classic cards (InlineCard, BlockCard, EmbedCard) rely on their own anchor navigation
172
+ // when onClick is provided, so this handler should not open the link for them.
173
+ // FlexibleCard's anchor is prevented from native navigation, so this handler always
174
+ // opens the link for FlexibleCard unless the consumer's onClick called preventDefault.
175
+ const shouldOpenLink = isFlexibleUi || !onClick;
176
+ const doOpenLink = shouldOpenLink && !consumerPreventedNavigation;
177
+ if (doOpenLink) {
178
+ event.preventDefault();
179
+ window.open(destinationUrl, target);
157
180
  }
158
- fireLinkClickedEvent(createAnalyticsEvent)(event, {
181
+
182
+ // Only set clickOutcome when this handler actually opened the link.
183
+ // If a parent onClick handled navigation, fire a generic click event instead.
184
+ fireLinkClickedEvent(createAnalyticsEvent)(event, doOpenLink ? {
159
185
  attributes: {
160
186
  clickOutcome: target === '_blank' ? 'clickThroughNewTabOrWindow' : 'clickThrough'
161
187
  }
162
- });
188
+ } : undefined);
163
189
  } else {
164
- if (onClick) {
165
- onClick(event);
190
+ // If preview panel is available and the user clicked on the link,
191
+ // delegate the click to the preview panel handler
192
+ if (!isModifierKeyPressed && ari && name && openPreviewPanel && isPreviewPanelAvailable !== null && isPreviewPanelAvailable !== void 0 && isPreviewPanelAvailable({
193
+ ari
194
+ }) && !isDisablePreviewPanel) {
195
+ var _extractSmartLinkEmbe2;
196
+ event.preventDefault();
197
+ event.stopPropagation();
198
+ openPreviewPanel({
199
+ url,
200
+ ari,
201
+ name,
202
+ iconUrl: getObjectIconUrl(state.details),
203
+ panelData: {
204
+ embedUrl: expValEquals('platform_hover_card_preview_panel', 'cohort', 'test') ? (_extractSmartLinkEmbe2 = extractSmartLinkEmbed(state.details)) === null || _extractSmartLinkEmbe2 === void 0 ? void 0 : _extractSmartLinkEmbe2.src : undefined
205
+ }
206
+ });
207
+ fireLinkClickedEvent(createAnalyticsEvent)(event, {
208
+ attributes: {
209
+ clickOutcome: 'previewPanel'
210
+ }
211
+ });
212
+ return;
213
+ } else if (!onClick && !isFlexibleUi) {
214
+ const clickUrl = getClickUrl(url, state.details);
215
+
216
+ // Ctrl+left click on mac typically doesn't trigger onClick
217
+ // The event could have potentially had `e.preventDefault()` called on it by now
218
+ // event by smart card internally
219
+ // If it has been called then only then can `isSpecialEvent` be true.
220
+ const target = isSpecialEvent(event) ? '_blank' : '_self';
221
+ window.open(clickUrl, target);
222
+ fireLinkClickedEvent(createAnalyticsEvent)(event, {
223
+ attributes: {
224
+ clickOutcome: target === '_blank' ? 'clickThroughNewTabOrWindow' : 'clickThrough'
225
+ }
226
+ });
227
+ } else {
228
+ if (onClick) {
229
+ onClick(event);
230
+ }
231
+ fireLinkClickedEvent(createAnalyticsEvent)(event);
166
232
  }
167
- fireLinkClickedEvent(createAnalyticsEvent)(event);
168
233
  }
169
- }, [fireEvent, id, isFlexibleUi, appearance, definitionId, onClick, url, wrapUrlWithCrossProductAnalytics, state.details, ari, name, fire3PClickEvent, shouldFire3PClickEvent, isPreviewPanelAvailable, openPreviewPanel, createAnalyticsEvent, disablePreviewPanel]);
234
+ }, [fireEvent, id, isFlexibleUi, appearance, definitionId, onClick, url, appendCrossProductAnalyticsParams, state.details, ari, name, fire3PClickEvent, shouldFire3PClickEvent, isPreviewPanelAvailable, openPreviewPanel, createAnalyticsEvent, disablePreviewPanel]);
170
235
 
171
236
  // Exposure fires once per eligible mount; click-time reads use no-exposure variant.
172
237
  useEffect(() => {
@@ -1,5 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
- import React, { useEffect, useMemo } from 'react';
2
+ import React, { useCallback, useEffect, useMemo } from 'react';
3
3
  import { useSmartLinkContext } from '@atlaskit/link-provider';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  import UFOHoldLoad from '@atlaskit/react-ufo/load-hold';
@@ -10,6 +10,7 @@ import { FlexibleCardContext } from '../../state/flexible-ui-context';
10
10
  import { useAISummaryConfig } from '../../state/hooks/use-ai-summary-config';
11
11
  import useResolve from '../../state/hooks/use-resolve';
12
12
  import useRovoConfig from '../../state/hooks/use-rovo-config';
13
+ import { useSmartLinkCrossProductUrlWrapperGated } from '../../state/hooks/use-smart-link-cross-product-url-wrapper';
13
14
  import Container from './components/container';
14
15
  import { getContextByStatus } from './utils';
15
16
  const PENDING_LINK_STATUSES = [SmartLinkStatus.Pending, SmartLinkStatus.Resolving];
@@ -60,6 +61,11 @@ const FlexibleCard = ({
60
61
  details
61
62
  } = cardState;
62
63
  const status = cardType;
64
+ const appendCrossProductAnalyticsParams = useSmartLinkCrossProductUrlWrapperGated({
65
+ details
66
+ });
67
+ const transformUrlCallback = useCallback((destinationUrl = url) => appendCrossProductAnalyticsParams(destinationUrl), [appendCrossProductAnalyticsParams, url]);
68
+ const transformUrl = fg('platform_smartlink_xpc_url_wrapping') ? transformUrlCallback : undefined;
63
69
 
64
70
  // if we have placeholder state it means we can internally use it
65
71
  // as temporary resolved data until the actual data comes back as one of the final statuses
@@ -86,8 +92,9 @@ const FlexibleCard = ({
86
92
  status: placeholderCardState ? placeHolderStatus : status,
87
93
  url,
88
94
  isPreviewPanelAvailable,
89
- openPreviewPanel
90
- }), [aiSummaryConfig, appearance, actionOptions, details, id, isPreviewPanelAvailable, onAuthorize, onClick, onAuxClick, onContextMenu, openPreviewPanel, origin, placeholderCardState, placeHolderStatus, product, renderers, resolve, rovoConfig, status, url, fireEvent]);
95
+ openPreviewPanel,
96
+ transformUrl
97
+ }), [aiSummaryConfig, appearance, actionOptions, details, id, isPreviewPanelAvailable, onAuthorize, onClick, onAuxClick, onContextMenu, openPreviewPanel, origin, placeholderCardState, placeHolderStatus, product, renderers, resolve, rovoConfig, status, transformUrl, url, fireEvent]);
91
98
  const flexibleCardContext = useMemo(() => ({
92
99
  data: context,
93
100
  status: placeHolderStatus !== null && placeHolderStatus !== void 0 ? placeHolderStatus : status,
@@ -11,10 +11,12 @@ import { CardDisplay, SmartLinkPosition, SmartLinkSize } from '../../../constant
11
11
  import extractRovoChatAction from '../../../extractors/flexible/actions/extract-rovo-chat-action';
12
12
  import { getDefinitionId, getExtensionKey, getServices } from '../../../state/helpers';
13
13
  import useRovoConfig from '../../../state/hooks/use-rovo-config';
14
+ import { useSmartLinkCrossProductUrlWrapperGated } from '../../../state/hooks/use-smart-link-cross-product-url-wrapper';
14
15
  import { useSmartCardState } from '../../../state/store';
15
16
  import { isSpecialEvent } from '../../../utils';
16
17
  import { getIsAISummaryEnabled } from '../../../utils/ai-summary';
17
18
  import { fireLinkClickedEvent } from '../../../utils/analytics/click';
19
+ import { getAnchorAttributesFromEvent } from '../../../utils/click-helpers';
18
20
  import { flexibleUiOptions } from '../styled';
19
21
  import { getMetadata } from '../utils';
20
22
  import ContentContainer from './ContentContainer';
@@ -86,6 +88,9 @@ const HoverCardContent = ({
86
88
  } = useSmartLinkContext();
87
89
  const isAISummaryEnabled = getIsAISummaryEnabled(isAdminHubAIEnabled, cardState.details);
88
90
  const services = getServices(linkState.details);
91
+ const appendCrossProductAnalyticsParams = useSmartLinkCrossProductUrlWrapperGated({
92
+ details: linkState.details
93
+ });
89
94
  const statusRef = useRef(linkStatus);
90
95
  const fireEventRef = useRef(fireEvent);
91
96
  const definitionIdRef = useRef(definitionId);
@@ -127,6 +132,24 @@ const HoverCardContent = ({
127
132
  };
128
133
  }, []);
129
134
  const onClick = useCallback(event => {
135
+ if (fg('platform_smartlink_xpc_url_wrapping')) {
136
+ // Prevent the anchor's native navigation so we can open the destination URL
137
+ // with cross-product analytics query parameters appended.
138
+ // The href is read from the anchor element at click time rather than at render
139
+ // time because the URL for the anchor may be different from original URL.
140
+ // The component may use the URL from the resolved response which can be a redirect URL
141
+ // or other preferred URL based on link extractor.
142
+ // The cross-product params are client-only and cannot be rendered
143
+ // server-side. Falls back to the original `url` prop if the event target is
144
+ // not an anchor element.
145
+ event.preventDefault();
146
+ const {
147
+ href = url,
148
+ target
149
+ } = getAnchorAttributesFromEvent(event);
150
+ const destinationUrl = appendCrossProductAnalyticsParams(href);
151
+ window.open(destinationUrl, target);
152
+ }
130
153
  const isModifierKeyPressed = isSpecialEvent(event);
131
154
  fireEvent('ui.smartLink.clicked.titleGoToLink', {
132
155
  id,
@@ -135,7 +158,7 @@ const HoverCardContent = ({
135
158
  definitionId: definitionId !== null && definitionId !== void 0 ? definitionId : null
136
159
  });
137
160
  fireLinkClickedEvent(createAnalyticsEvent)(event);
138
- }, [createAnalyticsEvent, id, fireEvent, definitionId]);
161
+ }, [createAnalyticsEvent, appendCrossProductAnalyticsParams, id, fireEvent, definitionId, url]);
139
162
  const data = (_cardState$details = cardState.details) === null || _cardState$details === void 0 ? void 0 : _cardState$details.data;
140
163
  const {
141
164
  subtitle
@@ -9,7 +9,7 @@ import LinkWarningModal from './LinkWarningModal';
9
9
  import { useLinkWarningModal } from './LinkWarningModal/hooks/use-link-warning-modal';
10
10
  const PACKAGE_DATA = {
11
11
  packageName: "@atlaskit/smart-card",
12
- packageVersion: "44.23.1",
12
+ packageVersion: "44.23.3",
13
13
  componentName: 'linkUrl'
14
14
  };
15
15
  const LinkUrl = ({
@@ -1,6 +1,7 @@
1
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
3
  import { extractLink } from '@atlaskit/link-extractors';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
4
5
  import { CardAction } from '../../constants';
5
6
  import { getDefinitionId, getExtensionKey, getResourceType } from '../../state/helpers';
6
7
  import { openUrl } from '../../utils';
@@ -9,6 +10,7 @@ import { getActionsFromJsonLd } from '../common/actions/extractActions';
9
10
  export var extractInvokeViewAction = function extractInvokeViewAction(_ref, force) {
10
11
  var actionOptions = _ref.actionOptions,
11
12
  appearance = _ref.appearance,
13
+ transformUrl = _ref.transformUrl,
12
14
  id = _ref.id,
13
15
  response = _ref.response;
14
16
  if (!canShowAction(CardAction.ViewAction, actionOptions)) {
@@ -26,11 +28,19 @@ export var extractInvokeViewAction = function extractInvokeViewAction(_ref, forc
26
28
  return {
27
29
  actionFn: function () {
28
30
  var _actionFn = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
31
+ var _transformUrl, destinationUrl;
29
32
  return _regeneratorRuntime.wrap(function (_context) {
30
33
  while (1) switch (_context.prev = _context.next) {
31
34
  case 0:
32
- return _context.abrupt("return", openUrl(url));
35
+ if (!fg('platform_smartlink_xpc_url_wrapping')) {
36
+ _context.next = 1;
37
+ break;
38
+ }
39
+ destinationUrl = (_transformUrl = transformUrl === null || transformUrl === void 0 ? void 0 : transformUrl(url)) !== null && _transformUrl !== void 0 ? _transformUrl : url;
40
+ return _context.abrupt("return", openUrl(destinationUrl));
33
41
  case 1:
42
+ return _context.abrupt("return", openUrl(url));
43
+ case 2:
34
44
  case "end":
35
45
  return _context.stop();
36
46
  }
@@ -21,6 +21,7 @@ export var extractFlexibleCardActions = function extractFlexibleCardActions(_ref
21
21
  product = _ref.product,
22
22
  response = _ref.response,
23
23
  rovoConfig = _ref.rovoConfig,
24
+ transformUrl = _ref.transformUrl,
24
25
  url = _ref.url,
25
26
  isPreviewPanelAvailable = _ref.isPreviewPanelAvailable,
26
27
  openPreviewPanel = _ref.openPreviewPanel;
@@ -34,7 +35,7 @@ export var extractFlexibleCardActions = function extractFlexibleCardActions(_ref
34
35
  appearance: appearance,
35
36
  id: id,
36
37
  response: response
37
- })), ActionName.FollowAction, extractFollowAction(response, actionOptions, id)), ActionName.PreviewAction, extractPreviewClientAction({
38
+ })), ActionName.FollowAction, extractFollowAction(response, actionOptions, id)), ActionName.PreviewAction, extractPreviewClientAction(_objectSpread({
38
39
  actionOptions: actionOptions,
39
40
  appearance: appearance,
40
41
  fireEvent: fireEvent,
@@ -43,7 +44,9 @@ export var extractFlexibleCardActions = function extractFlexibleCardActions(_ref
43
44
  response: response,
44
45
  isPreviewPanelAvailable: isPreviewPanelAvailable,
45
46
  openPreviewPanel: openPreviewPanel
46
- })), ActionName.AutomationAction, extractAutomationAction(response)), InternalActionName.AISummaryAction, extractAISummaryAction(response, url, actionOptions, aiSummaryConfig)), fg('platform_sl_3p_auth_rovo_action_kill_switch') ? _defineProperty({}, ActionName.RovoChatAction, extractRovoChatAction({
47
+ }, fg('platform_smartlink_xpc_url_wrapping') ? {
48
+ transformUrl: transformUrl
49
+ } : undefined))), ActionName.AutomationAction, extractAutomationAction(response)), InternalActionName.AISummaryAction, extractAISummaryAction(response, url, actionOptions, aiSummaryConfig)), fg('platform_sl_3p_auth_rovo_action_kill_switch') ? _defineProperty({}, ActionName.RovoChatAction, extractRovoChatAction({
47
50
  actionOptions: actionOptions,
48
51
  appearance: appearance,
49
52
  id: id,
@@ -2,6 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
3
3
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
4
4
  import { extractLink } from '@atlaskit/link-extractors';
5
+ import { fg } from '@atlaskit/platform-feature-flags';
5
6
  import { CardAction } from '../../constants';
6
7
  import { getExtensionKey } from '../../state/helpers';
7
8
  import { canShowAction } from '../../utils/actions/can-show-action';
@@ -21,7 +22,7 @@ var toInvokeRequest = function toInvokeRequest(extensionKey, resourceIdentifiers
21
22
  details: details
22
23
  };
23
24
  };
24
- var extractAction = function extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel) {
25
+ var extractAction = function extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, transformUrl) {
25
26
  var _action$dataRetrieval, _extractInvokePreview, _action$dataUpdateAct;
26
27
  var extensionKey = getExtensionKey(response);
27
28
  var data = response === null || response === void 0 ? void 0 : response.data;
@@ -37,7 +38,7 @@ var extractAction = function extractAction(response, id, actionOptions, appearan
37
38
  }
38
39
  var read = toInvokeRequest(extensionKey, action.resourceIdentifiers, (_action$dataRetrieval = action.dataRetrievalAction) === null || _action$dataRetrieval === void 0 ? void 0 : _action$dataRetrieval.name);
39
40
  var url = extractLink(data);
40
- var invokePreviewAction = response ? (_extractInvokePreview = extractInvokePreviewAction({
41
+ var invokePreviewAction = response ? (_extractInvokePreview = extractInvokePreviewAction(_objectSpread({
41
42
  actionOptions: actionOptions,
42
43
  appearance: appearance,
43
44
  fireEvent: fireEvent,
@@ -49,7 +50,9 @@ var extractAction = function extractAction(response, id, actionOptions, appearan
49
50
  response: response,
50
51
  isPreviewPanelAvailable: isPreviewPanelAvailable,
51
52
  openPreviewPanel: openPreviewPanel
52
- })) === null || _extractInvokePreview === void 0 ? void 0 : _extractInvokePreview.invokeAction : undefined;
53
+ }, fg('platform_smartlink_xpc_url_wrapping') ? {
54
+ transformUrl: transformUrl
55
+ } : undefined))) === null || _extractInvokePreview === void 0 ? void 0 : _extractInvokePreview.invokeAction : undefined;
53
56
  var details = {
54
57
  id: id,
55
58
  url: url,
@@ -61,7 +64,7 @@ var extractAction = function extractAction(response, id, actionOptions, appearan
61
64
  update: update
62
65
  } : undefined;
63
66
  };
64
- var extractState = function extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel) {
67
+ var extractState = function extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, transformUrl) {
65
68
  if (!response || !response.data) {
66
69
  return;
67
70
  }
@@ -72,7 +75,7 @@ var extractState = function extractState(response, actionOptions, id, appearance
72
75
  if (!canShowAction(CardAction.ChangeStatusAction, actionOptions)) {
73
76
  return lozenge;
74
77
  }
75
- var action = extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel);
78
+ var action = extractAction(response, id, actionOptions, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, fg('platform_smartlink_xpc_url_wrapping') ? transformUrl : undefined);
76
79
  return _objectSpread(_objectSpread({}, lozenge), {}, {
77
80
  action: action
78
81
  });
@@ -1,6 +1,6 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
- var _excluded = ["appearance", "fireEvent", "id", "onClick", "onAuxClick", "onContextMenu", "origin", "product", "resolve", "rovoConfig", "actionOptions", "response", "status", "aiSummaryConfig", "isPreviewPanelAvailable", "openPreviewPanel"];
3
+ var _excluded = ["appearance", "fireEvent", "id", "onClick", "onAuxClick", "onContextMenu", "origin", "product", "resolve", "rovoConfig", "actionOptions", "response", "status", "aiSummaryConfig", "isPreviewPanelAvailable", "openPreviewPanel", "transformUrl"];
4
4
  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; }
5
5
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
6
  import { extractPersonOwnedBy, extractSmartLinkAri, extractSmartLinkAuthorGroup, extractSmartLinkCreatedBy, extractSmartLinkCreatedOn, extractSmartLinkModifiedBy, extractSmartLinkModifiedOn, extractSmartLinkUrl, extractType } from '@atlaskit/link-extractors';
@@ -34,6 +34,7 @@ var extractFlexibleUiContext = function extractFlexibleUiContext() {
34
34
  aiSummaryConfig = _ref.aiSummaryConfig,
35
35
  isPreviewPanelAvailable = _ref.isPreviewPanelAvailable,
36
36
  openPreviewPanel = _ref.openPreviewPanel,
37
+ transformUrl = _ref.transformUrl,
37
38
  props = _objectWithoutProperties(_ref, _excluded);
38
39
  if (!response) {
39
40
  return undefined;
@@ -58,7 +59,9 @@ var extractFlexibleUiContext = function extractFlexibleUiContext() {
58
59
  // Use the original URL in edge cases, such as short links for AI summary and copy link actions.
59
60
  isPreviewPanelAvailable: isPreviewPanelAvailable,
60
61
  openPreviewPanel: openPreviewPanel
61
- })),
62
+ }, fg('platform_smartlink_xpc_url_wrapping') ? {
63
+ transformUrl: transformUrl
64
+ } : undefined)),
62
65
  appliedToComponentsCount: extractAppliedToComponentsCount(data),
63
66
  assignedToGroup: extractPersonAssignedToAsArray(data),
64
67
  attachmentCount: extractAttachmentCount(data),
@@ -90,7 +93,7 @@ var extractFlexibleUiContext = function extractFlexibleUiContext() {
90
93
  snippet: extractSmartLinkSummary(response) || undefined,
91
94
  // Explicitly set here to remove an empty string
92
95
  sourceBranch: extractSourceBranch(data),
93
- state: extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel),
96
+ state: extractState(response, actionOptions, id, appearance, origin, fireEvent, resolve, isPreviewPanelAvailable, openPreviewPanel, fg('platform_smartlink_xpc_url_wrapping') ? transformUrl : undefined),
94
97
  subscriberCount: extractSubscriberCount(data),
95
98
  subTasksProgress: extractSubTasksProgress(data),
96
99
  storyPoints: extractStoryPoints(data),