@atlaskit/link-create 1.10.0 → 1.11.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 +39 -0
- package/analytics.spec.yaml +29 -3
- package/dist/cjs/common/messages.js +14 -0
- package/dist/cjs/common/ui/error-boundary-ui/index.js +4 -3
- package/dist/cjs/common/ui/error-boundary-ui/messages.js +0 -5
- package/dist/cjs/common/utils/analytics/analytics.codegen.js +1 -1
- package/dist/cjs/common/utils/errors/index.js +39 -0
- package/dist/cjs/controllers/callback-context/main.js +23 -7
- package/dist/cjs/controllers/experience-tracker/index.js +85 -0
- package/dist/cjs/index.js +7 -0
- package/dist/cjs/ui/create-form/async-select/main.js +62 -20
- package/dist/cjs/ui/create-form/main.js +62 -7
- package/dist/cjs/ui/link-create/error-boundary/index.js +14 -9
- package/dist/cjs/ui/link-create/main.js +5 -3
- package/dist/cjs/ui/main.js +9 -9
- package/dist/es2019/common/messages.js +8 -0
- package/dist/es2019/common/ui/error-boundary-ui/index.js +2 -1
- package/dist/es2019/common/ui/error-boundary-ui/messages.js +0 -5
- package/dist/es2019/common/utils/analytics/analytics.codegen.js +1 -1
- package/dist/es2019/common/utils/errors/index.js +31 -0
- package/dist/es2019/controllers/callback-context/main.js +19 -3
- package/dist/es2019/controllers/experience-tracker/index.js +75 -0
- package/dist/es2019/index.js +1 -0
- package/dist/es2019/ui/create-form/async-select/main.js +32 -1
- package/dist/es2019/ui/create-form/main.js +47 -3
- package/dist/es2019/ui/link-create/error-boundary/index.js +14 -9
- package/dist/es2019/ui/link-create/main.js +5 -3
- package/dist/es2019/ui/main.js +10 -9
- package/dist/esm/common/messages.js +8 -0
- package/dist/esm/common/ui/error-boundary-ui/index.js +2 -1
- package/dist/esm/common/ui/error-boundary-ui/messages.js +0 -5
- package/dist/esm/common/utils/analytics/analytics.codegen.js +1 -1
- package/dist/esm/common/utils/errors/index.js +32 -0
- package/dist/esm/controllers/callback-context/main.js +23 -7
- package/dist/esm/controllers/experience-tracker/index.js +75 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/ui/create-form/async-select/main.js +62 -20
- package/dist/esm/ui/create-form/main.js +62 -7
- package/dist/esm/ui/link-create/error-boundary/index.js +14 -9
- package/dist/esm/ui/link-create/main.js +5 -3
- package/dist/esm/ui/main.js +10 -10
- package/dist/types/common/messages.d.ts +8 -0
- package/dist/types/common/ui/error-boundary-ui/messages.d.ts +0 -5
- package/dist/types/common/utils/analytics/analytics.codegen.d.ts +11 -2
- package/dist/types/common/utils/errors/index.d.ts +10 -0
- package/dist/types/controllers/callback-context/main.d.ts +1 -1
- package/dist/types/controllers/experience-tracker/index.d.ts +22 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/ui/create-form/async-select/main.d.ts +1 -1
- package/dist/types/ui/create-form/main.d.ts +18 -1
- package/dist/types-ts4.5/common/messages.d.ts +8 -0
- package/dist/types-ts4.5/common/ui/error-boundary-ui/messages.d.ts +0 -5
- package/dist/types-ts4.5/common/utils/analytics/analytics.codegen.d.ts +11 -2
- package/dist/types-ts4.5/common/utils/errors/index.d.ts +10 -0
- package/dist/types-ts4.5/controllers/callback-context/main.d.ts +1 -1
- package/dist/types-ts4.5/controllers/experience-tracker/index.d.ts +22 -0
- package/dist/types-ts4.5/index.d.ts +1 -0
- package/dist/types-ts4.5/ui/create-form/async-select/main.d.ts +1 -1
- package/dist/types-ts4.5/ui/create-form/main.d.ts +18 -1
- package/package.json +8 -4
- package/report.api.md +11 -8
- package/tmp/api-report-tmp.d.ts +0 -187
|
@@ -7,19 +7,24 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
7
7
|
});
|
|
8
8
|
exports.TEST_ID = exports.CreateForm = void 0;
|
|
9
9
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
11
|
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
|
|
11
12
|
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
|
|
12
13
|
var _react = require("react");
|
|
13
14
|
var _react2 = require("@emotion/react");
|
|
15
|
+
var _finalForm = require("final-form");
|
|
14
16
|
var _reactFinalForm = require("react-final-form");
|
|
17
|
+
var _reactIntlNext = require("react-intl-next");
|
|
15
18
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
16
19
|
var _primitives = require("@atlaskit/primitives");
|
|
17
20
|
var _constants = require("../../common/constants");
|
|
21
|
+
var _messages = _interopRequireDefault(require("../../common/messages"));
|
|
22
|
+
var _callbackContext = require("../../controllers/callback-context");
|
|
18
23
|
var _exitWarningModalContext = require("../../controllers/exit-warning-modal-context");
|
|
19
24
|
var _formContext = require("../../controllers/form-context");
|
|
20
25
|
var _formFooter = require("./form-footer");
|
|
21
26
|
var _formLoader = require("./form-loader");
|
|
22
|
-
var _excluded = ["submitting"];
|
|
27
|
+
var _excluded = ["submitting", "submitError"];
|
|
23
28
|
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
|
24
29
|
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /** @jsx jsx */
|
|
25
30
|
var formStyles = (0, _react2.css)({
|
|
@@ -39,10 +44,14 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
39
44
|
hideFooter = _ref.hideFooter,
|
|
40
45
|
initialValues = _ref.initialValues;
|
|
41
46
|
var _useFormContext = (0, _formContext.useFormContext)(),
|
|
47
|
+
setFormErrorMessage = _useFormContext.setFormErrorMessage,
|
|
42
48
|
formErrorMessage = _useFormContext.formErrorMessage,
|
|
43
49
|
enableEditView = _useFormContext.enableEditView;
|
|
50
|
+
var intl = (0, _reactIntlNext.useIntl)();
|
|
44
51
|
var _useExitWarningModal = (0, _exitWarningModalContext.useExitWarningModal)(),
|
|
45
52
|
setShouldShowWarning = _useExitWarningModal.setShouldShowWarning;
|
|
53
|
+
var _useLinkCreateCallbac = (0, _callbackContext.useLinkCreateCallback)(),
|
|
54
|
+
onFailure = _useLinkCreateCallbac.onFailure;
|
|
46
55
|
var handleSubmit = (0, _react.useCallback)( /*#__PURE__*/function () {
|
|
47
56
|
var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(data) {
|
|
48
57
|
var shouldEnableEditView, formData;
|
|
@@ -60,6 +69,11 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
60
69
|
* if submission is successful
|
|
61
70
|
*/
|
|
62
71
|
enableEditView === null || enableEditView === void 0 || enableEditView(!!shouldEnableEditView);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This is the onSubmit handler provided by the plugin
|
|
75
|
+
* It will be async, and it will likely involve awaiting `onCreate` (the adopters handler)
|
|
76
|
+
*/
|
|
63
77
|
return _context.abrupt("return", onSubmit(formData));
|
|
64
78
|
case 4:
|
|
65
79
|
return _context.abrupt("return", onSubmit(data));
|
|
@@ -73,6 +87,40 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
73
87
|
return _ref2.apply(this, arguments);
|
|
74
88
|
};
|
|
75
89
|
}(), [onSubmit, enableEditView]);
|
|
90
|
+
var handleSubmitWithErrorHandling = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2() {
|
|
91
|
+
var _args2 = arguments;
|
|
92
|
+
return _regenerator.default.wrap(function _callee2$(_context2) {
|
|
93
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
94
|
+
case 0:
|
|
95
|
+
_context2.prev = 0;
|
|
96
|
+
/**
|
|
97
|
+
* Clear any error message that may have been set by async select fields
|
|
98
|
+
* This will immediately remove any indication of an error, but the form likely will fail to submit,
|
|
99
|
+
* it will be likely a 400 because the user probably could not set all fields anyway
|
|
100
|
+
*/
|
|
101
|
+
setFormErrorMessage();
|
|
102
|
+
_context2.next = 4;
|
|
103
|
+
return handleSubmit.apply(void 0, _args2);
|
|
104
|
+
case 4:
|
|
105
|
+
return _context2.abrupt("return", _context2.sent);
|
|
106
|
+
case 7:
|
|
107
|
+
_context2.prev = 7;
|
|
108
|
+
_context2.t0 = _context2["catch"](0);
|
|
109
|
+
/**
|
|
110
|
+
* Notify link create of failed experience
|
|
111
|
+
*/
|
|
112
|
+
onFailure === null || onFailure === void 0 || onFailure(_context2.t0);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Return a generic message for react final form to render
|
|
116
|
+
*/
|
|
117
|
+
return _context2.abrupt("return", (0, _defineProperty2.default)({}, _finalForm.FORM_ERROR, intl.formatMessage(_messages.default.genericErrorMessage)));
|
|
118
|
+
case 11:
|
|
119
|
+
case "end":
|
|
120
|
+
return _context2.stop();
|
|
121
|
+
}
|
|
122
|
+
}, _callee2, null, [[0, 7]]);
|
|
123
|
+
})), [handleSubmit, setFormErrorMessage, intl, onFailure]);
|
|
76
124
|
var handleCancel = (0, _react.useCallback)(function () {
|
|
77
125
|
onCancel && onCancel();
|
|
78
126
|
}, [onCancel]);
|
|
@@ -80,7 +128,7 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
80
128
|
return (0, _react2.jsx)(_formLoader.CreateFormLoader, null);
|
|
81
129
|
}
|
|
82
130
|
return (0, _react2.jsx)(_reactFinalForm.Form, {
|
|
83
|
-
onSubmit: handleSubmit,
|
|
131
|
+
onSubmit: (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ? handleSubmitWithErrorHandling : handleSubmit,
|
|
84
132
|
initialValues: initialValues,
|
|
85
133
|
mutators: {
|
|
86
134
|
setField: function setField(args, state, tools) {
|
|
@@ -89,9 +137,10 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
89
137
|
});
|
|
90
138
|
}
|
|
91
139
|
}
|
|
92
|
-
}, function (
|
|
93
|
-
var submitting =
|
|
94
|
-
|
|
140
|
+
}, function (_ref5) {
|
|
141
|
+
var submitting = _ref5.submitting,
|
|
142
|
+
submitError = _ref5.submitError,
|
|
143
|
+
formProps = (0, _objectWithoutProperties2.default)(_ref5, _excluded);
|
|
95
144
|
return (0, _react2.jsx)("form", {
|
|
96
145
|
onSubmit: formProps.handleSubmit,
|
|
97
146
|
name: "link-create-form",
|
|
@@ -112,8 +161,14 @@ var CreateForm = exports.CreateForm = function CreateForm(_ref) {
|
|
|
112
161
|
});
|
|
113
162
|
setShouldShowWarning(isModified);
|
|
114
163
|
}
|
|
115
|
-
}), (0, _react2.jsx)(_primitives.Box, null, children), !hideFooter && (0, _react2.jsx)(_formFooter.CreateFormFooter
|
|
116
|
-
|
|
164
|
+
}), (0, _react2.jsx)(_primitives.Box, null, children), !hideFooter && (0, _react2.jsx)(_formFooter.CreateFormFooter
|
|
165
|
+
/**
|
|
166
|
+
* We will prefer to render the error message connected to
|
|
167
|
+
* react final form state (submitError) otherwise we can
|
|
168
|
+
* default to the `formErrorMessage` that we sometimes use with our own
|
|
169
|
+
* "form context" (only currently used for AsyncSelect field reporting failed loading)
|
|
170
|
+
*/, {
|
|
171
|
+
formErrorMessage: (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ? submitError || formErrorMessage : formErrorMessage,
|
|
117
172
|
handleCancel: handleCancel,
|
|
118
173
|
submitting: submitting,
|
|
119
174
|
testId: testId
|
|
@@ -13,6 +13,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
|
13
13
|
var _constants = require("../../../common/constants");
|
|
14
14
|
var _errorBoundaryUi = require("../../../common/ui/error-boundary-ui");
|
|
15
15
|
var _analytics = _interopRequireDefault(require("../../../common/utils/analytics/analytics.codegen"));
|
|
16
|
+
var _experienceTracker = require("../../../controllers/experience-tracker");
|
|
16
17
|
var _errorBoundaryBase = require("./error-boundary-base");
|
|
17
18
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
18
19
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
@@ -21,14 +22,17 @@ var ErrorBoundary = exports.ErrorBoundary = function ErrorBoundary(_ref) {
|
|
|
21
22
|
errorComponent = _ref.errorComponent;
|
|
22
23
|
var _useAnalyticsEvents = (0, _analyticsNext.useAnalyticsEvents)(),
|
|
23
24
|
createAnalyticsEvent = _useAnalyticsEvents.createAnalyticsEvent;
|
|
25
|
+
var experience = (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ?
|
|
26
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
27
|
+
(0, _experienceTracker.useExperience)() : null;
|
|
24
28
|
var handleError = (0, _react.useCallback)(function (error, info) {
|
|
25
29
|
var _window, _window2, _info$componentStack;
|
|
26
|
-
if ((0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
if (!(0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability')) {
|
|
31
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.enable-sentry-client')) {
|
|
32
|
+
// Capture exception to Sentry
|
|
33
|
+
(0, _sentry.captureException)(error, 'link-create');
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
// Fire Analytics event
|
|
32
36
|
createAnalyticsEvent((0, _analytics.default)('operational.linkCreate.unhandledErrorCaught', (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.enable-sentry-client') ? {
|
|
33
37
|
browserInfo: ((_window = window) === null || _window === void 0 || (_window = _window.navigator) === null || _window === void 0 ? void 0 : _window.userAgent) || 'unknown',
|
|
34
38
|
error: error.name,
|
|
@@ -38,10 +42,11 @@ var ErrorBoundary = exports.ErrorBoundary = function ErrorBoundary(_ref) {
|
|
|
38
42
|
error: error.toString(),
|
|
39
43
|
componentStack: (_info$componentStack = info === null || info === void 0 ? void 0 : info.componentStack) !== null && _info$componentStack !== void 0 ? _info$componentStack : ''
|
|
40
44
|
})).fire(_constants.ANALYTICS_CHANNEL);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
if ((0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability')) {
|
|
46
|
+
// Track experience as failed for SLO
|
|
47
|
+
experience === null || experience === void 0 || experience.failure(error);
|
|
48
|
+
}
|
|
49
|
+
}, [createAnalyticsEvent, experience]);
|
|
45
50
|
return /*#__PURE__*/_react.default.createElement(_errorBoundaryBase.BaseErrorBoundary, {
|
|
46
51
|
onError: handleError,
|
|
47
52
|
errorComponent: errorComponent !== null && errorComponent !== void 0 ? errorComponent : /*#__PURE__*/_react.default.createElement(_errorBoundaryUi.ErrorBoundaryUI, null)
|
|
@@ -82,7 +82,9 @@ var LinkCreateWithModal = function LinkCreateWithModal(_ref2) {
|
|
|
82
82
|
}(), [onCreate, setFormErrorMessage]);
|
|
83
83
|
var handleFailure = (0, _react.useCallback)(function (error) {
|
|
84
84
|
// Set the form error message
|
|
85
|
-
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
setFormErrorMessage(error.message);
|
|
87
|
+
}
|
|
86
88
|
onFailure && onFailure(error);
|
|
87
89
|
}, [onFailure, setFormErrorMessage]);
|
|
88
90
|
var _useExitWarningModal = (0, _exitWarningModalContext.useExitWarningModal)(),
|
|
@@ -104,8 +106,8 @@ var LinkCreateWithModal = function LinkCreateWithModal(_ref2) {
|
|
|
104
106
|
return setShowExitWarning(false);
|
|
105
107
|
}, []);
|
|
106
108
|
return (0, _react2.jsx)(_callbackContext.LinkCreateCallbackProvider, {
|
|
107
|
-
onCreate: handleCreate,
|
|
108
|
-
onFailure: handleFailure,
|
|
109
|
+
onCreate: (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ? onCreate : handleCreate,
|
|
110
|
+
onFailure: (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ? onFailure : handleFailure,
|
|
109
111
|
onCancel: handleCancel
|
|
110
112
|
}, (0, _react2.jsx)(_modalDialog.ModalTransition, null, active && (0, _react2.jsx)(_ModalDialog.Modal, {
|
|
111
113
|
testId: "link-create-modal",
|
package/dist/cjs/ui/main.js
CHANGED
|
@@ -5,35 +5,35 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = exports.PACKAGE_DATA = void 0;
|
|
8
|
-
var _objectDestructuringEmpty2 = _interopRequireDefault(require("@babel/runtime/helpers/objectDestructuringEmpty"));
|
|
9
|
-
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
10
8
|
var _react = require("react");
|
|
11
9
|
var _react2 = require("@emotion/react");
|
|
12
10
|
var _analyticsNext = require("@atlaskit/analytics-next");
|
|
13
11
|
var _intlMessagesProvider = require("@atlaskit/intl-messages-provider");
|
|
12
|
+
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
14
13
|
var _constants = require("../common/constants");
|
|
15
14
|
var _errorBoundaryModal = require("../common/ui/error-boundary-modal");
|
|
16
15
|
var _analytics = require("../common/utils/analytics");
|
|
17
16
|
var _fetchMessagesForLocale = require("../common/utils/locale/fetch-messages-for-locale");
|
|
17
|
+
var _experienceTracker = require("../controllers/experience-tracker");
|
|
18
18
|
var _en = _interopRequireDefault(require("../i18n/en"));
|
|
19
19
|
var _linkCreate = _interopRequireDefault(require("./link-create"));
|
|
20
20
|
var _errorBoundary = require("./link-create/error-boundary");
|
|
21
21
|
/** @jsx jsx */
|
|
22
22
|
|
|
23
|
-
var LinkCreateWithAnalyticsContext = (0, _analytics.withLinkCreateAnalyticsContext)( /*#__PURE__*/(0, _react.memo)(function (
|
|
24
|
-
var
|
|
25
|
-
return (0, _react2.jsx)(_errorBoundary.ErrorBoundary, {
|
|
23
|
+
var LinkCreateWithAnalyticsContext = (0, _analytics.withLinkCreateAnalyticsContext)( /*#__PURE__*/(0, _react.memo)(function (props) {
|
|
24
|
+
var ExperienceProvider = (0, _platformFeatureFlags.getBooleanFF)('platform.linking-platform.link-create.better-observability') ? _experienceTracker.Experience : _react.Fragment;
|
|
25
|
+
return (0, _react2.jsx)(ExperienceProvider, null, (0, _react2.jsx)(_errorBoundary.ErrorBoundary, {
|
|
26
26
|
errorComponent: (0, _react2.jsx)(_errorBoundaryModal.ErrorBoundaryModal, {
|
|
27
27
|
active: props.active,
|
|
28
28
|
onClose: props.onCancel
|
|
29
29
|
})
|
|
30
|
-
}, (0, _react2.jsx)(_linkCreate.default, props));
|
|
30
|
+
}, (0, _react2.jsx)(_linkCreate.default, props)));
|
|
31
31
|
}));
|
|
32
32
|
var PACKAGE_DATA = exports.PACKAGE_DATA = {
|
|
33
33
|
packageName: "@atlaskit/link-create" || '',
|
|
34
|
-
packageVersion: "1.
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
packageVersion: "1.11.1" || '',
|
|
35
|
+
component: _constants.COMPONENT_NAME,
|
|
36
|
+
componentName: _constants.COMPONENT_NAME
|
|
37
37
|
};
|
|
38
38
|
var ComposedLinkCreate = /*#__PURE__*/(0, _react.memo)(function (props) {
|
|
39
39
|
return (0, _react2.jsx)(_analyticsNext.AnalyticsContext, {
|
|
@@ -3,6 +3,7 @@ import React from 'react';
|
|
|
3
3
|
import { FormattedMessage, useIntl } from 'react-intl-next';
|
|
4
4
|
import Button from '@atlaskit/button';
|
|
5
5
|
import EmptyState from '@atlaskit/empty-state';
|
|
6
|
+
import commonMessages from '../../messages';
|
|
6
7
|
import ErrorSVG from './error-svg';
|
|
7
8
|
import messages from './messages';
|
|
8
9
|
export const CONTACT_SUPPORT_LINK = 'https://support.atlassian.com/contact/';
|
|
@@ -11,7 +12,7 @@ export const ErrorBoundaryUI = () => {
|
|
|
11
12
|
return /*#__PURE__*/React.createElement(EmptyState, {
|
|
12
13
|
maxImageWidth: 82,
|
|
13
14
|
testId: 'link-create-error-boundary-ui',
|
|
14
|
-
header: intl.formatMessage(
|
|
15
|
+
header: intl.formatMessage(commonMessages.genericErrorMessage),
|
|
15
16
|
description: /*#__PURE__*/React.createElement(FormattedMessage, _extends({}, messages.description, {
|
|
16
17
|
values: {
|
|
17
18
|
a: label => /*#__PURE__*/React.createElement(Button, {
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { defineMessages } from 'react-intl-next';
|
|
2
2
|
export default defineMessages({
|
|
3
|
-
heading: {
|
|
4
|
-
id: 'link-create.unknown-error.heading',
|
|
5
|
-
defaultMessage: 'Something went wrong',
|
|
6
|
-
description: 'Heading when an unknown error occurs'
|
|
7
|
-
},
|
|
8
3
|
description: {
|
|
9
4
|
id: 'link-create.unknown-error.description',
|
|
10
5
|
defaultMessage: 'Refresh the page, or contact <a>Atlassian Support</a> if this keeps happening.',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates Typescript types for analytics events from analytics.spec.yaml
|
|
5
5
|
*
|
|
6
|
-
* @codegen <<SignedSource::
|
|
6
|
+
* @codegen <<SignedSource::510281cade5c7b1a8dec8b382c8d4b86>>
|
|
7
7
|
* @codegenCommand yarn workspace @atlaskit/link-create run codegen-analytics
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getTraceId } from '@atlaskit/linking-common/utils';
|
|
2
|
+
const getUrlPath = url => {
|
|
3
|
+
try {
|
|
4
|
+
return new URL(url).pathname;
|
|
5
|
+
} catch {
|
|
6
|
+
return 'Failed to parse pathname from url';
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
export const getNetworkFields = error => {
|
|
10
|
+
if (error instanceof Response) {
|
|
11
|
+
return {
|
|
12
|
+
traceId: getTraceId(error),
|
|
13
|
+
status: error.status,
|
|
14
|
+
path: getUrlPath(error.url)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
traceId: null,
|
|
19
|
+
status: null,
|
|
20
|
+
path: null
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export const getErrorType = error => {
|
|
24
|
+
if (error instanceof Response) {
|
|
25
|
+
return 'NetworkError';
|
|
26
|
+
}
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
return error.name;
|
|
29
|
+
}
|
|
30
|
+
return typeof error;
|
|
31
|
+
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import React, { useContext, useMemo } from 'react';
|
|
2
2
|
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
3
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
3
4
|
import { ANALYTICS_CHANNEL } from '../../common/constants';
|
|
4
5
|
import createEventPayload from '../../common/utils/analytics/analytics.codegen';
|
|
6
|
+
import { getErrorType } from '../../common/utils/errors';
|
|
7
|
+
import { useExperience } from '../experience-tracker';
|
|
5
8
|
const LinkCreateCallbackContext = /*#__PURE__*/React.createContext({});
|
|
6
9
|
const LinkCreateCallbackProvider = ({
|
|
7
10
|
children,
|
|
@@ -12,8 +15,18 @@ const LinkCreateCallbackProvider = ({
|
|
|
12
15
|
const {
|
|
13
16
|
createAnalyticsEvent
|
|
14
17
|
} = useAnalyticsEvents();
|
|
18
|
+
const experience = getBooleanFF('platform.linking-platform.link-create.better-observability') ?
|
|
19
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
20
|
+
useExperience() : null;
|
|
15
21
|
const handleCreate = useMemo(() => ({
|
|
16
22
|
onCreate: async result => {
|
|
23
|
+
if (getBooleanFF('platform.linking-platform.link-create.better-observability')) {
|
|
24
|
+
/**
|
|
25
|
+
* We consider the experience successful once we have
|
|
26
|
+
* successfully created an object
|
|
27
|
+
*/
|
|
28
|
+
experience === null || experience === void 0 ? void 0 : experience.success();
|
|
29
|
+
}
|
|
17
30
|
const {
|
|
18
31
|
objectId,
|
|
19
32
|
objectType
|
|
@@ -26,15 +39,18 @@ const LinkCreateCallbackProvider = ({
|
|
|
26
39
|
await onCreate(result);
|
|
27
40
|
}
|
|
28
41
|
}
|
|
29
|
-
}), [createAnalyticsEvent, onCreate]);
|
|
42
|
+
}), [createAnalyticsEvent, onCreate, experience]);
|
|
30
43
|
const handleFailure = useMemo(() => ({
|
|
31
44
|
onFailure: async error => {
|
|
32
45
|
createAnalyticsEvent(createEventPayload('track.object.createFailed.linkCreate', {
|
|
33
|
-
failureType: error
|
|
46
|
+
failureType: getErrorType(error)
|
|
34
47
|
})).fire(ANALYTICS_CHANNEL);
|
|
48
|
+
if (getBooleanFF('platform.linking-platform.link-create.better-observability')) {
|
|
49
|
+
experience === null || experience === void 0 ? void 0 : experience.failure(error);
|
|
50
|
+
}
|
|
35
51
|
onFailure && onFailure(error);
|
|
36
52
|
}
|
|
37
|
-
}), [createAnalyticsEvent, onFailure]);
|
|
53
|
+
}), [createAnalyticsEvent, onFailure, experience]);
|
|
38
54
|
const value = useMemo(() => ({
|
|
39
55
|
onCancel,
|
|
40
56
|
...handleCreate,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
|
2
|
+
import { useAnalyticsEvents } from '@atlaskit/analytics-next';
|
|
3
|
+
import { captureException } from '@atlaskit/linking-common/sentry';
|
|
4
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
5
|
+
import { ANALYTICS_CHANNEL } from '../../common/constants';
|
|
6
|
+
import createEventPayload from '../../common/utils/analytics/analytics.codegen';
|
|
7
|
+
import { getErrorType, getNetworkFields } from '../../common/utils/errors';
|
|
8
|
+
const ExperienceContext = /*#__PURE__*/createContext({
|
|
9
|
+
success: () => {},
|
|
10
|
+
failure: () => {}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Experience provider that simply keeps track of the state of the experience.
|
|
15
|
+
* Fires an operational event when experience state changes to FAILED.
|
|
16
|
+
*/
|
|
17
|
+
export const Experience = ({
|
|
18
|
+
children
|
|
19
|
+
}) => {
|
|
20
|
+
const {
|
|
21
|
+
createAnalyticsEvent
|
|
22
|
+
} = useAnalyticsEvents();
|
|
23
|
+
const experience = useRef('STARTED');
|
|
24
|
+
const value = useMemo(() => ({
|
|
25
|
+
success: () => {
|
|
26
|
+
if (experience.current !== 'SUCCEEDED') {
|
|
27
|
+
experience.current = 'SUCCEEDED';
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
/**
|
|
31
|
+
* Indicate the experience has failed and capture exception information
|
|
32
|
+
* @param error Typically an Error class or Response class
|
|
33
|
+
*/
|
|
34
|
+
failure: error => {
|
|
35
|
+
const experienceStatus = 'FAILED';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Always capture an event to Splunk
|
|
39
|
+
*/
|
|
40
|
+
createAnalyticsEvent(createEventPayload('operational.linkCreateExperience.failed', {
|
|
41
|
+
/**
|
|
42
|
+
* The type of error that has failed the experience
|
|
43
|
+
*/
|
|
44
|
+
errorType: getErrorType(error),
|
|
45
|
+
/**
|
|
46
|
+
* The current status of the experience (has failed)
|
|
47
|
+
*/
|
|
48
|
+
experienceStatus,
|
|
49
|
+
/**
|
|
50
|
+
* Previous experience status indicates whether the experience
|
|
51
|
+
* has just failed now, or has already failing
|
|
52
|
+
*/
|
|
53
|
+
previousExperienceStatus: experience.current,
|
|
54
|
+
/**
|
|
55
|
+
* Fields related to `Response` object that can help with debugging
|
|
56
|
+
* what has gone wrong
|
|
57
|
+
*/
|
|
58
|
+
...getNetworkFields(error)
|
|
59
|
+
})).fire(ANALYTICS_CHANNEL);
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
if (getBooleanFF('platform.linking-platform.link-create.enable-sentry-client')) {
|
|
62
|
+
// Capture exception to Sentry
|
|
63
|
+
captureException(error, 'link-create');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (experience.current !== experienceStatus) {
|
|
67
|
+
experience.current = experienceStatus;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}), [experience, createAnalyticsEvent]);
|
|
71
|
+
return /*#__PURE__*/React.createElement(ExperienceContext.Provider, {
|
|
72
|
+
value: value
|
|
73
|
+
}, children);
|
|
74
|
+
};
|
|
75
|
+
export const useExperience = () => useContext(ExperienceContext);
|
package/dist/es2019/index.js
CHANGED
|
@@ -4,8 +4,13 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
4
4
|
import { jsx } from '@emotion/react';
|
|
5
5
|
import debounce from 'debounce-promise';
|
|
6
6
|
import { useForm } from 'react-final-form';
|
|
7
|
+
import { useIntl } from 'react-intl-next';
|
|
8
|
+
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
7
9
|
import { AsyncSelect as AkAsyncSelect } from '@atlaskit/select';
|
|
10
|
+
import messages from '../../../common/messages';
|
|
11
|
+
import { useLinkCreateCallback } from '../../../controllers/callback-context';
|
|
8
12
|
import { CreateField } from '../../../controllers/create-field';
|
|
13
|
+
import { useFormContext } from '../../../controllers/form-context';
|
|
9
14
|
export const TEST_ID = 'link-create-async-select';
|
|
10
15
|
|
|
11
16
|
/**
|
|
@@ -23,15 +28,41 @@ export function AsyncSelect({
|
|
|
23
28
|
validationHelpText,
|
|
24
29
|
testId = TEST_ID,
|
|
25
30
|
defaultOption: propsDefaultValue,
|
|
26
|
-
loadOptions,
|
|
31
|
+
loadOptions: loadOptionsFn,
|
|
27
32
|
...restProps
|
|
28
33
|
}) {
|
|
29
34
|
const {
|
|
30
35
|
mutators
|
|
31
36
|
} = useForm();
|
|
37
|
+
const {
|
|
38
|
+
onFailure
|
|
39
|
+
} = useLinkCreateCallback();
|
|
40
|
+
const {
|
|
41
|
+
setFormErrorMessage
|
|
42
|
+
} = useFormContext();
|
|
43
|
+
const intl = useIntl();
|
|
32
44
|
const [defaultValue, setDefaultValue] = useState(propsDefaultValue);
|
|
33
45
|
const [isLoadingDefaultOptions, setIsLoadingDefaultOptions] = useState(false);
|
|
34
46
|
const [defaultOptions, setDefaultOptions] = useState([]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* This binds experience to fail if async fetch ever fails to load
|
|
50
|
+
*/
|
|
51
|
+
const loadOptions = getBooleanFF('platform.linking-platform.link-create.better-observability') ?
|
|
52
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
53
|
+
useMemo(() => {
|
|
54
|
+
if (loadOptionsFn) {
|
|
55
|
+
return async function (...args) {
|
|
56
|
+
try {
|
|
57
|
+
return await loadOptionsFn(...args);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
onFailure === null || onFailure === void 0 ? void 0 : onFailure(err);
|
|
60
|
+
setFormErrorMessage(intl.formatMessage(messages.genericErrorMessage));
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}, [intl, onFailure, loadOptionsFn, setFormErrorMessage]) : loadOptionsFn;
|
|
35
66
|
useEffect(() => {
|
|
36
67
|
let current = true;
|
|
37
68
|
const fetch = async (query = '') => {
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/** @jsx jsx */
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { css, jsx } from '@emotion/react';
|
|
4
|
+
import { FORM_ERROR } from 'final-form';
|
|
4
5
|
import { Form, FormSpy } from 'react-final-form';
|
|
6
|
+
import { useIntl } from 'react-intl-next';
|
|
5
7
|
import { getBooleanFF } from '@atlaskit/platform-feature-flags';
|
|
6
8
|
import { Box } from '@atlaskit/primitives';
|
|
7
9
|
import { CREATE_FORM_MAX_WIDTH_IN_PX, LINK_CREATE_FORM_POST_CREATE_FIELD } from '../../common/constants';
|
|
10
|
+
import messages from '../../common/messages';
|
|
11
|
+
import { useLinkCreateCallback } from '../../controllers/callback-context';
|
|
8
12
|
import { useExitWarningModal } from '../../controllers/exit-warning-modal-context';
|
|
9
13
|
import { useFormContext } from '../../controllers/form-context';
|
|
10
14
|
import { CreateFormFooter } from './form-footer';
|
|
@@ -26,12 +30,17 @@ export const CreateForm = ({
|
|
|
26
30
|
initialValues
|
|
27
31
|
}) => {
|
|
28
32
|
const {
|
|
33
|
+
setFormErrorMessage,
|
|
29
34
|
formErrorMessage,
|
|
30
35
|
enableEditView
|
|
31
36
|
} = useFormContext();
|
|
37
|
+
const intl = useIntl();
|
|
32
38
|
const {
|
|
33
39
|
setShouldShowWarning
|
|
34
40
|
} = useExitWarningModal();
|
|
41
|
+
const {
|
|
42
|
+
onFailure
|
|
43
|
+
} = useLinkCreateCallback();
|
|
35
44
|
const handleSubmit = useCallback(async data => {
|
|
36
45
|
if (getBooleanFF('platform.linking-platform.link-create.enable-edit')) {
|
|
37
46
|
const {
|
|
@@ -45,10 +54,38 @@ export const CreateForm = ({
|
|
|
45
54
|
* if submission is successful
|
|
46
55
|
*/
|
|
47
56
|
enableEditView === null || enableEditView === void 0 ? void 0 : enableEditView(!!shouldEnableEditView);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* This is the onSubmit handler provided by the plugin
|
|
60
|
+
* It will be async, and it will likely involve awaiting `onCreate` (the adopters handler)
|
|
61
|
+
*/
|
|
48
62
|
return onSubmit(formData);
|
|
49
63
|
}
|
|
50
64
|
return onSubmit(data);
|
|
51
65
|
}, [onSubmit, enableEditView]);
|
|
66
|
+
const handleSubmitWithErrorHandling = useCallback(async (...args) => {
|
|
67
|
+
try {
|
|
68
|
+
/**
|
|
69
|
+
* Clear any error message that may have been set by async select fields
|
|
70
|
+
* This will immediately remove any indication of an error, but the form likely will fail to submit,
|
|
71
|
+
* it will be likely a 400 because the user probably could not set all fields anyway
|
|
72
|
+
*/
|
|
73
|
+
setFormErrorMessage();
|
|
74
|
+
return await handleSubmit(...args);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
/**
|
|
77
|
+
* Notify link create of failed experience
|
|
78
|
+
*/
|
|
79
|
+
onFailure === null || onFailure === void 0 ? void 0 : onFailure(error);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Return a generic message for react final form to render
|
|
83
|
+
*/
|
|
84
|
+
return {
|
|
85
|
+
[FORM_ERROR]: intl.formatMessage(messages.genericErrorMessage)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}, [handleSubmit, setFormErrorMessage, intl, onFailure]);
|
|
52
89
|
const handleCancel = useCallback(() => {
|
|
53
90
|
onCancel && onCancel();
|
|
54
91
|
}, [onCancel]);
|
|
@@ -56,7 +93,7 @@ export const CreateForm = ({
|
|
|
56
93
|
return jsx(CreateFormLoader, null);
|
|
57
94
|
}
|
|
58
95
|
return jsx(Form, {
|
|
59
|
-
onSubmit: handleSubmit,
|
|
96
|
+
onSubmit: getBooleanFF('platform.linking-platform.link-create.better-observability') ? handleSubmitWithErrorHandling : handleSubmit,
|
|
60
97
|
initialValues: initialValues,
|
|
61
98
|
mutators: {
|
|
62
99
|
setField: (args, state, tools) => {
|
|
@@ -65,6 +102,7 @@ export const CreateForm = ({
|
|
|
65
102
|
}
|
|
66
103
|
}, ({
|
|
67
104
|
submitting,
|
|
105
|
+
submitError,
|
|
68
106
|
...formProps
|
|
69
107
|
}) => {
|
|
70
108
|
return jsx("form", {
|
|
@@ -85,8 +123,14 @@ export const CreateForm = ({
|
|
|
85
123
|
const isModified = Object.values(state.modified).some(value => value);
|
|
86
124
|
setShouldShowWarning(isModified);
|
|
87
125
|
}
|
|
88
|
-
}), jsx(Box, null, children), !hideFooter && jsx(CreateFormFooter
|
|
89
|
-
|
|
126
|
+
}), jsx(Box, null, children), !hideFooter && jsx(CreateFormFooter
|
|
127
|
+
/**
|
|
128
|
+
* We will prefer to render the error message connected to
|
|
129
|
+
* react final form state (submitError) otherwise we can
|
|
130
|
+
* default to the `formErrorMessage` that we sometimes use with our own
|
|
131
|
+
* "form context" (only currently used for AsyncSelect field reporting failed loading)
|
|
132
|
+
*/, {
|
|
133
|
+
formErrorMessage: getBooleanFF('platform.linking-platform.link-create.better-observability') ? submitError || formErrorMessage : formErrorMessage,
|
|
90
134
|
handleCancel: handleCancel,
|
|
91
135
|
submitting: submitting,
|
|
92
136
|
testId: testId
|