Skip to content

Commit

Permalink
[Search][Connectors] Confirmation modal before leaving the connector …
Browse files Browse the repository at this point in the history
…creation flow (#197646)

## Summary

This PR shows a confirmation modal when users leave the connectors
creation flow before providing all necessary info, asking for
intentional confirmation after leaving the experience.

Setting `isFormDirty = true` only after generating the connectors config
and letting users leave the experience setting `isFormDirty = false`
when we arrive to the Finish up step

![CleanShot 2024-10-24 at 18 56
11](https://github.com/user-attachments/assets/90f355e2-d227-4d2a-a45e-bcfbb743d588)

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Jedr Blaszyk <[email protected]>
(cherry picked from commit aec93bf)
  • Loading branch information
JoseLuisGJ committed Oct 29, 2024
1 parent bbf8547 commit 02d56ed
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Status } from '../../../../../../common/types/api';
import * as Constants from '../../../../shared/constants';
import { ConnectorConfigurationApiLogic } from '../../../api/connector/update_connector_configuration_api_logic';
import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic';
import { NewConnectorLogic } from '../../new_index/method_connector/new_connector_logic';

interface ConfigurationStepProps {
setCurrentStep: Function;
Expand All @@ -38,6 +39,7 @@ interface ConfigurationStepProps {
export const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ title, setCurrentStep }) => {
const { connector } = useValues(ConnectorViewLogic);
const { updateConnectorConfiguration } = useActions(ConnectorViewLogic);
const { setFormDirty } = useActions(NewConnectorLogic);
const { status } = useValues(ConnectorConfigurationApiLogic);
const isSyncing = false;

Expand Down Expand Up @@ -109,7 +111,10 @@ export const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ title, set
<EuiSpacer size="m" />
<EuiButton
data-test-subj="enterpriseSearchStartStepGenerateConfigurationButton"
onClick={() => setCurrentStep('finish')}
onClick={() => {
setFormDirty(false);
setCurrentStep('finish');
}}
fill
>
{Constants.NEXT_BUTTON_LABEL}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import {

import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';

import { HttpLogic } from '../../../../shared/http';
import { KibanaLogic } from '../../../../shared/kibana';

import { AddConnectorApiLogic } from '../../../api/connector/add_connector_api_logic';
import { EnterpriseSearchContentPageTemplate } from '../../layout';
Expand All @@ -47,11 +52,16 @@ import { StartStep } from './start_step';
export type ConnectorCreationSteps = 'start' | 'deployment' | 'configure' | 'finish';
export type SelfManagePreference = 'native' | 'selfManaged';
export const CreateConnector: React.FC = () => {
const { overlays } = useKibana().services;

const { http } = useValues(HttpLogic);
const { application, history } = useValues(KibanaLogic);

const { error } = useValues(AddConnectorApiLogic);
const { euiTheme } = useEuiTheme();
const [selfManagePreference, setSelfManagePreference] = useState<SelfManagePreference>('native');

const { selectedConnector, currentStep } = useValues(NewConnectorLogic);
const { selectedConnector, currentStep, isFormDirty } = useValues(NewConnectorLogic);
const { setCurrentStep } = useActions(NewConnectorLogic);
const stepStates = generateStepState(currentStep);

Expand Down Expand Up @@ -137,6 +147,33 @@ export const CreateConnector: React.FC = () => {
),
};

useUnsavedChangesPrompt({
cancelButtonText: i18n.translate(
'xpack.enterpriseSearch.createConnector.unsavedPrompt.cancel',
{
defaultMessage: 'Continue setup',
}
),
confirmButtonText: i18n.translate(
'xpack.enterpriseSearch.createConnector.unsavedPrompt.confirm',
{
defaultMessage: 'Leave the page',
}
),
hasUnsavedChanges: isFormDirty,
history,
http,
messageText: i18n.translate('xpack.enterpriseSearch.createConnector.unsavedPrompt.body', {
defaultMessage:
'Your connector is created but missing some details. You can complete the setup later in the connector configuration page, but this guided flow offers more help.',
}),
navigateToUrl: application.navigateToUrl,
openConfirm: overlays?.openConfirm ?? (() => Promise.resolve(false)),
titleText: i18n.translate('xpack.enterpriseSearch.createConnector.unsavedPrompt.title', {
defaultMessage: 'Your connector is not fully configured',
}),
});

return (
<EnterpriseSearchContentPageTemplate
pageChrome={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export const StartStep: React.FC<StartStepProps> = ({
isGenerateLoading,
isCreateLoading,
} = useValues(NewConnectorLogic);
const { setRawName, createConnector, generateConnectorName } = useActions(NewConnectorLogic);
const { setRawName, createConnector, generateConnectorName, setFormDirty } =
useActions(NewConnectorLogic);
const { connector } = useValues(ConnectorViewLogic);

const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -236,6 +237,7 @@ export const StartStep: React.FC<StartStepProps> = ({
createConnector({
isSelfManaged: true,
});
setFormDirty(true);
setCurrentStep('deployment');
}
}}
Expand Down Expand Up @@ -294,7 +296,9 @@ export const StartStep: React.FC<StartStepProps> = ({
<EuiButton
data-test-subj="enterpriseSearchStartStepGenerateConfigurationButton"
fill
onClick={() => setCurrentStep('configure')}
onClick={() => {
setCurrentStep('configure');
}}
>
{Constants.NEXT_BUTTON_LABEL}
</EuiButton>
Expand All @@ -310,6 +314,7 @@ export const StartStep: React.FC<StartStepProps> = ({
iconType="sparkles"
isLoading={isGenerateLoading || isCreateLoading}
onClick={() => {
setFormDirty(true);
createConnector({
isSelfManaged: false,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface NewConnectorValues {
| undefined;
generatedNameData: GenerateConnectorNamesApiResponse | undefined;
isCreateLoading: boolean;
isFormDirty: boolean;
isGenerateLoading: boolean;
rawName: string;
selectedConnector: ConnectorDefinition | null;
Expand Down Expand Up @@ -85,6 +86,7 @@ type NewConnectorActions = {
createConnectorApi: AddConnectorApiLogicActions['makeRequest'];
fetchConnector: ConnectorViewActions['fetchConnector'];
setCurrentStep(step: ConnectorCreationSteps): { step: ConnectorCreationSteps };
setFormDirty: (isDirty: boolean) => { isDirty: boolean };
setRawName(rawName: string): { rawName: string };
setSelectedConnector(connector: ConnectorDefinition | null): {
connector: ConnectorDefinition | null;
Expand All @@ -103,6 +105,7 @@ export const NewConnectorLogic = kea<MakeLogicType<NewConnectorValues, NewConnec
shouldNavigateToConnectorAfterCreate,
}),
setCurrentStep: (step) => ({ step }),
setFormDirty: (isDirty) => ({ isDirty }),
setRawName: (rawName) => ({ rawName }),
setSelectedConnector: (connector) => ({ connector }),
},
Expand Down Expand Up @@ -214,6 +217,13 @@ export const NewConnectorLogic = kea<MakeLogicType<NewConnectorValues, NewConnec
) => step,
},
],
isFormDirty: [
false, // Initial state (form is not dirty)
{
// @ts-expect-error upgrade typescript v5.1.6
setFormDirty: (_, { isDirty }) => isDirty,
},
],
rawName: [
'',
{
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/enterprise_search/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"@kbn/navigation-plugin",
"@kbn/security-plugin-types-common",
"@kbn/core-security-server",
"@kbn/core-security-server-mocks"
"@kbn/core-security-server-mocks",
"@kbn/unsaved-changes-prompt"
]
}

0 comments on commit 02d56ed

Please sign in to comment.