Skip to content

Commit 63f75bd

Browse files
[Security Solution] [Onboarding] a11y: Fix incorrect navigation after Save on AI assistant modal dialog (elastic#219935)
## Summary Closes: elastic#204211 https://github.com/user-attachments/assets/ce60a37a-913d-4c17-bf3c-0192b1e99ddc ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent 9a12e3b commit 63f75bd

1 file changed

Lines changed: 108 additions & 92 deletions

File tree

  • x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form

x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx

Lines changed: 108 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const ConnectorAddModal = ({
6464
const [allActionTypes, setAllActionTypes] = useState<ActionTypeIndex | undefined>(undefined);
6565
const { isLoading: isSavingConnector, createConnector } = useCreateConnector();
6666
const isMounted = useRef(false);
67+
const containerRef = useRef<HTMLDivElement>(null);
6768
const [initialConnector, setInitialConnector] = useState({
6869
actionTypeId: actionType?.id ?? '',
6970
isDeprecated: false,
@@ -163,6 +164,19 @@ const ConnectorAddModal = ({
163164

164165
const createdConnector = await createConnector(validConnector);
165166
return createdConnector;
167+
} else {
168+
// point the user to the first invalid field
169+
const container = containerRef.current;
170+
if (!container) return;
171+
172+
const selector = 'input[aria-invalid="true"]';
173+
const firstInputInvalid = container.querySelector<HTMLInputElement>(selector);
174+
175+
if (firstInputInvalid) {
176+
window.requestAnimationFrame(() => {
177+
firstInputInvalid.focus({ preventScroll: false });
178+
});
179+
}
166180
}
167181
}, [submit, preSubmitValidator, createConnector]);
168182

@@ -222,108 +236,110 @@ const ConnectorAddModal = ({
222236
className="actConnectorModal"
223237
css={css`
224238
z-index: 9000;
239+
width: ${actionTypeRegistry.get(actionType.id).modalWidth};
225240
`}
226241
data-test-subj="connectorAddModal"
227242
onClose={closeModal}
228-
style={{ width: actionTypeRegistry.get(actionType.id).modalWidth }}
229243
aria-labelledby={modalTitleId}
230244
>
231-
<EuiModalHeader>
232-
<EuiFlexGroup gutterSize="m" alignItems="center">
233-
{actionTypeModel && actionTypeModel.iconClass ? (
245+
<div ref={containerRef}>
246+
<EuiModalHeader>
247+
<EuiFlexGroup gutterSize="m" alignItems="center">
248+
{actionTypeModel && actionTypeModel.iconClass ? (
249+
<EuiFlexItem grow={false}>
250+
<EuiIcon type={actionTypeModel.iconClass} size="xl" />
251+
</EuiFlexItem>
252+
) : null}
234253
<EuiFlexItem grow={false}>
235-
<EuiIcon type={actionTypeModel.iconClass} size="xl" />
254+
<EuiFlexGroup gutterSize="s" justifyContent="center" alignItems="center">
255+
<EuiFlexItem>
256+
<EuiModalHeaderTitle id={modalTitleId} size="s" component="h3">
257+
<FormattedMessage
258+
defaultMessage="{actionTypeName} connector"
259+
id="xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle"
260+
values={{
261+
actionTypeName: actionType.name,
262+
}}
263+
/>
264+
</EuiModalHeaderTitle>
265+
</EuiFlexItem>
266+
{actionTypeModel && actionTypeModel.isExperimental && (
267+
<EuiFlexItem className="betaBadgeFlexItem" grow={false}>
268+
<EuiBetaBadge
269+
label={TECH_PREVIEW_LABEL}
270+
tooltipContent={TECH_PREVIEW_DESCRIPTION}
271+
/>
272+
</EuiFlexItem>
273+
)}
274+
</EuiFlexGroup>
236275
</EuiFlexItem>
237-
) : null}
238-
<EuiFlexItem grow={false}>
239-
<EuiFlexGroup gutterSize="s" justifyContent="center" alignItems="center">
240-
<EuiFlexItem>
241-
<EuiModalHeaderTitle id={modalTitleId} size="s" component="h3">
242-
<FormattedMessage
243-
defaultMessage="{actionTypeName} connector"
244-
id="xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle"
245-
values={{
246-
actionTypeName: actionType.name,
247-
}}
248-
/>
249-
</EuiModalHeaderTitle>
250-
</EuiFlexItem>
251-
{actionTypeModel && actionTypeModel.isExperimental && (
252-
<EuiFlexItem className="betaBadgeFlexItem" grow={false}>
253-
<EuiBetaBadge
254-
label={TECH_PREVIEW_LABEL}
255-
tooltipContent={TECH_PREVIEW_DESCRIPTION}
276+
</EuiFlexGroup>
277+
</EuiModalHeader>
278+
279+
<EuiModalBody>
280+
{loadingActionTypes ? (
281+
<SectionLoading>
282+
<FormattedMessage
283+
id="xpack.triggersActionsUI.sections.connectorAddModal.loadingConnectorTypesDescription"
284+
defaultMessage="Loading connector types…"
285+
/>
286+
</SectionLoading>
287+
) : (
288+
<>
289+
{groupActionTypeModel && (
290+
<>
291+
<EuiButtonGroup
292+
isFullWidth
293+
buttonSize="m"
294+
color="primary"
295+
legend=""
296+
options={groupActionButtons}
297+
idSelected={actionType.id}
298+
onChange={onChangeGroupAction}
299+
data-test-subj="slackTypeChangeButton"
256300
/>
257-
</EuiFlexItem>
301+
<EuiSpacer size="xs" />
302+
</>
258303
)}
259-
</EuiFlexGroup>
260-
</EuiFlexItem>
261-
</EuiFlexGroup>
262-
</EuiModalHeader>
263-
264-
<EuiModalBody>
265-
{loadingActionTypes ? (
266-
<SectionLoading>
267-
<FormattedMessage
268-
id="xpack.triggersActionsUI.sections.connectorAddModal.loadingConnectorTypesDescription"
269-
defaultMessage="Loading connector types…"
270-
/>
271-
</SectionLoading>
272-
) : (
273-
<>
274-
{groupActionTypeModel && (
275-
<>
276-
<EuiButtonGroup
277-
isFullWidth
278-
buttonSize="m"
279-
color="primary"
280-
legend=""
281-
options={groupActionButtons}
282-
idSelected={actionType.id}
283-
onChange={onChangeGroupAction}
284-
data-test-subj="slackTypeChangeButton"
285-
/>
286-
<EuiSpacer size="xs" />
287-
</>
288-
)}
289-
<ConnectorForm
290-
actionTypeModel={actionTypeModel}
291-
connector={initialConnector}
292-
isEdit={false}
293-
onChange={setFormState}
294-
setResetForm={setResetForm}
295-
/>
296-
{preSubmitValidationErrorMessage}
297-
</>
298-
)}
299-
</EuiModalBody>
300-
<EuiModalFooter>
301-
<EuiButtonEmpty onClick={closeModal} isLoading={isSaving}>
302-
{i18n.translate(
303-
'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel',
304-
{
305-
defaultMessage: 'Cancel',
306-
}
304+
<ConnectorForm
305+
actionTypeModel={actionTypeModel}
306+
connector={initialConnector}
307+
isEdit={false}
308+
onChange={setFormState}
309+
setResetForm={setResetForm}
310+
/>
311+
{preSubmitValidationErrorMessage}
312+
</>
307313
)}
308-
</EuiButtonEmpty>
309-
{canSave ? (
310-
<EuiButton
311-
fill
312-
color="primary"
313-
data-test-subj="saveActionButtonModal"
314-
type="submit"
315-
iconType="check"
316-
isLoading={isSaving}
317-
disabled={hasErrors}
318-
onClick={onSubmit}
319-
>
320-
<FormattedMessage
321-
id="xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel"
322-
defaultMessage="Save"
323-
/>
324-
</EuiButton>
325-
) : null}
326-
</EuiModalFooter>
314+
</EuiModalBody>
315+
<EuiModalFooter>
316+
<EuiButtonEmpty onClick={closeModal} isLoading={isSaving}>
317+
{i18n.translate(
318+
'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel',
319+
{
320+
defaultMessage: 'Cancel',
321+
}
322+
)}
323+
</EuiButtonEmpty>
324+
{canSave ? (
325+
<EuiButton
326+
fill
327+
color="primary"
328+
data-test-subj="saveActionButtonModal"
329+
type="submit"
330+
iconType="check"
331+
isLoading={isSaving}
332+
disabled={hasErrors}
333+
onClick={onSubmit}
334+
>
335+
<FormattedMessage
336+
id="xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel"
337+
defaultMessage="Save"
338+
/>
339+
</EuiButton>
340+
) : null}
341+
</EuiModalFooter>
342+
</div>
327343
</EuiModal>
328344
);
329345
};

0 commit comments

Comments
 (0)