mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Ingest Pipelines] Add unsaved changes prompt (#183699)
This commit is contained in:
parent
1ec9412e82
commit
1e197cf718
23 changed files with 421 additions and 6 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -893,6 +893,7 @@ examples/unified_field_list_examples @elastic/kibana-data-discovery
|
|||
src/plugins/unified_histogram @elastic/kibana-data-discovery
|
||||
src/plugins/unified_search @elastic/kibana-visualizations
|
||||
packages/kbn-unsaved-changes-badge @elastic/kibana-data-discovery
|
||||
packages/kbn-unsaved-changes-prompt @elastic/kibana-management
|
||||
x-pack/plugins/upgrade_assistant @elastic/kibana-management
|
||||
x-pack/plugins/observability_solution/uptime @elastic/obs-ux-infra_services-team
|
||||
x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux
|
||||
|
|
|
@ -148,6 +148,7 @@
|
|||
"unifiedHistogram": "src/plugins/unified_histogram",
|
||||
"unifiedDataTable": "packages/kbn-unified-data-table",
|
||||
"unsavedChangesBadge": "packages/kbn-unsaved-changes-badge",
|
||||
"unsavedChangesPrompt": "packages/kbn-unsaved-changes-prompt",
|
||||
"managedContentBadge": "packages/kbn-managed-content-badge"
|
||||
},
|
||||
"translations": []
|
||||
|
|
|
@ -889,6 +889,7 @@
|
|||
"@kbn/unified-histogram-plugin": "link:src/plugins/unified_histogram",
|
||||
"@kbn/unified-search-plugin": "link:src/plugins/unified_search",
|
||||
"@kbn/unsaved-changes-badge": "link:packages/kbn-unsaved-changes-badge",
|
||||
"@kbn/unsaved-changes-prompt": "link:packages/kbn-unsaved-changes-prompt",
|
||||
"@kbn/upgrade-assistant-plugin": "link:x-pack/plugins/upgrade_assistant",
|
||||
"@kbn/uptime-plugin": "link:x-pack/plugins/observability_solution/uptime",
|
||||
"@kbn/url-drilldown-plugin": "link:x-pack/plugins/drilldowns/url_drilldown",
|
||||
|
|
30
packages/kbn-unsaved-changes-prompt/README.md
Normal file
30
packages/kbn-unsaved-changes-prompt/README.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# @kbn/unsaved-changes-prompt
|
||||
|
||||
The useUnsavedChangesPrompt function is a custom React hook that prompts users with
|
||||
a confirmation dialog when they try to leave a page with unsaved changes. It blocks
|
||||
navigation and shows a dialog using the provided openConfirm function. If the user
|
||||
confirms, it navigates away; otherwise, it cancels the navigation, ensuring unsaved
|
||||
changes are not lost.
|
||||
|
||||
|
||||
```typescript
|
||||
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
|
||||
|
||||
export const SampleForm = ({ servicesForUnsavedChangesPrompt }) => {
|
||||
const { form } = useForm();
|
||||
const isFormDirty = useFormIsModified({ form });
|
||||
|
||||
useUnsavedChangesPrompt({
|
||||
hasUnsavedChanges: isFormDirty,
|
||||
...servicesForUnsavedChangesPrompt,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form form={form}>
|
||||
....
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
9
packages/kbn-unsaved-changes-prompt/index.ts
Normal file
9
packages/kbn-unsaved-changes-prompt/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { useUnsavedChangesPrompt } from './src/unsaved_changes_prompt';
|
13
packages/kbn-unsaved-changes-prompt/jest.config.js
Normal file
13
packages/kbn-unsaved-changes-prompt/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-unsaved-changes-prompt'],
|
||||
};
|
5
packages/kbn-unsaved-changes-prompt/kibana.jsonc
Normal file
5
packages/kbn-unsaved-changes-prompt/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/unsaved-changes-prompt",
|
||||
"owner": "@elastic/kibana-management"
|
||||
}
|
6
packages/kbn-unsaved-changes-prompt/package.json
Normal file
6
packages/kbn-unsaved-changes-prompt/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/unsaved-changes-prompt",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { useUnsavedChangesPrompt } from './unsaved_changes_prompt';
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { CoreScopedHistory } from '@kbn/core/public';
|
||||
|
||||
import { useUnsavedChangesPrompt } from './unsaved_changes_prompt';
|
||||
|
||||
const basePath = '/mock';
|
||||
const memoryHistory = createMemoryHistory({ initialEntries: [basePath] });
|
||||
const history = new CoreScopedHistory(memoryHistory, basePath);
|
||||
const coreStart = coreMock.createStart();
|
||||
const navigateToUrl = jest.fn().mockImplementation(async (url) => {
|
||||
history.push(url);
|
||||
});
|
||||
|
||||
describe('useUnsavedChangesPrompt', () => {
|
||||
it('should not block if not edited', () => {
|
||||
renderHook(() =>
|
||||
useUnsavedChangesPrompt({
|
||||
hasUnsavedChanges: false,
|
||||
http: coreStart.http,
|
||||
openConfirm: coreStart.overlays.openConfirm,
|
||||
history,
|
||||
navigateToUrl,
|
||||
})
|
||||
);
|
||||
|
||||
act(() => history.push('/test'));
|
||||
|
||||
expect(history.location.pathname).toBe('/test');
|
||||
expect(history.location.search).toBe('');
|
||||
expect(coreStart.overlays.openConfirm).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should block if edited', async () => {
|
||||
coreStart.overlays.openConfirm.mockResolvedValue(true);
|
||||
|
||||
renderHook(() =>
|
||||
useUnsavedChangesPrompt({
|
||||
hasUnsavedChanges: true,
|
||||
http: coreStart.http,
|
||||
openConfirm: coreStart.overlays.openConfirm,
|
||||
history,
|
||||
navigateToUrl,
|
||||
})
|
||||
);
|
||||
|
||||
act(() => history.push('/test'));
|
||||
|
||||
// needed because we have an async useEffect
|
||||
await act(() => new Promise((resolve) => resolve()));
|
||||
|
||||
expect(navigateToUrl).toBeCalledWith('/mock/test', expect.anything());
|
||||
expect(coreStart.overlays.openConfirm).toBeCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { ApplicationStart, ScopedHistory, OverlayStart, HttpStart } from '@kbn/core/public';
|
||||
|
||||
const DEFAULT_BODY_TEXT = i18n.translate('unsavedChangesPrompt.defaultModalText', {
|
||||
defaultMessage: `The data will be lost if you leave this page without saving the changes.`,
|
||||
});
|
||||
|
||||
const DEFAULT_TITLE_TEXT = i18n.translate('unsavedChangesPrompt.defaultModalTitle', {
|
||||
defaultMessage: 'Discard unsaved changes?',
|
||||
});
|
||||
|
||||
const DEFAULT_CANCEL_BUTTON = i18n.translate('unsavedChangesPrompt.defaultModalCancel', {
|
||||
defaultMessage: 'Keep editing',
|
||||
});
|
||||
|
||||
const DEFAULT_CONFIRM_BUTTON = i18n.translate('unsavedChangesPrompt.defaultModalConfirm', {
|
||||
defaultMessage: 'Leave page',
|
||||
});
|
||||
|
||||
interface Props {
|
||||
hasUnsavedChanges: boolean;
|
||||
http: HttpStart;
|
||||
openConfirm: OverlayStart['openConfirm'];
|
||||
history: ScopedHistory;
|
||||
navigateToUrl: ApplicationStart['navigateToUrl'];
|
||||
titleText?: string;
|
||||
messageText?: string;
|
||||
cancelButtonText?: string;
|
||||
confirmButtonText?: string;
|
||||
}
|
||||
|
||||
export const useUnsavedChangesPrompt = ({
|
||||
hasUnsavedChanges,
|
||||
openConfirm,
|
||||
history,
|
||||
http,
|
||||
navigateToUrl,
|
||||
// Provide overrides for confirm dialog
|
||||
messageText = DEFAULT_BODY_TEXT,
|
||||
titleText = DEFAULT_TITLE_TEXT,
|
||||
confirmButtonText = DEFAULT_CONFIRM_BUTTON,
|
||||
cancelButtonText = DEFAULT_CANCEL_BUTTON,
|
||||
}: Props) => {
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unblock = history.block((state) => {
|
||||
async function confirmAsync() {
|
||||
const confirmResponse = await openConfirm(messageText, {
|
||||
title: titleText,
|
||||
cancelButtonText,
|
||||
confirmButtonText,
|
||||
'data-test-subj': 'navigationBlockConfirmModal',
|
||||
});
|
||||
|
||||
if (confirmResponse) {
|
||||
// Compute the URL we want to redirect to
|
||||
const url = http.basePath.prepend(state.pathname) + state.hash + state.search;
|
||||
// Unload history block
|
||||
unblock();
|
||||
// Navigate away
|
||||
navigateToUrl(url, {
|
||||
state: state.state,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
confirmAsync();
|
||||
return false;
|
||||
});
|
||||
|
||||
return unblock;
|
||||
}, [
|
||||
history,
|
||||
hasUnsavedChanges,
|
||||
openConfirm,
|
||||
navigateToUrl,
|
||||
http.basePath,
|
||||
titleText,
|
||||
cancelButtonText,
|
||||
confirmButtonText,
|
||||
messageText,
|
||||
]);
|
||||
};
|
14
packages/kbn-unsaved-changes-prompt/tsconfig.json
Normal file
14
packages/kbn-unsaved-changes-prompt/tsconfig.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": ["*.ts", "src/**/*"],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/i18n",
|
||||
"@kbn/core"
|
||||
]
|
||||
}
|
|
@ -1780,6 +1780,8 @@
|
|||
"@kbn/unified-search-plugin/*": ["src/plugins/unified_search/*"],
|
||||
"@kbn/unsaved-changes-badge": ["packages/kbn-unsaved-changes-badge"],
|
||||
"@kbn/unsaved-changes-badge/*": ["packages/kbn-unsaved-changes-badge/*"],
|
||||
"@kbn/unsaved-changes-prompt": ["packages/kbn-unsaved-changes-prompt"],
|
||||
"@kbn/unsaved-changes-prompt/*": ["packages/kbn-unsaved-changes-prompt/*"],
|
||||
"@kbn/upgrade-assistant-plugin": ["x-pack/plugins/upgrade_assistant"],
|
||||
"@kbn/upgrade-assistant-plugin/*": ["x-pack/plugins/upgrade_assistant/*"],
|
||||
"@kbn/uptime-plugin": ["x-pack/plugins/observability_solution/uptime"],
|
||||
|
|
|
@ -19,7 +19,9 @@ import {
|
|||
scopedHistoryMock,
|
||||
uiSettingsServiceMock,
|
||||
applicationServiceMock,
|
||||
overlayServiceMock,
|
||||
} from '@kbn/core/public/mocks';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
|
||||
import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/public/mocks';
|
||||
|
||||
|
@ -66,6 +68,8 @@ const appServices = {
|
|||
share: {
|
||||
url: new MockUrlService(),
|
||||
},
|
||||
overlays: overlayServiceMock.createStartContract(),
|
||||
http: httpServiceMock.createStartContract({ basePath: '/mock' }),
|
||||
};
|
||||
|
||||
export const setupEnvironment = () => {
|
||||
|
|
|
@ -6,14 +6,18 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { useForm, Form, FormConfig } from '../../../shared_imports';
|
||||
import { useUnsavedChangesPrompt } from '@kbn/unsaved-changes-prompt';
|
||||
import { Pipeline, Processor } from '../../../../common/types';
|
||||
import { useForm, Form, FormConfig, useFormIsModified } from '../../../shared_imports';
|
||||
|
||||
import { useKibana } from '../../../shared_imports';
|
||||
import { OnUpdateHandlerArg, OnUpdateHandler } from '../pipeline_editor';
|
||||
|
||||
import { deepEqualIgnoreUndefined } from './utils';
|
||||
import { PipelineRequestFlyout } from './pipeline_request_flyout';
|
||||
import { PipelineFormFields } from './pipeline_form_fields';
|
||||
import { PipelineFormError } from './pipeline_form_error';
|
||||
|
@ -48,7 +52,16 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
onCancel,
|
||||
canEditName,
|
||||
}) => {
|
||||
const {
|
||||
overlays,
|
||||
history,
|
||||
application: { navigateToUrl },
|
||||
http,
|
||||
} = useKibana().services;
|
||||
|
||||
const [isRequestVisible, setIsRequestVisible] = useState<boolean>(false);
|
||||
const [areProcessorsDirty, setAreProcessorsDirty] = useState<boolean>(false);
|
||||
const [hasSubmittedForm, setHasSubmittedForm] = useState<boolean>(false);
|
||||
|
||||
const {
|
||||
processors: initialProcessors,
|
||||
|
@ -74,6 +87,11 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
if (processorStateRef.current) {
|
||||
const state = processorStateRef.current;
|
||||
if (await state.validate()) {
|
||||
// We only want to show unsaved changed prompts to the user when the form
|
||||
// hasnt been submitted.
|
||||
setHasSubmittedForm(true);
|
||||
|
||||
// Save the form state, this will also trigger a redirect to pipelines list
|
||||
onSave({ ...formData, ...state.getData() });
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +103,8 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
onSubmit: handleSave,
|
||||
});
|
||||
|
||||
const isFormDirty = useFormIsModified({ form });
|
||||
|
||||
const onEditorFlyoutOpen = useCallback(() => {
|
||||
setIsRequestVisible(false);
|
||||
}, [setIsRequestVisible]);
|
||||
|
@ -107,10 +127,48 @@ export const PipelineForm: React.FunctionComponent<PipelineFormProps> = ({
|
|||
);
|
||||
|
||||
const onProcessorsChangeHandler = useCallback<OnUpdateHandler>(
|
||||
(arg) => (processorStateRef.current = arg),
|
||||
[]
|
||||
(arg) => {
|
||||
processorStateRef.current = arg;
|
||||
|
||||
const currentProcessorsState = processorStateRef.current?.getData();
|
||||
|
||||
// Calculate if the current processor state has changed compared to the
|
||||
// initial processors state.
|
||||
setAreProcessorsDirty(
|
||||
!deepEqualIgnoreUndefined(
|
||||
{
|
||||
processors: processorsState?.processors || [],
|
||||
onFailure: processorsState?.onFailure || [],
|
||||
},
|
||||
{
|
||||
processors: currentProcessorsState?.processors || [],
|
||||
onFailure: currentProcessorsState?.on_failure || [],
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
[processorsState]
|
||||
);
|
||||
|
||||
/*
|
||||
We need to check if the form is dirty and also if the form has been submitted.
|
||||
Because on form submission we also redirect the user to the pipelines list,
|
||||
and this could otherwise trigger an unwanted unsaved changes prompt.
|
||||
*/
|
||||
useUnsavedChangesPrompt({
|
||||
titleText: i18n.translate('xpack.ingestPipelines.form.unsavedPrompt.title', {
|
||||
defaultMessage: `Exit pipeline creation 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`,
|
||||
}),
|
||||
hasUnsavedChanges: (isFormDirty || areProcessorsDirty) && !hasSubmittedForm,
|
||||
openConfirm: overlays.openConfirm,
|
||||
history,
|
||||
http,
|
||||
navigateToUrl,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { removeUndefinedValues, deepEqualIgnoreUndefined } from './utils';
|
||||
|
||||
describe('deepEqualIgnoreUndefined', () => {
|
||||
const testObjectA = Object.freeze({
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const testObjectB = Object.freeze({
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
d: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
it('knows how to remove undefined values', () => {
|
||||
expect(removeUndefinedValues(testObjectA)).toStrictEqual({
|
||||
a: 1,
|
||||
b: {
|
||||
c: 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('knows how to compare two objects and see if they are equal ignoring undefined values', () => {
|
||||
expect(deepEqualIgnoreUndefined(testObjectA, testObjectB)).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { transform, isObject, isEqual } from 'lodash';
|
||||
|
||||
export function removeUndefinedValues(obj: object) {
|
||||
// If the input is an object, recursively clean each key-value pair.
|
||||
if (isObject(obj)) {
|
||||
// Use transform to iterate over the object and build a new result object.
|
||||
return transform(
|
||||
obj,
|
||||
(result: Record<string, any>, value: any, key: string) => {
|
||||
const cleanedValue = removeUndefinedValues(value);
|
||||
if (cleanedValue !== undefined) {
|
||||
// Only add the key-value pair if the value is not undefined.
|
||||
result[key as keyof typeof obj] = cleanedValue;
|
||||
}
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function deepEqualIgnoreUndefined(obj1: object, obj2: object) {
|
||||
// Clean both objects by removing undefined values.
|
||||
const cleanedObj1 = removeUndefinedValues(obj1);
|
||||
const cleanedObj2 = removeUndefinedValues(obj2);
|
||||
|
||||
return isEqual(cleanedObj1, cleanedObj2);
|
||||
}
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
|
||||
import { ApplicationStart } from '@kbn/core/public';
|
||||
import { NotificationsSetup, IUiSettingsClient } from '@kbn/core/public';
|
||||
import { NotificationsSetup, IUiSettingsClient, OverlayStart, HttpStart } from '@kbn/core/public';
|
||||
import { ManagementAppMountParams } from '@kbn/management-plugin/public';
|
||||
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
|
||||
import type { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
|
@ -48,6 +48,8 @@ export interface AppServices {
|
|||
application: ApplicationStart;
|
||||
license: ILicense | null;
|
||||
consolePlugin?: ConsolePluginStart;
|
||||
overlays: OverlayStart;
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
type StartServices = Pick<CoreStart, 'analytics' | 'i18n' | 'theme'>;
|
||||
|
|
|
@ -28,7 +28,7 @@ export async function mountManagementSection(
|
|||
) {
|
||||
const { element, setBreadcrumbs, history, license } = params;
|
||||
const [coreStart, depsStart] = await getStartServices();
|
||||
const { docLinks, application, executionContext } = coreStart;
|
||||
const { docLinks, application, executionContext, overlays } = coreStart;
|
||||
|
||||
documentationService.setup(docLinks);
|
||||
breadcrumbService.setup(setBreadcrumbs);
|
||||
|
@ -49,6 +49,8 @@ export async function mountManagementSection(
|
|||
executionContext,
|
||||
license,
|
||||
consolePlugin: depsStart.console,
|
||||
overlays,
|
||||
http,
|
||||
};
|
||||
|
||||
return renderApp(element, services, { ...coreStart, http });
|
||||
|
|
|
@ -62,6 +62,7 @@ export {
|
|||
FormDataProvider,
|
||||
getFieldValidityAndErrorMessage,
|
||||
useFormData,
|
||||
useFormIsModified,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
|
||||
export { fieldFormatters, fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
"@kbn/code-editor",
|
||||
"@kbn/react-kibana-context-render",
|
||||
"@kbn/console-plugin",
|
||||
"@kbn/react-kibana-context-theme"
|
||||
"@kbn/react-kibana-context-theme",
|
||||
"@kbn/unsaved-changes-prompt",
|
||||
"@kbn/core-http-browser-mocks"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -133,6 +133,22 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Shows a prompt when trying to navigate away from the creation form when the form is dirty', async () => {
|
||||
// Navigate to creation flow
|
||||
await testSubjects.click('createPipelineDropdown');
|
||||
await testSubjects.click('createNewPipeline');
|
||||
|
||||
// Fill in the form with some data
|
||||
await testSubjects.setValue('nameField > input', 'test_name');
|
||||
await testSubjects.setValue('descriptionField > input', 'test_description');
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
describe('Create pipeline', () => {
|
||||
afterEach(async () => {
|
||||
// Delete the pipeline that was created
|
||||
|
|
|
@ -6728,6 +6728,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/unsaved-changes-prompt@link:packages/kbn-unsaved-changes-prompt":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/upgrade-assistant-plugin@link:x-pack/plugins/upgrade_assistant":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue