@atlaskit/smart-card 20.0.4 → 20.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/cjs/state/actions/constants.js +4 -2
  3. package/dist/cjs/state/actions/index.js +51 -19
  4. package/dist/cjs/state/analytics/useSmartLinkAnalytics.js +26 -6
  5. package/dist/cjs/version.json +1 -1
  6. package/dist/cjs/view/BlockCard/index.js +29 -0
  7. package/dist/cjs/view/CardWithUrl/component.js +7 -2
  8. package/dist/cjs/view/CardWithUrl/loader.js +7 -9
  9. package/dist/cjs/view/EmbedCard/index.js +29 -0
  10. package/dist/cjs/view/FlexibleCard/components/blocks/element-group/index.js +2 -2
  11. package/dist/cjs/view/FlexibleCard/index.js +34 -7
  12. package/dist/cjs/view/HoverCard/components/HoverCardComponent.js +143 -0
  13. package/dist/cjs/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +29 -25
  14. package/dist/cjs/view/HoverCard/index.js +25 -126
  15. package/dist/cjs/view/HoverCard/styled.js +11 -1
  16. package/dist/cjs/view/HoverCard/utils.js +3 -3
  17. package/dist/cjs/view/InlineCard/index.js +29 -0
  18. package/dist/es2019/state/actions/constants.js +2 -1
  19. package/dist/es2019/state/actions/index.js +46 -18
  20. package/dist/es2019/state/analytics/useSmartLinkAnalytics.js +24 -7
  21. package/dist/es2019/version.json +1 -1
  22. package/dist/es2019/view/BlockCard/index.js +29 -0
  23. package/dist/es2019/view/CardWithUrl/component.js +7 -2
  24. package/dist/es2019/view/CardWithUrl/loader.js +8 -10
  25. package/dist/es2019/view/EmbedCard/index.js +29 -0
  26. package/dist/es2019/view/FlexibleCard/components/blocks/element-group/index.js +4 -0
  27. package/dist/es2019/view/FlexibleCard/index.js +32 -7
  28. package/dist/es2019/view/HoverCard/components/HoverCardComponent.js +102 -0
  29. package/dist/es2019/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +18 -21
  30. package/dist/es2019/view/HoverCard/index.js +26 -97
  31. package/dist/es2019/view/HoverCard/styled.js +8 -0
  32. package/dist/es2019/view/HoverCard/utils.js +1 -1
  33. package/dist/es2019/view/InlineCard/index.js +29 -0
  34. package/dist/esm/state/actions/constants.js +2 -1
  35. package/dist/esm/state/actions/index.js +51 -20
  36. package/dist/esm/state/analytics/useSmartLinkAnalytics.js +27 -7
  37. package/dist/esm/version.json +1 -1
  38. package/dist/esm/view/BlockCard/index.js +29 -0
  39. package/dist/esm/view/CardWithUrl/component.js +7 -2
  40. package/dist/esm/view/CardWithUrl/loader.js +8 -10
  41. package/dist/esm/view/EmbedCard/index.js +29 -0
  42. package/dist/esm/view/FlexibleCard/components/blocks/element-group/index.js +2 -2
  43. package/dist/esm/view/FlexibleCard/index.js +33 -7
  44. package/dist/esm/view/HoverCard/components/HoverCardComponent.js +114 -0
  45. package/dist/esm/view/HoverCard/components/{hover-card-content.js → HoverCardContent.js} +20 -22
  46. package/dist/esm/view/HoverCard/index.js +24 -109
  47. package/dist/esm/view/HoverCard/styled.js +8 -0
  48. package/dist/esm/view/HoverCard/utils.js +1 -1
  49. package/dist/esm/view/InlineCard/index.js +29 -0
  50. package/dist/types/state/actions/constants.d.ts +1 -0
  51. package/dist/types/state/actions/index.d.ts +1 -0
  52. package/dist/types/state/analytics/useSmartLinkAnalytics.d.ts +9 -0
  53. package/dist/types/state/hooks/useSmartLink.d.ts +3 -0
  54. package/dist/types/view/BlockCard/types.d.ts +2 -0
  55. package/dist/types/view/Card/index.d.ts +1 -1
  56. package/dist/types/view/Card/types.d.ts +2 -1
  57. package/dist/types/view/CardWithUrl/component.d.ts +1 -1
  58. package/dist/types/view/CardWithUrl/types.d.ts +2 -1
  59. package/dist/types/view/EmbedCard/components/styled.d.ts +16 -16
  60. package/dist/types/view/EmbedCard/types.d.ts +2 -0
  61. package/dist/types/view/FlexibleCard/types.d.ts +9 -4
  62. package/dist/types/view/HoverCard/components/HoverCardComponent.d.ts +4 -0
  63. package/dist/types/view/HoverCard/components/HoverCardContent.d.ts +9 -0
  64. package/dist/types/view/HoverCard/index.d.ts +2 -4
  65. package/dist/types/view/HoverCard/styled.d.ts +2 -0
  66. package/dist/types/view/HoverCard/types.d.ts +8 -0
  67. package/dist/types/view/HoverCard/utils.d.ts +1 -1
  68. package/dist/types/view/InlineCard/Frame/styled.d.ts +1 -1
  69. package/dist/types/view/InlineCard/Icon.d.ts +3 -3
  70. package/dist/types/view/InlineCard/IconAndTitleLayout/styled.d.ts +9 -9
  71. package/dist/types/view/InlineCard/ResolvingView/styled.d.ts +13 -5
  72. package/dist/types/view/InlineCard/styled.d.ts +8 -4
  73. package/dist/types/view/InlineCard/types.d.ts +2 -1
  74. package/dist/types/view/types.d.ts +6 -0
  75. package/package.json +4 -4
  76. package/dist/types/view/HoverCard/components/hover-card-content.d.ts +0 -4
