@atlaskit/jql-editor 5.0.7 → 5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/jql-editor
2
2
 
3
+ ## 5.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#129443](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/129443)
8
+ [`cea97763ee228`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/cea97763ee228) -
9
+ Pass down validation error messages and extra IDs for a11y [GRAVITYAI-2553]
10
+
11
+ ## 5.0.8
12
+
13
+ ### Patch Changes
14
+
15
+ - [#129314](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/129314)
16
+ [`439b876d25b73`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/439b876d25b73) -
17
+ Improved error logging for editor transactions.
18
+
3
19
  ## 5.0.7
4
20
 
5
21
  ### Patch Changes
@@ -6,5 +6,5 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.useJqlEditorAnalytics = void 0;
7
7
  var _jqlEditorCommon = require("@atlaskit/jql-editor-common");
8
8
  var useJqlEditorAnalytics = exports.useJqlEditorAnalytics = function useJqlEditorAnalytics(analyticsSource) {
9
- return (0, _jqlEditorCommon.useJqlPackageAnalytics)(analyticsSource, "@atlaskit/jql-editor", "5.0.7", _jqlEditorCommon.ANALYTICS_CHANNEL);
9
+ return (0, _jqlEditorCommon.useJqlPackageAnalytics)(analyticsSource, "@atlaskit/jql-editor", "5.1.0", _jqlEditorCommon.ANALYTICS_CHANNEL);
10
10
  };
@@ -452,10 +452,25 @@ var actions = exports.actions = {
452
452
  query = _getState7.query,
453
453
  editorState = _getState7.editorState,
454
454
  editorView = _getState7.editorView,
455
- enableRichInlineNodes = _getState7.enableRichInlineNodes;
455
+ enableRichInlineNodes = _getState7.enableRichInlineNodes,
456
+ onDebugUnsafeMessage = _getState7.onDebugUnsafeMessage;
456
457
  var oldSelection = editorState.selection;
457
458
  var updatedQuery = (0, _documentText.getNodeText)(transaction.doc, 0, transaction.doc.content.size);
458
- var updatedEditorState = editorState.apply(transaction);
459
+ var updatedEditorState;
460
+ try {
461
+ updatedEditorState = editorState.apply(transaction);
462
+ } catch (error) {
463
+ // We've observed several errors in Splunk from this step but we're unsure how to reproduce it. It seems to be some type of
464
+ // race condition where the transaction is applied to the editor state but the editor state is being updated in another transaction.
465
+ if (error instanceof RangeError && editorView) {
466
+ var message = "Error occurred trying to update editor state with the message: ".concat(error.message);
467
+ (0, _util.sendDebugMessage)(message, editorView, editorState, onDebugUnsafeMessage, {
468
+ stack: error.stack,
469
+ transaction: JSON.stringify(transaction)
470
+ });
471
+ }
472
+ throw error;
473
+ }
459
474
 
460
475
  // Update state in our editor view
461
476
  if (editorView) {
@@ -61,6 +61,65 @@ var useFormattedErrorMessage = function useFormattedErrorMessage() {
61
61
  }
62
62
  return null;
63
63
  };
64
+
65
+ /**
66
+ * This function maps client side validation errors and external messages into a unified format
67
+ * This is so that the output of this function can then be processed in a unified manner by different renderers
68
+ *
69
+ * ------ REMOVE THE FOLLOWING COMMENTS WITH THE FG CLEANUP OF gravityai-2553-fix-jql-debugger-flicker -----
70
+ * Cloned from useFormattedErrorMessage. Differences are:
71
+ * - return type is ExternalMessage[] instead of ReactNode
72
+ * - Stripped rendering logic away (i.e. FormatMessages) to make the implementation modular
73
+ */
74
+ var useErrorMessages = function useErrorMessages() {
75
+ var _useIntl3 = (0, _state.useIntl)(),
76
+ _useIntl4 = (0, _slicedToArray2.default)(_useIntl3, 1),
77
+ formatMessage = _useIntl4[0].formatMessage;
78
+ var _useJqlError3 = (0, _state.useJqlError)(),
79
+ _useJqlError4 = (0, _slicedToArray2.default)(_useJqlError3, 1),
80
+ jqlError = _useJqlError4[0];
81
+ var _useExternalMessages3 = (0, _state.useExternalMessages)(),
82
+ _useExternalMessages4 = (0, _slicedToArray2.default)(_useExternalMessages3, 1),
83
+ externalErrors = _useExternalMessages4[0].errors;
84
+ var _useStoreActions3 = (0, _state.useStoreActions)(),
85
+ _useStoreActions4 = (0, _slicedToArray2.default)(_useStoreActions3, 2),
86
+ externalErrorMessageViewed = _useStoreActions4[1].externalErrorMessageViewed;
87
+ var editorViewIsInvalid = (0, _useEditorViewIsInvalid.useEditorViewIsInvalid)();
88
+ (0, _react.useEffect)(function () {
89
+ // Emit a UI event whenever external errors has changed
90
+ externalErrorMessageViewed();
91
+ }, [externalErrorMessageViewed, externalErrors]);
92
+ if (!editorViewIsInvalid) {
93
+ return null;
94
+ }
95
+
96
+ /**
97
+ * Transform a string message to take the form of an ExternalMessage,
98
+ * so that we can reduce a bunch of conditionals when processing the output of useErrorMessages
99
+ */
100
+ var toExternalMessageShape = function toExternalMessageShape(message) {
101
+ return [{
102
+ type: 'error',
103
+ message: message
104
+ }];
105
+ };
106
+
107
+ // Prioritise client errors over external errors
108
+ if (jqlError instanceof _jqlAst.JQLSyntaxError) {
109
+ return toExternalMessageShape("".concat(jqlError.message, " ").concat(formatMessage(_messages2.messages.jqlErrorPosition, {
110
+ lineNumber: jqlError.line,
111
+ charPosition: jqlError.charPositionInLine + 1
112
+ })));
113
+ }
114
+ if (externalErrors.length > 0) {
115
+ return externalErrors;
116
+ }
117
+ if (jqlError !== null) {
118
+ return toExternalMessageShape(formatMessage(_messages.commonMessages.unknownError));
119
+ }
120
+ return null;
121
+ };
122
+
64
123
  /**
65
124
  * This is a decorator component to include the editorTheme prop to the already passed list of props
66
125
  */
@@ -86,10 +145,41 @@ var ErrorMessages = exports.ErrorMessages = function ErrorMessages() {
86
145
  var _useScopedId3 = (0, _state.useScopedId)(_constants.JQL_EDITOR_VALIDATION_ID),
87
146
  _useScopedId4 = (0, _slicedToArray2.default)(_useScopedId3, 1),
88
147
  validationId = _useScopedId4[0];
89
- var errorMessage = useFormattedErrorMessage();
148
+ // eslint-disable-next-line react-hooks/rules-of-hooks
149
+ var errorMessages = (0, _platformFeatureFlags.fg)('gravityai-2553-fix-jql-debugger-flicker') ? useErrorMessages() : null;
150
+ var errorMessage = !(0, _platformFeatureFlags.fg)('gravityai-2553-fix-jql-debugger-flicker') ?
151
+ // eslint-disable-next-line react-hooks/rules-of-hooks
152
+ useFormattedErrorMessage() : null;
90
153
  var _useCustomErrorCompon = (0, _state.useCustomErrorComponent)(),
91
154
  _useCustomErrorCompon2 = (0, _slicedToArray2.default)(_useCustomErrorCompon, 1),
92
155
  CustomErrorComponent = _useCustomErrorCompon2[0];
156
+ if ((0, _platformFeatureFlags.fg)('gravityai-2553-fix-jql-debugger-flicker')) {
157
+ var _childrenToRender = errorMessages != null ? /*#__PURE__*/_react.default.createElement(_format.MessageContainer, null, /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, {
158
+ testId: _constants.JQL_EDITOR_VALIDATION_ID
159
+ }, (0, _platformFeatureFlags.fg)('jql_editor_a11y') ? /*#__PURE__*/_react.default.createElement("span", {
160
+ role: "alert",
161
+ id: validationId
162
+ }, /*#__PURE__*/_react.default.createElement(_format.FormatMessages, {
163
+ messages: errorMessages
164
+ })) : /*#__PURE__*/_react.default.createElement("span", {
165
+ role: "alert",
166
+ "aria-describedby": editorId
167
+ }, /*#__PURE__*/_react.default.createElement(_format.FormatMessages, {
168
+ messages: errorMessages
169
+ })))) : null;
170
+
171
+ // Only render CustomErrorComponent if there is an errorMessage
172
+ if (errorMessages != null && CustomErrorComponent) {
173
+ return /*#__PURE__*/_react.default.createElement(CustomComponentDecoratedWithEditorTheme, {
174
+ testId: _constants.JQL_EDITOR_VALIDATION_ID,
175
+ editorId: editorId,
176
+ validationId: validationId,
177
+ Component: CustomErrorComponent,
178
+ errorMessages: (0, _format.extractMessageNodes)(errorMessages)
179
+ }, _childrenToRender);
180
+ }
181
+ return _childrenToRender;
182
+ }
93
183
  var childrenToRender = errorMessage != null ? /*#__PURE__*/_react.default.createElement(_format.MessageContainer, null, /*#__PURE__*/_react.default.createElement(_form.ErrorMessage, {
94
184
  testId: _constants.JQL_EDITOR_VALIDATION_ID
95
185
  }, (0, _platformFeatureFlags.fg)('jql_editor_a11y') ? /*#__PURE__*/_react.default.createElement("span", {
@@ -4,8 +4,9 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.MessageContainer = exports.FormatMessages = void 0;
7
+ exports.extractMessageNodes = exports.MessageContainer = exports.FormatMessages = void 0;
8
8
  var _react = _interopRequireDefault(require("react"));
9
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
9
10
  var _useEditorTheme = require("../../../../hooks/use-editor-theme");
10
11
  var _styled = require("./styled");
11
12
  // Max number messages we want to show
@@ -20,6 +21,20 @@ var MessageContainer = exports.MessageContainer = function MessageContainer(_ref
20
21
  };
21
22
  var FormatMessages = exports.FormatMessages = function FormatMessages(_ref2) {
22
23
  var messages = _ref2.messages;
24
+ if ((0, _platformFeatureFlags.fg)('gravityai-2553-fix-jql-debugger-flicker')) {
25
+ var messageNodes = extractMessageNodes(messages);
26
+ if (messageNodes.length === 0) {
27
+ return null;
28
+ }
29
+ if (messageNodes.length === 1) {
30
+ return /*#__PURE__*/_react.default.createElement("span", null, messageNodes[0]);
31
+ }
32
+ return /*#__PURE__*/_react.default.createElement(_styled.MessageList, null, messageNodes.map(function (m, i) {
33
+ return /*#__PURE__*/_react.default.createElement("li", {
34
+ key: i
35
+ }, m);
36
+ }));
37
+ }
23
38
  if (messages.length === 0) {
24
39
  return null;
25
40
  }
@@ -31,4 +46,16 @@ var FormatMessages = exports.FormatMessages = function FormatMessages(_ref2) {
31
46
  key: i
32
47
  }, m.message);
33
48
  }));
49
+ };
50
+
51
+ /**
52
+ * This function was extracted from FormatMessages, so that the rendering is decoupled from the logic
53
+ * This is so that the extractMessageNodes can used elsewhere where rendering is delegated to a different renderer
54
+ *
55
+ * Simply put, this function only handles getting m.message, and limiting to MAX_MESSAGES
56
+ */
57
+ var extractMessageNodes = exports.extractMessageNodes = function extractMessageNodes(messages) {
58
+ return messages.slice(0, MAX_MESSAGES).map(function (m) {
59
+ return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, m.message);
60
+ });
34
61
  };
@@ -1,4 +1,4 @@
1
1
  import { ANALYTICS_CHANNEL, useJqlPackageAnalytics } from '@atlaskit/jql-editor-common';
2
2
  export const useJqlEditorAnalytics = analyticsSource => {
3
- return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "5.0.7", ANALYTICS_CHANNEL);
3
+ return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "5.1.0", ANALYTICS_CHANNEL);
4
4
  };
