Skip to content

Commit

Permalink
[ILM] Add unsaved change prompt to create/edit form (#186837)
Browse files Browse the repository at this point in the history
  • Loading branch information
alisonelizabeth authored Jun 27, 2024
1 parent e095931 commit 6100600
Show file tree
Hide file tree
Showing 9 changed files with 74 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
fatalErrorsServiceMock,
docLinksServiceMock,
executionContextServiceMock,
overlayServiceMock,
applicationServiceMock,
httpServiceMock,
scopedHistoryMock,
} from '@kbn/core/public/mocks';
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
import { init as initHttp } from '../../public/application/services/http';
Expand All @@ -31,8 +35,12 @@ const appContextMock = {
breadcrumbService,
license: licensingMock.createLicense({ license: { type: 'enterprise' } }),
docLinks: docLinksServiceMock.createStartContract(),
getUrlForApp: () => {},
getUrlForApp: applicationServiceMock.createStartContract().getUrlForApp,
executionContext: executionContextServiceMock.createSetupContract(),
navigateToUrl: applicationServiceMock.createStartContract().navigateToUrl,
overlays: overlayServiceMock.createStartContract(),
http: httpServiceMock.createSetupContract(),
history: scopedHistoryMock.create(),
};

export const WithAppDependencies =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ import {
import { App } from './app';
import { BreadcrumbService } from './services/breadcrumbs';

type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;

export const renderApp = (
startServices: StartServices,
startServices: CoreStart,
element: Element,
history: ScopedHistory,
application: ApplicationStart,
Expand All @@ -34,7 +32,9 @@ export const renderApp = (
executionContext: ExecutionContextStart,
cloud?: CloudSetup
): UnmountCallback => {
const { getUrlForApp } = application;
const { navigateToUrl, getUrlForApp } = application;
const { overlays, http } = startServices;

render(
<KibanaRenderContextProvider {...startServices}>
<div className={APP_WRAPPER_CLASS}>
Expand All @@ -51,6 +51,10 @@ export const renderApp = (
getUrlForApp,
docLinks,
executionContext,
navigateToUrl,
overlays,
http,
history,
}}
>
<App history={history} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@

import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { get } from 'lodash';

import { useHistory } from 'react-router-dom';

import './edit_policy.scss';

import {
Expand All @@ -27,7 +26,13 @@ import {
EuiTimeline,
} from '@elastic/eui';

import { TextField, useForm, useFormData, useKibana } from '../../../shared_imports';
import {
TextField,
useForm,
useFormData,
useKibana,
useFormIsModified,
} from '../../../shared_imports';
import { toasts } from '../../services/notification';
import { UseField } from './form';
import { savePolicy } from './save_policy';
Expand Down Expand Up @@ -69,10 +74,11 @@ export const EditPolicy: React.FunctionComponent = () => {
} = useEditPolicyContext();

const {
services: { cloud, docLinks },
services: { cloud, docLinks, history, navigateToUrl, overlays, http },
} = useKibana();

const [isClonedPolicy, setIsClonedPolicy] = useState(false);
const [hasSubmittedForm, setHasSubmittedForm] = useState<boolean>(false);
const originalPolicyName: string = isNewPolicy ? '' : policyName!;
const isAllowedByLicense = license.canUseSearchableSnapshot();
const isCloudEnabled = Boolean(cloud?.isCloudEnabled);
Expand Down Expand Up @@ -105,6 +111,8 @@ export const EditPolicy: React.FunctionComponent = () => {
});

const [formData] = useFormData({ form, watch: policyNamePath });
const isFormDirty = useFormIsModified({ form });

const getPolicyName = () => {
return isNewPolicy || isClonedPolicy ? get(formData, policyNamePath) : originalPolicyName;
};
Expand All @@ -119,7 +127,6 @@ export const EditPolicy: React.FunctionComponent = () => {
[originalPolicyName, existingPolicies, isClonedPolicy]
);

const history = useHistory();
const backToPolicyList = () => {
history.push('/policies');
};
Expand All @@ -134,13 +141,15 @@ export const EditPolicy: React.FunctionComponent = () => {
})
);
} else {
setHasSubmittedForm(true);
const success = await savePolicy(
{
...policy,
name: getPolicyName(),
},
isNewPolicy || isClonedPolicy
);

if (success) {
backToPolicyList();
}
Expand All @@ -151,6 +160,21 @@ export const EditPolicy: React.FunctionComponent = () => {
setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout);
};

useUnsavedChangesPrompt({
titleText: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.unsavedPrompt.title', {
defaultMessage: 'Exit without saving changes?',
}),
messageText: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.unsavedPrompt.body', {
defaultMessage:
'The data will be lost if you leave this page without saving the policy changes.',
}),
hasUnsavedChanges: isFormDirty && hasSubmittedForm === false,
openConfirm: overlays.openConfirm,
history,
http,
navigateToUrl,
});

return (
<>
<EuiPageHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
getFieldValidityAndErrorMessage,
useFormContext,
UseMultiFields,
useFormIsModified,
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';

export { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/index_lifecycle_management/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { ApplicationStart } from '@kbn/core/public';
import { ApplicationStart, HttpSetup, OverlayStart, ScopedHistory } from '@kbn/core/public';
import { DocLinksStart } from '@kbn/core/public';
import { HomePublicPluginSetup } from '@kbn/home-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
Expand Down Expand Up @@ -41,5 +41,9 @@ export interface AppServicesContext {
license: ILicense;
cloud?: CloudSetup;
getUrlForApp: ApplicationStart['getUrlForApp'];
navigateToUrl: ApplicationStart['navigateToUrl'];
docLinks: DocLinksStart;
overlays: OverlayStart;
http: HttpSetup;
history: ScopedHistory;
}
1 change: 1 addition & 0 deletions x-pack/plugins/index_lifecycle_management/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@kbn/shared-ux-link-redirect-app",
"@kbn/index-management",
"@kbn/react-kibana-context-render",
"@kbn/unsaved-changes-prompt",
],
"exclude": [
"target/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,11 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
*/
useUnsavedChangesPrompt({
titleText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.title', {
defaultMessage: `Exit pipeline creation without saving changes?`,
defaultMessage: 'Exit without saving changes?',
}),
messageText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.body', {
defaultMessage: `The data will be lost if you leave this page without saving the pipeline changes`,
defaultMessage:
'The data will be lost if you leave this page without saving the pipeline changes.',
}),
hasUnsavedChanges: (isFormDirty || areProcessorsDirty) && !hasSubmittedForm,
openConfirm: overlays.openConfirm,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const esClient = getService('es');
const security = getService('security');
const deployment = getService('deployment');
const testSubjects = getService('testSubjects');

describe('Home page', function () {
before(async () => {
Expand Down Expand Up @@ -80,5 +81,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {

expect(createdPolicy.length).to.be(1);
});

it('Shows a prompt when trying to navigate away from the creation form when the form is dirty', async () => {
await pageObjects.indexLifecycleManagement.clickCreatePolicyButton();

await pageObjects.indexLifecycleManagement.fillNewPolicyForm({
policyName,
});

// Try to navigate to another page
await testSubjects.click('logo');

// Since the form is now dirty it should trigger a confirmation prompt
expect(await testSubjects.exists('navigationBlockConfirmModal')).to.be(true);
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider
async createPolicyButton() {
return await testSubjects.find('createPolicyButton');
},
async clickCreatePolicyButton() {
return await testSubjects.click('createPolicyButton');
},
async fillNewPolicyForm(policy: Policy) {
const {
policyName,
Expand Down

0 comments on commit 6100600

Please sign in to comment.