@@ -1,24 +1,17 @@
1
+ import ShortcutIcon from '@atlaskit/icon/glyph/shortcut';
2
+ import { extractType } from '@atlaskit/linking-common/extractors';
1
3
  import React, { useCallback, useEffect, useMemo } from 'react';
2
- import { extractMetadata } from '../../../extractors/hover/extractMetadata';
3
- import { getSimulatedMetadata, HOVER_CARD_ANALYTICS_DISPLAY } from '../utils';
4
+ import { ActionName, SmartLinkPosition, SmartLinkSize } from '../../../constants';
4
5
  import extractPreview from '../../../extractors/flexible/extract-preview';
5
- import { ActionName, SmartLinkPosition, SmartLinkSize, SmartLinkTheme } from '../../../constants';
6
+ import { extractMetadata } from '../../../extractors/hover/extractMetadata';
7
+ import { useSmartCardActions } from '../../../state/actions';
8
+ import { getExtensionKey } from '../../../state/helpers';
9
+ import { isSpecialEvent } from '../../../utils';
6
10
  import FlexibleCard from '../../FlexibleCard';
7
11
  import { FooterBlock, MetadataBlock, PreviewBlock, SnippetBlock, TitleBlock } from '../../FlexibleCard/components/blocks';
8
- import { isSpecialEvent } from '../../../utils';
9
- import ShortcutIcon from '@atlaskit/icon/glyph/shortcut';
10
- import { getExtensionKey } from '../../../state/helpers';
11
- import { titleBlockCss, metadataBlockCss, footerBlockCss } from '../styled';
12
- import { extractType } from '@atlaskit/linking-common/extractors';
13
- const flexibleUiOptions = {
14
- hideElevation: true,
15
- size: SmartLinkSize.Medium,
16
- // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
17
- theme: SmartLinkTheme.Black,
18
- hidePadding: true
19
- };
20
-
21
- const getOpenAction = (url, analytics, onActionClick) => ({
12
+ import { flexibleUiOptions, footerBlockCss, metadataBlockCss, titleBlockCss } from '../styled';
13
+ import { getSimulatedMetadata, SMART_CARD_ANALYTICS_DISPLAY } from '../utils';
14
+ export const getOpenAction = (url, analytics, onActionClick) => ({
22
15
  name: ActionName.CustomAction,
23
16
  icon: /*#__PURE__*/React.createElement(ShortcutIcon, {
24
17
  label: "open in new tab",
@@ -35,8 +28,7 @@ const getOpenAction = (url, analytics, onActionClick) => ({
35
28
  },
36
29
  testId: 'hover-card-open-button'
37
30
  });
38
-
39
- const toFooterActions = (cardActions, onActionClick) => cardActions.map(action => ({
31
+ export const toFooterActions = (cardActions, onActionClick) => cardActions.map(action => ({
40
32
  content: action.text,
41
33
  name: ActionName.CustomAction,
42
34
  onClick: () => {
@@ -50,6 +42,7 @@ const toFooterActions = (cardActions, onActionClick) => cardActions.map(action =
50
42
  }));
51
43
 
52
44
  const HoverCardContent = ({
45
+ id = '',
53
46
  analytics,
54
47
  cardActions = [],
55
48
  cardState,
@@ -60,15 +53,19 @@ const HoverCardContent = ({
60
53
  }) => {
61
54
  var _cardState$details;
62
55
 
56
+ const actions = useSmartCardActions(id, url, analytics);
57
+ useEffect(() => {
58
+ actions.loadMetadata();
59
+ }, [actions]);
63
60
  useEffect(() => {
64
61
  // Since hover preview only render on resolved status,
65
62
  // there is no need to check for statuses.
66
- analytics.ui.renderSuccessEvent(HOVER_CARD_ANALYTICS_DISPLAY, cardState.status);
63
+ analytics.ui.renderSuccessEvent(SMART_CARD_ANALYTICS_DISPLAY, cardState.status);
67
64
  }, [analytics.ui, cardState.status]);
68
65
  const extensionKey = useMemo(() => getExtensionKey(cardState.details), [cardState.details]);
69
66
  const onClick = useCallback(event => {
70
67
  const isModifierKeyPressed = isSpecialEvent(event);
71
- analytics.ui.cardClickedEvent(undefined, HOVER_CARD_ANALYTICS_DISPLAY, cardState.status, undefined, undefined, isModifierKeyPressed, undefined, undefined, undefined, 'titleGoToLink');
68
+ analytics.ui.cardClickedEvent(undefined, SMART_CARD_ANALYTICS_DISPLAY, cardState.status, undefined, undefined, isModifierKeyPressed, undefined, undefined, undefined, 'titleGoToLink');
72
69
  }, [cardState.status, analytics.ui]);
73
70
  const titleActions = useMemo(() => [getOpenAction(url, analytics, onActionClick)], [url, analytics, onActionClick]);
74
71
  const footerActions = useMemo(() => toFooterActions(cardActions, onActionClick), [cardActions, onActionClick]);
@@ -1,109 +1,38 @@
1
1
  import _extends from "@babel/runtime/helpers/extends";
2
2
 
3
3
  /** @jsx jsx */
4
- import { jsx } from '@emotion/core';
5
- import React, { useCallback, useRef } from 'react';
6
- import Popup from '@atlaskit/popup';
7
- import { useSmartLinkActions } from '../../state/hooks-external/useSmartLinkActions';
8
- import { HoverCardContainer } from './styled';
9
4
  import { withAnalyticsContext, withAnalyticsEvents } from '@atlaskit/analytics-next';
10
- import { fireSmartLinkEvent } from '../../utils/analytics';
11
- import { useSmartCardState as useLinkState } from '../../state/store';
5
+ import { jsx } from '@emotion/core';
6
+ import { useCallback } from 'react';
7
+ import { ErrorBoundary } from 'react-error-boundary';
12
8
  import { useSmartLinkAnalytics } from '../../state/analytics';
13
- import { useSmartLinkRenderers } from '../../state/renderers';
14
- import HoverCardContent from './components/hover-card-content';
15
- import { HOVER_CARD_ANALYTICS_DISPLAY } from './utils';
16
- export const hoverCardClassName = 'smart-links-hover-preview';
17
- export const HoverCardComponent = ({
18
- children,
19
- id,
20
- url,
21
- createAnalyticsEvent
22
- }) => {
9
+ import { fireSmartLinkEvent } from '../../utils/analytics';
10
+ import { HoverCardComponent } from './components/HoverCardComponent';
11
+ import { SMART_CARD_ANALYTICS_DISPLAY } from './utils';
12
+
13
+ const HoverCardWithErrorBoundary = props => {
14
+ const {
15
+ url,
16
+ id,
17
+ children,
18
+ createAnalyticsEvent
19
+ } = props;
23
20
  const analyticsHandler = useCallback(analyticsPayload => {
24
21
  fireSmartLinkEvent(analyticsPayload, createAnalyticsEvent);
25
22
  }, [createAnalyticsEvent]);
26
- const delay = 300;
27
- const [isOpen, setIsOpen] = React.useState(false);
28
- const fadeOutTimeoutId = useRef();
29
- const fadeInTimeoutId = useRef();
30
- const cardOpenTime = useRef();
31
- const renderers = useSmartLinkRenderers();
32
- const linkState = useLinkState(url);
33
23
  const analytics = useSmartLinkAnalytics(url, analyticsHandler, id);
34
- const hideCard = useCallback(() => {
35
- //Check its previously open to avoid firing events when moving between the child and hover card components
36
- if (isOpen === true && cardOpenTime.current) {
37
- const hoverTime = Date.now() - cardOpenTime.current;
38
- analytics.ui.hoverCardDismissedEvent('card', hoverTime, 'mouse_hover');
39
- }
40
-
41
- setIsOpen(false);
42
- }, [analytics.ui, isOpen]);
43
- const initHideCard = useCallback(() => {
44
- if (fadeInTimeoutId.current) {
45
- clearTimeout(fadeInTimeoutId.current);
46
- }
47
-
48
- fadeOutTimeoutId.current = setTimeout(() => hideCard(), delay);
49
- }, [hideCard]);
50
- const initShowCard = useCallback(() => {
51
- if (fadeOutTimeoutId.current) {
52
- clearTimeout(fadeOutTimeoutId.current);
53
- }
54
-
55
- fadeInTimeoutId.current = setTimeout(() => {
56
- //Check if its previously closed to avoid firing events when moving between the child and hover card components
57
- if (isOpen === false) {
58
- cardOpenTime.current = Date.now();
59
- analytics.ui.hoverCardViewedEvent('card', 'mouse_hover');
60
- }
61
-
62
- setIsOpen(true);
63
- }, delay);
64
- }, [delay, isOpen, analytics.ui]);
65
- const linkActions = useSmartLinkActions({
66
- url,
67
- appearance: HOVER_CARD_ANALYTICS_DISPLAY,
68
- analyticsHandler
69
- });
70
- const onActionClick = useCallback(actionId => {
71
- if (actionId === 'preview-content') {
72
- hideCard();
73
- }
74
- }, [hideCard]); // Stop hover preview content to propagate event to parent.
75
-
76
- const onClick = useCallback(e => e.stopPropagation(), []);
77
- return jsx(Popup, {
78
- testId: "hover-card",
79
- isOpen: isOpen,
80
- onClose: hideCard,
81
- placement: "bottom-start",
82
- content: ({
83
- update
84
- }) => jsx("div", {
85
- onMouseEnter: initShowCard,
86
- onMouseLeave: initHideCard,
87
- className: hoverCardClassName,
88
- css: HoverCardContainer,
89
- onClick: onClick
90
- }, jsx(HoverCardContent, {
91
- analytics: analytics,
92
- cardActions: linkActions,
93
- cardState: linkState,
94
- onActionClick: onActionClick,
95
- onResolve: update,
96
- renderers: renderers,
97
- url: url
98
- })),
99
- trigger: triggerProps => jsx("span", _extends({}, triggerProps, {
100
- onMouseEnter: initShowCard,
101
- onMouseLeave: initHideCard
102
- }), children),
103
- zIndex: 511 // Temporary fix for Confluence inline comment on editor mod has z-index of 500, Jira issue view has z-index of 510
104
-
105
- });
24
+ const onError = useCallback((error, info) => {
25
+ analytics.ui.renderFailedEvent(SMART_CARD_ANALYTICS_DISPLAY, id, error, info);
26
+ }, [analytics.ui, id]);
27
+ return jsx(ErrorBoundary, {
28
+ fallback: children,
29
+ onError: onError
30
+ }, jsx(HoverCardComponent, _extends({}, props, {
31
+ analyticsHandler: analyticsHandler,
32
+ analytics: analytics
33
+ }), children));
106
34
  };
35
+
107
36
  export const HoverCard = withAnalyticsContext({
108
37
  source: 'smartLinkPreviewHoverCard'
109
- })(withAnalyticsEvents()(HoverCardComponent));
38
+ })(withAnalyticsEvents()(HoverCardWithErrorBoundary));
@@ -1,5 +1,13 @@
1
1
  import { css } from '@emotion/core';
2
2
  import { loadingPlaceholderClassName } from '../../index';
3
+ import { SmartLinkSize, SmartLinkTheme } from '../../constants';
4
+ export const flexibleUiOptions = {
5
+ hideElevation: true,
6
+ size: SmartLinkSize.Medium,
7
+ // eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage
8
+ theme: SmartLinkTheme.Black,
9
+ hidePadding: true
10
+ };
3
11
  const blockGap = '0.5rem';
4
12
  const elementGap = '0.5rem';
5
13
  const separatorCss = css`
@@ -1,5 +1,5 @@
1
1
  import { ElementName } from '../../constants';
2
- export const HOVER_CARD_ANALYTICS_DISPLAY = 'flexible';
2
+ export const SMART_CARD_ANALYTICS_DISPLAY = 'flexible';
3
3
  export const getSimulatedMetadata = (extensionKey = '', types = []) => {
4
4
  switch (extensionKey) {
5
5
  case 'bitbucket-object-provider':
@@ -20,6 +20,7 @@ export const InlineCard = ({
20
20
  isSelected,
21
21
  renderers,
22
22
  onResolve,
23
+ onError,
23
24
  testId,
24
25
  inlinePreloaderStyle,
25
26
  showHoverPreview
@@ -64,6 +65,13 @@ export const InlineCard = ({
64
65
  }));
65
66
 
66
67
  case 'unauthorized':
68
+ if (onError) {
69
+ onError({
70
+ url,
71
+ status
72
+ });
73
+ }
74
+
67
75
  const provider = extractProvider(cardDetails);
68
76
  return /*#__PURE__*/React.createElement(InlineCardUnauthorizedView, {
69
77
  icon: provider && provider.icon,
@@ -75,6 +83,13 @@ export const InlineCard = ({
75
83
  });
76
84
 
77
85
  case 'forbidden':
86
+ if (onError) {
87
+ onError({
88
+ url,
89
+ status
90
+ });
91
+ }
92
+
78
93
  const providerForbidden = extractProvider(cardDetails);
79
94
  const cardMetadata = (_details$meta = details === null || details === void 0 ? void 0 : details.meta) !== null && _details$meta !== void 0 ? _details$meta : getUnauthorizedJsonLd().meta;
80
95
  const requestAccessContext = extractRequestAccessContext({
@@ -94,6 +109,13 @@ export const InlineCard = ({
94
109
  });
95
110
 
96
111
  case 'not_found':
112
+ if (onError) {
113
+ onError({
114
+ url,
115
+ status
116
+ });
117
+ }
118
+
97
119
  const providerNotFound = extractProvider(cardDetails);
98
120
  return /*#__PURE__*/React.createElement(InlineCardErroredView, {
99
121
  url: url,
@@ -106,6 +128,13 @@ export const InlineCard = ({
106
128
 
107
129
  case 'fallback':
108
130
  case 'errored':
131
+ if (onError) {
132
+ onError({
133
+ url,
134
+ status
135
+ });
136
+ }
137
+
109
138
  return /*#__PURE__*/React.createElement(CardLinkView, {
110
139
  link: url,
111
140
  isSelected: isSelected,
@@ -2,4 +2,5 @@ export var ANALYTICS_RESOLVING = 'resolving';
2
2
  export var ANALYTICS_ERROR = 'errored';
3
3
  export var ANALYTICS_FALLBACK = 'fallback';
4
4
  export var ERROR_MESSAGE_OAUTH = 'Provider.authFlow is not set to OAuth2.';
5
- export var ERROR_MESSAGE_FATAL = 'Fatal error resolving URL';
5
+ export var ERROR_MESSAGE_FATAL = 'Fatal error resolving URL';
6
+ export var ERROR_MESSAGE_METADATA = 'Error resolving metadata.';
@@ -2,11 +2,10 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _regeneratorRuntime from "@babel/runtime/regenerator";
3
3
  import { useMemo, useCallback } from 'react';
4
4
  import { auth } from '@atlaskit/outbound-auth-flow-client';
5
- import { APIError } from '@atlaskit/linking-common';
6
5
  import { useSmartLinkContext } from '@atlaskit/link-provider';
7
- import { ACTION_PENDING, ACTION_RESOLVED, ACTION_ERROR, ACTION_ERROR_FALLBACK, ACTION_RELOADING, cardAction } from '@atlaskit/linking-common';
6
+ import { ACTION_PENDING, ACTION_RESOLVED, ACTION_ERROR, ACTION_ERROR_FALLBACK, ACTION_RELOADING, ACTION_UPDATE_METADATA_STATUS, cardAction, APIError } from '@atlaskit/linking-common';
8
7
  import { getDefinitionId, getByDefinitionId, getServices, getStatus, getExtensionKey } from '../helpers';
9
- import { ERROR_MESSAGE_OAUTH, ERROR_MESSAGE_FATAL } from './constants';
8
+ import { ERROR_MESSAGE_OAUTH, ERROR_MESSAGE_FATAL, ERROR_MESSAGE_METADATA } from './constants';
10
9
  import * as measure from '../../utils/performance';
11
10
  import { getUnauthorizedJsonLd } from '../../utils/jsonld';
12
11
  import { addMetadataToExperience } from '../analytics';
@@ -24,11 +23,23 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
24
23
  details: undefined
25
24
  },
26
25
  details = _ref.details,
27
- status = _ref.status;
26
+ status = _ref.status,
27
+ metadataStatus = _ref.metadataStatus;
28
28
 
29
29
  var hasAuthFlowSupported = config.authFlow !== 'disabled';
30
30
  var hasData = !!(details && details.data);
31
- var handleResolvedLinkError = useCallback(function (url, error, response) {
31
+ var setMetadataStatus = useCallback(function (metadataStatus) {
32
+ dispatch(cardAction(ACTION_UPDATE_METADATA_STATUS, {
33
+ url: url
34
+ }, undefined, undefined, metadataStatus));
35
+ }, [dispatch, url]);
36
+ var handleResolvedLinkError = useCallback(function (url, error, response, isMetadataRequest) {
37
+ // If metadata request then set metadata status, return and do not alter link status
38
+ if (isMetadataRequest) {
39
+ setMetadataStatus('errored');
40
+ return;
41
+ }
42
+
32
43
  if (error.kind === 'fatal') {
33
44
  // If there's no previous data in the store for this URL, then bail
34
45
  // out and let the editor handle fallbacks (returns to a blue link).
@@ -65,25 +76,34 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
65
76
  }, undefined, error));
66
77
  }
67
78
  }
68
- }, [hasData, status, dispatch, details]);
79
+ }, [setMetadataStatus, hasData, status, dispatch, details]);
69
80
  var handleResolvedLinkResponse = useCallback(function (resourceUrl, response) {
70
81
  var isReloading = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
82
+ var isMetadataRequest = arguments.length > 3 ? arguments[3] : undefined;
71
83
  var hostname = new URL(resourceUrl).hostname;
72
84
  var nextStatus = response ? getStatus(response) : 'fatal'; // If we require authorization & do not have an authFlow available,
73
85
  // throw an error and render as a normal blue link.
74
86
 
75
87
  if ((nextStatus === 'unauthorized' || nextStatus === 'forbidden') && !hasAuthFlowSupported) {
76
- handleResolvedLinkError(resourceUrl, new APIError('fallback', hostname, ERROR_MESSAGE_OAUTH), response);
88
+ handleResolvedLinkError(resourceUrl, new APIError('fallback', hostname, ERROR_MESSAGE_OAUTH), response, isMetadataRequest);
77
89
  return;
78
90
  } // Handle any other errors
79
91
 
80
92
 
81
93
  if (nextStatus === 'fatal') {
82
- handleResolvedLinkError(resourceUrl, new APIError('fatal', hostname, ERROR_MESSAGE_FATAL));
94
+ handleResolvedLinkError(resourceUrl, new APIError('fatal', hostname, ERROR_MESSAGE_FATAL), undefined, isMetadataRequest);
95
+ return;
96
+ } // Handle errors of any other kind in the metadata request response
97
+
98
+
99
+ if (isMetadataRequest && nextStatus !== 'resolved') {
100
+ handleResolvedLinkError(resourceUrl, new APIError('error', hostname, ERROR_MESSAGE_METADATA), response, isMetadataRequest);
83
101
  return;
84
- } // Dispatch Analytics and resolved card action - including unauthorized states.
102
+ } //if a link resolves normally, metadata will also always be resolved
85
103
 
86
104
 
105
+ setMetadataStatus('resolved'); // Dispatch Analytics and resolved card action - including unauthorized states.
106
+
87
107
  if (isReloading) {
88
108
  dispatch(cardAction(ACTION_RELOADING, {
89
109
  url: resourceUrl
@@ -91,12 +111,13 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
91
111
  } else {
92
112
  dispatch(cardAction(ACTION_RESOLVED, {
93
113
  url: resourceUrl
94
- }, response));
114
+ }, response, undefined, undefined, isMetadataRequest));
95
115
  }
96
- }, [dispatch, handleResolvedLinkError, hasAuthFlowSupported]);
116
+ }, [dispatch, handleResolvedLinkError, hasAuthFlowSupported, setMetadataStatus]);
97
117
  var resolve = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
98
118
  var resourceUrl,
99
119
  isReloading,
120
+ isMetadataRequest,
100
121
  _args = arguments;
101
122
  return _regeneratorRuntime.wrap(function _callee$(_context) {
102
123
  while (1) {
@@ -104,24 +125,25 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
104
125
  case 0:
105
126
  resourceUrl = _args.length > 0 && _args[0] !== undefined ? _args[0] : url;
106
127
  isReloading = _args.length > 1 && _args[1] !== undefined ? _args[1] : false;
128
+ isMetadataRequest = _args.length > 2 && _args[2] !== undefined ? _args[2] : false;
107
129
 
108
- if (!(isReloading || !hasData)) {
109
- _context.next = 6;
130
+ if (!(isReloading || !hasData || isMetadataRequest)) {
131
+ _context.next = 7;
110
132
  break;
111
133
  }
112
134
 
113
135
  return _context.abrupt("return", connections.client.fetchData(resourceUrl).then(function (response) {
114
- return handleResolvedLinkResponse(resourceUrl, response, isReloading);
136
+ return handleResolvedLinkResponse(resourceUrl, response, isReloading, isMetadataRequest);
115
137
  }).catch(function (error) {
116
- return handleResolvedLinkError(resourceUrl, error);
138
+ return handleResolvedLinkError(resourceUrl, error, undefined, isMetadataRequest);
117
139
  }));
118
140
 
119
- case 6:
141
+ case 7:
120
142
  addMetadataToExperience('smart-link-rendered', id, {
121
143
  cached: true
122
144
  });
123
145
 
124
- case 7:
146
+ case 8:
125
147
  case "end":
126
148
  return _context.stop();
127
149
  }
@@ -133,10 +155,11 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
133
155
  dispatch(cardAction(ACTION_PENDING, {
134
156
  url: url
135
157
  }));
158
+ setMetadataStatus('pending');
136
159
  }
137
160
 
138
161
  return resolve();
139
- }, [url, resolve, details, dispatch]);
162
+ }, [details, resolve, dispatch, url, setMetadataStatus]);
140
163
  var reload = useCallback(function () {
141
164
  var definitionId = getDefinitionId(details);
142
165
 
@@ -148,6 +171,13 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
148
171
  resolve(url, true);
149
172
  }
150
173
  }, [url, details, getState, resolve]);
174
+ var loadMetadata = useCallback(function () {
175
+ //metadataStatus will be undefined for SSR links only
176
+ if (metadataStatus === undefined) {
177
+ setMetadataStatus('pending');
178
+ return resolve(url, false, true);
179
+ }
180
+ }, [metadataStatus, resolve, setMetadataStatus, url]);
151
181
  var authorize = useCallback(function (appearance) {
152
182
  var definitionId = getDefinitionId(details);
153
183
  var extensionKey = getExtensionKey(details);
@@ -243,7 +273,8 @@ export var useSmartCardActions = function useSmartCardActions(id, url, analytics
243
273
  register: register,
244
274
  reload: reload,
245
275
  authorize: authorize,
246
- invoke: invoke
276
+ invoke: invoke,
277
+ loadMetadata: loadMetadata
247
278
  };
248
- }, [register, reload, authorize, invoke]);
279
+ }, [register, reload, authorize, invoke, loadMetadata]);
249
280
  };
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  import { getUrl } from '@atlaskit/linking-common';
3
- import { uiAuthEvent, uiAuthAlternateAccountEvent, uiCardClickedEvent, uiActionClickedEvent, uiClosedAuthEvent, uiRenderSuccessEvent, uiHoverCardViewedEvent, uiHoverCardDismissedEvent, uiHoverCardOpenLinkClickedEvent, invokeSucceededEvent as _invokeSucceededEvent, invokeFailedEvent as _invokeFailedEvent, connectSucceededEvent as _connectSucceededEvent, connectFailedEvent as _connectFailedEvent, trackAppAccountConnected, screenAuthPopupEvent, instrumentEvent } from '../../utils/analytics';
3
+ import { uiAuthEvent, uiAuthAlternateAccountEvent, uiCardClickedEvent, uiActionClickedEvent, uiClosedAuthEvent, uiRenderSuccessEvent, uiRenderFailedEvent, uiHoverCardViewedEvent, uiHoverCardDismissedEvent, uiHoverCardOpenLinkClickedEvent, invokeSucceededEvent as _invokeSucceededEvent, invokeFailedEvent as _invokeFailedEvent, connectSucceededEvent as _connectSucceededEvent, connectFailedEvent as _connectFailedEvent, trackAppAccountConnected, screenAuthPopupEvent, instrumentEvent } from '../../utils/analytics';
4
4
  import { failUfoExperience, startUfoExperience, succeedUfoExperience } from './ufoExperiences';
5
5
  import { getDefinitionId, getExtensionKey, getResourceType, getSubproduct, getProduct } from '../helpers';
6
6
  import { useSmartLinkContext } from '@atlaskit/link-provider';
@@ -20,12 +20,13 @@ export var useSmartLinkAnalytics = function useSmartLinkAnalytics(url, dispatchA
20
20
  var _useSmartLinkContext = useSmartLinkContext(),
21
21
  store = _useSmartLinkContext.store;
22
22
 
23
- var state = getUrl(store, url);
24
- var extractedDefinitionId = getDefinitionId(state.details);
25
- var extractedExtensionKey = getExtensionKey(state.details);
26
- var extractedResourceType = getResourceType(state.details);
27
- var extractedSubproduct = getSubproduct(state.details);
28
- var extractedProduct = getProduct(state.details);
23
+ var state = store ? getUrl(store, url) : undefined;
24
+ var details = state ? state.details : undefined;
25
+ var extractedDefinitionId = getDefinitionId(details);
26
+ var extractedExtensionKey = getExtensionKey(details);
27
+ var extractedResourceType = getResourceType(details);
28
+ var extractedSubproduct = getSubproduct(details);
29
+ var extractedProduct = getProduct(details);
29
30
  /** Contains all ui analytics events */
30
31
 
31
32
  var ui = useMemo(function () {
@@ -160,6 +161,25 @@ export var useSmartLinkAnalytics = function useSmartLinkAnalytics(url, dispatchA
160
161
  dispatchAnalytics(uiRenderSuccessEvent(display, status, definitionId, extensionKey));
161
162
  },
162
163
 
164
+ /**
165
+ * This fires an event that represents when a Smart Link renders unsuccessfuly.
166
+ * @param display Whether the card was an Inline, Block, Embed or Flexible UI.
167
+ * @param id The unique ID for this Smart Link.
168
+ * @param error: An error representing why the Smart Link render failed.
169
+ * @param errorInfo: Additional details about the error including the stack trace.
170
+ */
171
+ renderFailedEvent: function renderFailedEvent(display) {
172
+ var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultId;
173
+ var error = arguments.length > 2 ? arguments[2] : undefined;
174
+ var errorInfo = arguments.length > 3 ? arguments[3] : undefined;
175
+ // Start and fail the smart-link-rendered experience. If it has already
176
+ // been started nothing happens.
177
+ startUfoExperience('smart-link-rendered', id);
178
+ failUfoExperience('smart-link-rendered', id);
179
+ failUfoExperience('smart-link-authenticated', id);
180
+ dispatchAnalytics(uiRenderFailedEvent(display, error, errorInfo));
181
+ },
182
+
163
183
  /**
164
184
  * This fires an event that represents a hover preview being opened.
165
185
  * @param hoverDisplay Whether the hover preview was displayed as a card or embed.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-card",
3
- "version": "20.0.4",
3
+ "version": "20.1.0",
4
4
  "sideEffects": false
5
5
  }
@@ -31,6 +31,7 @@ export var BlockCard = function BlockCard(_ref) {
31
31
  renderers = _ref.renderers,
32
32
  isSelected = _ref.isSelected,
33
33
  onResolve = _ref.onResolve,
34
+ onError = _ref.onError,
34
35
  testId = _ref.testId,
35
36
  showActions = _ref.showActions,
36
37
  platform = _ref.platform;
@@ -68,6 +69,13 @@ export var BlockCard = function BlockCard(_ref) {
68
69
  }));
69
70
 
70
71
  case 'unauthorized':
72
+ if (onError) {
73
+ onError({
74
+ url: url,
75
+ status: status
76
+ });
77
+ }
78
+
71
79
  var unauthorizedViewProps = extractBlockProps(data, meta, extractorOpts);
72
80
  return /*#__PURE__*/React.createElement(BlockCardUnauthorisedView, _extends({}, unauthorizedViewProps, {
73
81
  isSelected: isSelected,
@@ -78,6 +86,13 @@ export var BlockCard = function BlockCard(_ref) {
78
86
  }));
79
87
 
80
88
  case 'forbidden':
89
+ if (onError) {
90
+ onError({
91
+ url: url,
92
+ status: status
93
+ });
94
+ }
95
+
81
96
  var forbiddenViewProps = extractBlockProps(data, meta, extractorOpts);
82
97
  var cardMetadata = (_details$meta = details === null || details === void 0 ? void 0 : details.meta) !== null && _details$meta !== void 0 ? _details$meta : getUnauthorizedJsonLd().meta;
83
98
  var requestAccessContext = extractRequestAccessContext({
@@ -94,6 +109,13 @@ export var BlockCard = function BlockCard(_ref) {
94
109
  }));
95
110
 
96
111
  case 'not_found':
112
+ if (onError) {
113
+ onError({
114
+ url: url,
115
+ status: status
116
+ });
117
+ }
118
+
97
119
  var notFoundViewProps = extractBlockProps(data, meta, extractorOpts);
98
120
  return /*#__PURE__*/React.createElement(BlockCardNotFoundView, _extends({}, notFoundViewProps, {
99
121
  isSelected: isSelected,
@@ -103,6 +125,13 @@ export var BlockCard = function BlockCard(_ref) {
103
125
 
104
126
  case 'fallback':
105
127
  case 'errored':
128
+ if (onError) {
129
+ onError({
130
+ url: url,
131
+ status: status
132
+ });
133
+ }
134
+
106
135
  if (authFlow && authFlow === 'disabled') {
107
136
  return /*#__PURE__*/React.createElement(CardLinkView, {
108
137
  link: url,
@@ -20,6 +20,7 @@ export function CardWithUrlContent(_ref) {
20
20
  appearance = _ref.appearance,
21
21
  dispatchAnalytics = _ref.dispatchAnalytics,
22
22
  onResolve = _ref.onResolve,
23
+ onError = _ref.onError,
23
24
  testId = _ref.testId,
24
25
  showActions = _ref.showActions,
25
26
  inheritDimensions = _ref.inheritDimensions,
@@ -97,7 +98,7 @@ export function CardWithUrlContent(_ref) {
97
98
  if (isFinalState(state.status)) {
98
99
  analytics.ui.renderSuccessEvent(appearance, state.status, id, definitionId, extensionKey);
99
100
  }
100
- }, [appearance, state.details, state.status, url, definitionId, extensionKey, analytics.ui, id]);
101
+ }, [appearance, state.status, url, definitionId, extensionKey, analytics.ui, id]);
101
102
 
102
103
  if (isFlexibleUi) {
103
104
  var cardState = state;
@@ -121,7 +122,8 @@ export function CardWithUrlContent(_ref) {
121
122
  ui: ui,
122
123
  url: url,
123
124
  testId: testId,
124
- onResolve: onResolve
125
+ onResolve: onResolve,
126
+ onError: onError
125
127
  }, children);
126
128
  } // We have to keep this last to prevent hook order from being violated
127
129
 
@@ -141,6 +143,7 @@ export function CardWithUrlContent(_ref) {
141
143
  handleFrameClick: handleClickWrapper,
142
144
  isSelected: isSelected,
143
145
  onResolve: onResolve,
146
+ onError: onError,
144
147
  testId: testId,
145
148
  inlinePreloaderStyle: inlinePreloaderStyle,
146
149
  showHoverPreview: showHoverPreview
@@ -160,6 +163,7 @@ export function CardWithUrlContent(_ref) {
160
163
  handleAnalytics: dispatchAnalytics,
161
164
  isSelected: isSelected,
162
165
  onResolve: onResolve,
166
+ onError: onError,
163
167
  testId: testId,
164
168
  showActions: showActions,
165
169
  platform: platform
@@ -179,6 +183,7 @@ export function CardWithUrlContent(_ref) {
179
183
  isFrameVisible: isFrameVisible,
180
184
  platform: platform,
181
185
  onResolve: onResolve,
186
+ onError: onError,
182
187
  testId: testId,
183
188
  inheritDimensions: inheritDimensions,
184
189
  showActions: showActions,