@@ -463,11 +463,26 @@ export const actions = {
463
463
  query,
464
464
  editorState,
465
465
  editorView,
466
- enableRichInlineNodes
466
+ enableRichInlineNodes,
467
+ onDebugUnsafeMessage
467
468
  } = getState();
468
469
  const oldSelection = editorState.selection;
469
470
  const updatedQuery = getNodeText(transaction.doc, 0, transaction.doc.content.size);
470
- const updatedEditorState = editorState.apply(transaction);
471
+ let updatedEditorState;
472
+ try {
473
+ updatedEditorState = editorState.apply(transaction);
474
+ } catch (error) {
475
+ // We've observed several errors in Splunk from this step but we're unsure how to reproduce it. It seems to be some type of
476
+ // race condition where the transaction is applied to the editor state but the editor state is being updated in another transaction.
477
+ if (error instanceof RangeError && editorView) {
478
+ const message = `Error occurred trying to update editor state with the message: ${error.message}`;
479
+ sendDebugMessage(message, editorView, editorState, onDebugUnsafeMessage, {
480
+ stack: error.stack,
481
+ transaction: JSON.stringify(transaction)
482
+ });
483
+ }
484
+ throw error;
485
+ }
471
486
 
472
487
  // Update state in our editor view
473
488
  if (editorView) {
@@ -9,7 +9,7 @@ import { commonMessages } from '../../../../common/messages';
9
9
  import { useEditorThemeContext } from '../../../../hooks/use-editor-theme';
10
10
  import { useEditorViewIsInvalid } from '../../../../hooks/use-editor-view-is-invalid';
11
11
  import { useCustomErrorComponent, useExternalMessages, useIntl, useJqlError, useScopedId, useStoreActions } from '../../../../state';
12
- import { FormatMessages, MessageContainer } from '../format';
12
+ import { extractMessageNodes, FormatMessages, MessageContainer } from '../format';
13
13
  import { messages } from './messages';
14
14
  const useFormattedErrorMessage = () => {
15
15
  const [{
@@ -46,6 +46,61 @@ const useFormattedErrorMessage = () => {
46
46
  }
47
47
  return null;
48
48
  };
49
+
50
+ /**
51
+ * This function maps client side validation errors and external messages into a unified format
52
+ * This is so that the output of this function can then be processed in a unified manner by different renderers
53
+ *
54
+ * ------ REMOVE THE FOLLOWING COMMENTS WITH THE FG CLEANUP OF gravityai-2553-fix-jql-debugger-flicker -----
55
+ * Cloned from useFormattedErrorMessage. Differences are:
56
+ * - return type is ExternalMessage[] instead of ReactNode
57
+ * - Stripped rendering logic away (i.e. FormatMessages) to make the implementation modular
58
+ */
59
+ const useErrorMessages = () => {
60
+ const [{
61
+ formatMessage
62
+ }] = useIntl();
63
+ const [jqlError] = useJqlError();
64
+ const [{
65
+ errors: externalErrors
66
+ }] = useExternalMessages();
67
+ const [, {
68
+ externalErrorMessageViewed
69
+ }] = useStoreActions();
70
+ const editorViewIsInvalid = useEditorViewIsInvalid();
71
+ useEffect(() => {
72
+ // Emit a UI event whenever external errors has changed
73
+ externalErrorMessageViewed();
74
+ }, [externalErrorMessageViewed, externalErrors]);
75
+ if (!editorViewIsInvalid) {
76
+ return null;
77
+ }
78
+
79
+ /**
80
+ * Transform a string message to take the form of an ExternalMessage,
81
+ * so that we can reduce a bunch of conditionals when processing the output of useErrorMessages
82
+ */
83
+ const toExternalMessageShape = message => [{
84
+ type: 'error',
85
+ message
86
+ }];
87
+
88
+ // Prioritise client errors over external errors
89
+ if (jqlError instanceof JQLSyntaxError) {
90
+ return toExternalMessageShape(`${jqlError.message} ${formatMessage(messages.jqlErrorPosition, {
91
+ lineNumber: jqlError.line,
92
+ charPosition: jqlError.charPositionInLine + 1
93
+ })}`);
94
+ }
95
+ if (externalErrors.length > 0) {
96
+ return externalErrors;
97
+ }
98
+ if (jqlError !== null) {
99
+ return toExternalMessageShape(formatMessage(commonMessages.unknownError));
100
+ }
101
+ return null;
102
+ };
103
+
49
104
  /**
50
105
  * This is a decorator component to include the editorTheme prop to the already passed list of props
51
106
  */
@@ -69,8 +124,39 @@ const CustomComponentDecoratedWithEditorTheme = props => {
69
124
  export const ErrorMessages = () => {
70
125
  const [editorId] = useScopedId(JQL_EDITOR_INPUT_ID);
71
126
  const [validationId] = useScopedId(JQL_EDITOR_VALIDATION_ID);
72
- const errorMessage = useFormattedErrorMessage();
127
+ // eslint-disable-next-line react-hooks/rules-of-hooks
128
+ const errorMessages = fg('gravityai-2553-fix-jql-debugger-flicker') ? useErrorMessages() : null;
129
+ const errorMessage = !fg('gravityai-2553-fix-jql-debugger-flicker') ?
130
+ // eslint-disable-next-line react-hooks/rules-of-hooks
131
+ useFormattedErrorMessage() : null;
73
132
  const [CustomErrorComponent] = useCustomErrorComponent();
133
+ if (fg('gravityai-2553-fix-jql-debugger-flicker')) {
134
+ const childrenToRender = errorMessages != null ? /*#__PURE__*/React.createElement(MessageContainer, null, /*#__PURE__*/React.createElement(ErrorMessage, {
135
+ testId: JQL_EDITOR_VALIDATION_ID
136
+ }, fg('jql_editor_a11y') ? /*#__PURE__*/React.createElement("span", {
137
+ role: "alert",
138
+ id: validationId
139
+ }, /*#__PURE__*/React.createElement(FormatMessages, {
140
+ messages: errorMessages
141
+ })) : /*#__PURE__*/React.createElement("span", {
142
+ role: "alert",
143
+ "aria-describedby": editorId
144
+ }, /*#__PURE__*/React.createElement(FormatMessages, {
145
+ messages: errorMessages
146
+ })))) : null;
147
+
148
+ // Only render CustomErrorComponent if there is an errorMessage
149
+ if (errorMessages != null && CustomErrorComponent) {
150
+ return /*#__PURE__*/React.createElement(CustomComponentDecoratedWithEditorTheme, {
151
+ testId: JQL_EDITOR_VALIDATION_ID,
152
+ editorId: editorId,
153
+ validationId: validationId,
154
+ Component: CustomErrorComponent,
155
+ errorMessages: extractMessageNodes(errorMessages)
156
+ }, childrenToRender);
157
+ }
158
+ return childrenToRender;
159
+ }
74
160
  const childrenToRender = errorMessage != null ? /*#__PURE__*/React.createElement(MessageContainer, null, /*#__PURE__*/React.createElement(ErrorMessage, {
75
161
  testId: JQL_EDITOR_VALIDATION_ID
76
162
  }, fg('jql_editor_a11y') ? /*#__PURE__*/React.createElement("span", {
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
2
3
  import { useEditorThemeContext } from '../../../../hooks/use-editor-theme';
3
4
  import { MessageContainer as MessageContainerStyled, MessageList } from './styled';
4
5
 
@@ -17,6 +18,18 @@ export const MessageContainer = ({
17
18
  export const FormatMessages = ({
18
19
  messages
19
20
  }) => {
21
+ if (fg('gravityai-2553-fix-jql-debugger-flicker')) {
22
+ const messageNodes = extractMessageNodes(messages);
23
+ if (messageNodes.length === 0) {
24
+ return null;
25
+ }
26
+ if (messageNodes.length === 1) {
27
+ return /*#__PURE__*/React.createElement("span", null, messageNodes[0]);
28
+ }
29
+ return /*#__PURE__*/React.createElement(MessageList, null, messageNodes.map((m, i) => /*#__PURE__*/React.createElement("li", {
30
+ key: i
31
+ }, m)));
32
+ }
20
33
  if (messages.length === 0) {
21
34
  return null;
22
35
  }
@@ -26,4 +39,14 @@ export const FormatMessages = ({
26
39
  return /*#__PURE__*/React.createElement(MessageList, null, messages.slice(0, MAX_MESSAGES).map((m, i) => /*#__PURE__*/React.createElement("li", {
27
40
  key: i
28
41
  }, m.message)));
42
+ };
43
+
44
+ /**
45
+ * This function was extracted from FormatMessages, so that the rendering is decoupled from the logic
46
+ * This is so that the extractMessageNodes can used elsewhere where rendering is delegated to a different renderer
47
+ *
48
+ * Simply put, this function only handles getting m.message, and limiting to MAX_MESSAGES
49
+ */
50
+ export const extractMessageNodes = messages => {
51
+ return messages.slice(0, MAX_MESSAGES).map(m => /*#__PURE__*/React.createElement(React.Fragment, null, m.message));
29
52
  };
@@ -1,4 +1,4 @@
1
1
  import { ANALYTICS_CHANNEL, useJqlPackageAnalytics } from '@atlaskit/jql-editor-common';
2
2
  export var useJqlEditorAnalytics = function useJqlEditorAnalytics(analyticsSource) {
3
- return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "5.0.7", ANALYTICS_CHANNEL);
3
+ return useJqlPackageAnalytics(analyticsSource, "@atlaskit/jql-editor", "5.1.0", ANALYTICS_CHANNEL);
4
4
  };
@@ -445,10 +445,25 @@ export var actions = {
445
445
  query = _getState7.query,
446
446
  editorState = _getState7.editorState,
447
447
  editorView = _getState7.editorView,
448
- enableRichInlineNodes = _getState7.enableRichInlineNodes;
448
+ enableRichInlineNodes = _getState7.enableRichInlineNodes,
449
+ onDebugUnsafeMessage = _getState7.onDebugUnsafeMessage;
449
450
  var oldSelection = editorState.selection;
450
451
  var updatedQuery = getNodeText(transaction.doc, 0, transaction.doc.content.size);
451
- var updatedEditorState = editorState.apply(transaction);
452
+ var updatedEditorState;
453
+ try {
454
+ updatedEditorState = editorState.apply(transaction);
455
+ } catch (error) {
456
+ // We've observed several errors in Splunk from this step but we're unsure how to reproduce it. It seems to be some type of
457
+ // race condition where the transaction is applied to the editor state but the editor state is being updated in another transaction.
458
+ if (error instanceof RangeError && editorView) {
459
+ var message = "Error occurred trying to update editor state with the message: ".concat(error.message);
460
+ sendDebugMessage(message, editorView, editorState, onDebugUnsafeMessage, {
461
+ stack: error.stack,
462
+ transaction: JSON.stringify(transaction)
463
+ });
464
+ }
465
+ throw error;
466
+ }
452
467
 
453
468
  // Update state in our editor view
454
469
  if (editorView) {
@@ -12,7 +12,7 @@ import { commonMessages } from '../../../../common/messages';
12
12
  import { useEditorThemeContext } from '../../../../hooks/use-editor-theme';
13
13
  import { useEditorViewIsInvalid } from '../../../../hooks/use-editor-view-is-invalid';
14
14
  import { useCustomErrorComponent, useExternalMessages, useIntl, useJqlError, useScopedId, useStoreActions } from '../../../../state';
15
- import { FormatMessages, MessageContainer } from '../format';
15
+ import { extractMessageNodes, FormatMessages, MessageContainer } from '../format';
16
16
  import { messages } from './messages';
17
17
  var useFormattedErrorMessage = function useFormattedErrorMessage() {
18
18
  var _useIntl = useIntl(),
@@ -51,6 +51,65 @@ var useFormattedErrorMessage = function useFormattedErrorMessage() {
51
51
  }
52
52
  return null;
53
53
  };
54
+
55
+ /**
56
+ * This function maps client side validation errors and external messages into a unified format
57
+ * This is so that the output of this function can then be processed in a unified manner by different renderers
58
+ *
59
+ * ------ REMOVE THE FOLLOWING COMMENTS WITH THE FG CLEANUP OF gravityai-2553-fix-jql-debugger-flicker -----
60
+ * Cloned from useFormattedErrorMessage. Differences are:
61
+ * - return type is ExternalMessage[] instead of ReactNode
62
+ * - Stripped rendering logic away (i.e. FormatMessages) to make the implementation modular
63
+ */
64
+ var useErrorMessages = function useErrorMessages() {
65
+ var _useIntl3 = useIntl(),
66
+ _useIntl4 = _slicedToArray(_useIntl3, 1),
67
+ formatMessage = _useIntl4[0].formatMessage;
68
+ var _useJqlError3 = useJqlError(),
69
+ _useJqlError4 = _slicedToArray(_useJqlError3, 1),
70
+ jqlError = _useJqlError4[0];
71
+ var _useExternalMessages3 = useExternalMessages(),
72
+ _useExternalMessages4 = _slicedToArray(_useExternalMessages3, 1),
73
+ externalErrors = _useExternalMessages4[0].errors;
74
+ var _useStoreActions3 = useStoreActions(),
75
+ _useStoreActions4 = _slicedToArray(_useStoreActions3, 2),
76
+ externalErrorMessageViewed = _useStoreActions4[1].externalErrorMessageViewed;
77
+ var editorViewIsInvalid = useEditorViewIsInvalid();
78
+ useEffect(function () {
79
+ // Emit a UI event whenever external errors has changed
80
+ externalErrorMessageViewed();
81
+ }, [externalErrorMessageViewed, externalErrors]);
82
+ if (!editorViewIsInvalid) {
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * Transform a string message to take the form of an ExternalMessage,
88
+ * so that we can reduce a bunch of conditionals when processing the output of useErrorMessages
89
+ */
90
+ var toExternalMessageShape = function toExternalMessageShape(message) {
91
+ return [{
92
+ type: 'error',
93
+ message: message
94
+ }];
95
+ };
96
+
97
+ // Prioritise client errors over external errors
98
+ if (jqlError instanceof JQLSyntaxError) {
99
+ return toExternalMessageShape("".concat(jqlError.message, " ").concat(formatMessage(messages.jqlErrorPosition, {
100
+ lineNumber: jqlError.line,
101
+ charPosition: jqlError.charPositionInLine + 1
102
+ })));
103
+ }
104
+ if (externalErrors.length > 0) {
105
+ return externalErrors;
106
+ }
107
+ if (jqlError !== null) {
108
+ return toExternalMessageShape(formatMessage(commonMessages.unknownError));
109
+ }
110
+ return null;
111
+ };
112
+
54
113
  /**
55
114
  * This is a decorator component to include the editorTheme prop to the already passed list of props
56
115
  */
@@ -76,10 +135,41 @@ export var ErrorMessages = function ErrorMessages() {
76
135
  var _useScopedId3 = useScopedId(JQL_EDITOR_VALIDATION_ID),
77
136
  _useScopedId4 = _slicedToArray(_useScopedId3, 1),
78
137
  validationId = _useScopedId4[0];
79
- var errorMessage = useFormattedErrorMessage();
138
+ // eslint-disable-next-line react-hooks/rules-of-hooks
139
+ var errorMessages = fg('gravityai-2553-fix-jql-debugger-flicker') ? useErrorMessages() : null;
140
+ var errorMessage = !fg('gravityai-2553-fix-jql-debugger-flicker') ?
141
+ // eslint-disable-next-line react-hooks/rules-of-hooks
142
+ useFormattedErrorMessage() : null;
80
143
  var _useCustomErrorCompon = useCustomErrorComponent(),
81
144
  _useCustomErrorCompon2 = _slicedToArray(_useCustomErrorCompon, 1),
82
145
  CustomErrorComponent = _useCustomErrorCompon2[0];
146
+ if (fg('gravityai-2553-fix-jql-debugger-flicker')) {
147
+ var _childrenToRender = errorMessages != null ? /*#__PURE__*/React.createElement(MessageContainer, null, /*#__PURE__*/React.createElement(ErrorMessage, {
148
+ testId: JQL_EDITOR_VALIDATION_ID
149
+ }, fg('jql_editor_a11y') ? /*#__PURE__*/React.createElement("span", {
150
+ role: "alert",
151
+ id: validationId
152
+ }, /*#__PURE__*/React.createElement(FormatMessages, {
153
+ messages: errorMessages
154
+ })) : /*#__PURE__*/React.createElement("span", {
155
+ role: "alert",
156
+ "aria-describedby": editorId
157
+ }, /*#__PURE__*/React.createElement(FormatMessages, {
158
+ messages: errorMessages
159
+ })))) : null;
160
+
161
+ // Only render CustomErrorComponent if there is an errorMessage
162
+ if (errorMessages != null && CustomErrorComponent) {
163
+ return /*#__PURE__*/React.createElement(CustomComponentDecoratedWithEditorTheme, {
164
+ testId: JQL_EDITOR_VALIDATION_ID,
165
+ editorId: editorId,
166
+ validationId: validationId,
167
+ Component: CustomErrorComponent,
168
+ errorMessages: extractMessageNodes(errorMessages)
169
+ }, _childrenToRender);
170
+ }
171
+ return _childrenToRender;
172
+ }
83
173
  var childrenToRender = errorMessage != null ? /*#__PURE__*/React.createElement(MessageContainer, null, /*#__PURE__*/React.createElement(ErrorMessage, {
84
174
  testId: JQL_EDITOR_VALIDATION_ID
85
175
  }, fg('jql_editor_a11y') ? /*#__PURE__*/React.createElement("span", {
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
2
3
  import { useEditorThemeContext } from '../../../../hooks/use-editor-theme';
3
4
  import { MessageContainer as MessageContainerStyled, MessageList } from './styled';
4
5
 
@@ -14,6 +15,20 @@ export var MessageContainer = function MessageContainer(_ref) {
14
15
  };
15
16
  export var FormatMessages = function FormatMessages(_ref2) {
16
17
  var messages = _ref2.messages;
18
+ if (fg('gravityai-2553-fix-jql-debugger-flicker')) {
19
+ var messageNodes = extractMessageNodes(messages);
20
+ if (messageNodes.length === 0) {
21
+ return null;
22
+ }
23
+ if (messageNodes.length === 1) {
24
+ return /*#__PURE__*/React.createElement("span", null, messageNodes[0]);
25
+ }
26
+ return /*#__PURE__*/React.createElement(MessageList, null, messageNodes.map(function (m, i) {
27
+ return /*#__PURE__*/React.createElement("li", {
28
+ key: i
29
+ }, m);
30
+ }));
31
+ }
17
32
  if (messages.length === 0) {
18
33
  return null;
19
34
  }
@@ -25,4 +40,16 @@ export var FormatMessages = function FormatMessages(_ref2) {
25
40
  key: i
26
41
  }, m.message);
27
42
  }));
43
+ };
44
+
45
+ /**
46
+ * This function was extracted from FormatMessages, so that the rendering is decoupled from the logic
47
+ * This is so that the extractMessageNodes can used elsewhere where rendering is delegated to a different renderer
48
+ *
49
+ * Simply put, this function only handles getting m.message, and limiting to MAX_MESSAGES
50
+ */
51
+ export var extractMessageNodes = function extractMessageNodes(messages) {
52
+ return messages.slice(0, MAX_MESSAGES).map(function (m) {
53
+ return /*#__PURE__*/React.createElement(React.Fragment, null, m.message);
54
+ });
28
55
  };
@@ -69,7 +69,12 @@ export type CustomErrorComponent = React.ComponentType<PropsWithChildren<{
69
69
  testId: string;
70
70
  editorTheme: EditorTheme;
71
71
  editorId: string;
72
+ errorMessages?: React.ReactElement[];
73
+ validationId?: string;
72
74
  }>>;
75
+ export type CustomErrorMessageProps = Omit<React.ComponentProps<CustomErrorComponent>, 'editorTheme'> & {
76
+ Component: CustomErrorComponent;
77
+ };
73
78
  export type CustomComponents = {
74
79
  ErrorMessage?: CustomErrorComponent;
75
80
  };
@@ -6,3 +6,10 @@ export declare const MessageContainer: ({ children }: {
6
6
  export declare const FormatMessages: ({ messages }: {
7
7
  messages: ExternalMessage[];
8
8
  }) => React.JSX.Element | null;
9
+ /**
10
+ * This function was extracted from FormatMessages, so that the rendering is decoupled from the logic
11
+ * This is so that the extractMessageNodes can used elsewhere where rendering is delegated to a different renderer
12
+ *
13
+ * Simply put, this function only handles getting m.message, and limiting to MAX_MESSAGES
14
+ */
15
+ export declare const extractMessageNodes: (messages: ExternalMessage[]) => React.ReactElement[];
@@ -69,7 +69,12 @@ export type CustomErrorComponent = React.ComponentType<PropsWithChildren<{
69
69
  testId: string;
70
70
  editorTheme: EditorTheme;
71
71
  editorId: string;
72
+ errorMessages?: React.ReactElement[];
73
+ validationId?: string;
72
74
  }>>;
75
+ export type CustomErrorMessageProps = Omit<React.ComponentProps<CustomErrorComponent>, 'editorTheme'> & {
76
+ Component: CustomErrorComponent;
77
+ };
73
78
  export type CustomComponents = {
74
79
  ErrorMessage?: CustomErrorComponent;
75
80
  };
@@ -6,3 +6,10 @@ export declare const MessageContainer: ({ children }: {
6
6
  export declare const FormatMessages: ({ messages }: {
7
7
  messages: ExternalMessage[];
8
8
  }) => React.JSX.Element | null;
9
+ /**
10
+ * This function was extracted from FormatMessages, so that the rendering is decoupled from the logic
11
+ * This is so that the extractMessageNodes can used elsewhere where rendering is delegated to a different renderer
12
+ *
13
+ * Simply put, this function only handles getting m.message, and limiting to MAX_MESSAGES
14
+ */
15
+ export declare const extractMessageNodes: (messages: ExternalMessage[]) => React.ReactElement[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/jql-editor",
3
- "version": "5.0.7",
3
+ "version": "5.1.0",
4
4
  "description": "This package allows consumers to render an advanced JQL editor component to enable autocomplete-assisted authoring and validation of JQL queries.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -50,10 +50,10 @@
50
50
  "@atlaskit/jql-parser": "^2.0.0",
51
51
  "@atlaskit/legacy-custom-icons": "^0.22.0",
52
52
  "@atlaskit/platform-feature-flags": "^1.1.0",
53
- "@atlaskit/primitives": "^14.1.0",
53
+ "@atlaskit/primitives": "^14.2.0",
54
54
  "@atlaskit/spinner": "^18.0.0",
55
55
  "@atlaskit/theme": "^18.0.0",
56
- "@atlaskit/tokens": "^4.4.0",
56
+ "@atlaskit/tokens": "^4.5.0",
57
57
  "@atlaskit/tooltip": "^20.0.0",
58
58
  "@babel/runtime": "^7.0.0",
59
59
  "@emotion/react": "^11.7.1",
@@ -136,6 +136,9 @@
136
136
  },
137
137
  "jql_editor_a11y": {
138
138
  "type": "boolean"
139
+ },
140
+ "gravityai-2553-fix-jql-debugger-flicker": {
141
+ "type": "boolean"
139
142
  }
140
143
  }
141
144
  }