Remove confirm modal directive and factory (#56846)

* Graph: replace confirmModal

* Remove confirmModal from visualize

* Remove confirmModal from dashboard

* Remove confirm_modal

* Remove confirmModalPromises

* Replace confirmModal

* FIx TS

* Add data-test-subj for graph confirm modal

* Update public.api.md

* Remove unused translation

* Update mock test
This commit is contained in:
Maryia Lapata 2020-02-10 14:32:28 +03:00 committed by GitHub
parent bdba16d92c
commit 56571bac84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 137 additions and 662 deletions

View file

@ -20,7 +20,7 @@
/* eslint-disable max-classes-per-file */
import { i18n as t } from '@kbn/i18n';
import { EuiModal, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
import { EuiModal, EuiConfirmModal, EuiOverlayMask, EuiConfirmModalProps } from '@elastic/eui';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Subject } from 'rxjs';
@ -68,6 +68,7 @@ export interface OverlayModalConfirmOptions {
className?: string;
closeButtonAriaLabel?: string;
'data-test-subj'?: string;
defaultFocusedButton?: EuiConfirmModalProps['defaultFocusedButton'];
}
/**

View file

@ -6,6 +6,7 @@
import { Breadcrumb } from '@elastic/eui';
import { EuiButtonEmptyProps } from '@elastic/eui';
import { EuiConfirmModalProps } from '@elastic/eui';
import { EuiGlobalToastListToast } from '@elastic/eui';
import { ExclusiveUnion } from '@elastic/eui';
import { IconType } from '@elastic/eui';

View file

@ -30,15 +30,11 @@ export const legacyChrome = chrome;
export { SavedObjectSaveOpts } from 'ui/saved_objects/types';
export { npSetup, npStart } from 'ui/new_platform';
export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
// @ts-ignore
export { ConfirmationButtonTypes } from 'ui/modals/confirm_modal';
export { KbnUrl } from 'ui/url/kbn_url';
// @ts-ignore
export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav';
// @ts-ignore
export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index';
// @ts-ignore
export { confirmModalFactory } from 'ui/modals/confirm_modal';
export { IInjector } from 'ui/chrome';
export { SavedObjectLoader } from 'ui/saved_objects';
export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable';

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { EuiConfirmModal, EuiIcon } from '@elastic/eui';
import { EuiIcon } from '@elastic/eui';
import angular, { IModule } from 'angular';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import {
@ -30,7 +30,6 @@ import {
import { Storage } from '../../../../../../plugins/kibana_utils/public';
import {
configureAppAngularModule,
confirmModalFactory,
createTopNavDirective,
createTopNavHelper,
IPrivate,
@ -111,7 +110,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav
createLocalConfigModule(core);
createLocalKbnUrlModule();
createLocalTopNavModule(navigation);
createLocalConfirmModalModule();
createLocalIconModule();
const dashboardAngularModule = angular.module(moduleName, [
@ -122,7 +120,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav
'app/dashboard/TopNav',
'app/dashboard/KbnUrl',
'app/dashboard/Promise',
'app/dashboard/ConfirmModal',
'app/dashboard/icon',
]);
return dashboardAngularModule;
@ -134,13 +131,6 @@ function createLocalIconModule() {
.directive('icon', reactDirective => reactDirective(EuiIcon));
}
function createLocalConfirmModalModule() {
angular
.module('app/dashboard/ConfirmModal', ['react'])
.factory('confirmModal', confirmModalFactory)
.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
}
function createLocalKbnUrlModule() {
angular
.module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute'])

View file

@ -25,7 +25,7 @@ import { IInjector } from '../legacy_imports';
import { ViewMode } from '../../../../embeddable_api/public/np_ready/public';
import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard';
import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types';
import { DashboardAppState, SavedDashboardPanel } from './types';
import {
IIndexPattern,
TimeRange,
@ -87,8 +87,6 @@ export interface DashboardAppScope extends ng.IScope {
export function initDashboardAppDirective(app: any, deps: RenderDeps) {
app.directive('dashboardApp', function($injector: IInjector) {
const confirmModal = $injector.get<ConfirmModalFn>('confirmModal');
return {
restrict: 'E',
controllerAs: 'dashboardApp',
@ -105,7 +103,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) {
$route,
$scope,
$routeParams,
confirmModal,
indexPatterns: deps.npDataStart.indexPatterns,
kbnUrlStateStorage,
history,

View file

@ -19,6 +19,7 @@
import _, { uniq } from 'lodash';
import { i18n } from '@kbn/i18n';
import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui';
import React from 'react';
import angular from 'angular';
@ -27,12 +28,7 @@ import { map } from 'rxjs/operators';
import { History } from 'history';
import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen';
import {
ConfirmationButtonTypes,
migrateLegacyQuery,
SavedObjectSaveOpts,
subscribeWithScope,
} from '../legacy_imports';
import { migrateLegacyQuery, SavedObjectSaveOpts, subscribeWithScope } from '../legacy_imports';
import {
COMPARE_ALL_OPTIONS,
compareFilters,
@ -63,7 +59,7 @@ import {
openAddPanelFlyout,
ViewMode,
} from '../../../../embeddable_api/public/np_ready/public';
import { ConfirmModalFn, NavAction, SavedDashboardPanel } from './types';
import { NavAction, SavedDashboardPanel } from './types';
import { showOptionsPopover } from './top_nav/show_options_popover';
import { DashboardSaveModal } from './top_nav/save_modal';
@ -90,7 +86,6 @@ export interface DashboardAppControllerDependencies extends RenderDeps {
$routeParams: any;
indexPatterns: IndexPatternsContract;
dashboardConfig: KibanaLegacyStart['dashboardConfig'];
confirmModal: ConfirmModalFn;
history: History;
kbnUrlStateStorage: IKbnUrlStateStorage;
}
@ -108,7 +103,6 @@ export class DashboardAppController {
dashboardConfig,
localStorage,
indexPatterns,
confirmModal,
savedQueryService,
embeddables,
share,
@ -635,27 +629,31 @@ export class DashboardAppController {
}
}
confirmModal(
i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', {
defaultMessage: `Once you discard your changes, there's no getting them back.`,
}),
{
onConfirm: revertChangesAndExitEditMode,
onCancel: _.noop,
confirmButtonText: i18n.translate(
'kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel',
{ defaultMessage: 'Discard changes' }
),
cancelButtonText: i18n.translate(
'kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel',
{ defaultMessage: 'Continue editing' }
),
defaultFocusedButton: ConfirmationButtonTypes.CANCEL,
title: i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', {
defaultMessage: 'Discard changes to dashboard?',
overlays
.openConfirm(
i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', {
defaultMessage: `Once you discard your changes, there's no getting them back.`,
}),
}
);
{
confirmButtonText: i18n.translate(
'kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel',
{ defaultMessage: 'Discard changes' }
),
cancelButtonText: i18n.translate(
'kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel',
{ defaultMessage: 'Continue editing' }
),
defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON,
title: i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', {
defaultMessage: 'Discard changes to dashboard?',
}),
}
)
.then(isConfirmed => {
if (isConfirmed) {
revertChangesAndExitEditMode();
}
});
};
/**

View file

@ -137,15 +137,3 @@ export interface StagedFilter {
operator: string;
index: string;
}
export type ConfirmModalFn = (
message: string,
confirmOptions: {
onConfirm: () => void;
onCancel: () => void;
confirmButtonText: string;
cancelButtonText: string;
defaultFocusedButton: string;
title: string;
}
) => void;

View file

@ -43,6 +43,7 @@ export class CreateIndexPatternWizard extends Component {
indexPatternCreationType: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
changeUrl: PropTypes.func.isRequired,
openConfirm: PropTypes.func.isRequired,
}).isRequired,
};
@ -142,12 +143,16 @@ export class CreateIndexPatternWizard extends Component {
values: { title: this.title },
defaultMessage: "An index pattern with the title '{title}' already exists.",
});
try {
await services.confirmModalPromise(confirmMessage, {
confirmButtonText: 'Go to existing pattern',
});
const isConfirmed = await services.openConfirm(confirmMessage, {
confirmButtonText: i18n.translate('kbn.management.indexPattern.goToPatternButtonLabel', {
defaultMessage: 'Go to existing pattern',
}),
});
if (isConfirmed) {
return services.changeUrl(`/management/kibana/index_patterns/${indexPatternId}`);
} catch (err) {
} else {
return false;
}
}

View file

@ -43,10 +43,10 @@ uiRoutes.when('/management/kibana/index_pattern', {
$http: npStart.core.http,
savedObjectsClient: npStart.core.savedObjects.client,
indexPatternCreationType,
confirmModalPromise: $injector.get('confirmModalPromise'),
changeUrl: url => {
$scope.$evalAsync(() => kbnUrl.changePath(url));
},
openConfirm: npStart.core.overlays.openConfirm,
};
const initialQuery = $routeParams.id ? decodeURIComponent($routeParams.id) : undefined;

View file

@ -198,8 +198,7 @@ uiModules
$route,
Promise,
config,
Private,
confirmModal
Private
) {
const {
startSyncingState,
@ -290,15 +289,19 @@ uiModules
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.refreshButton', {
defaultMessage: 'Refresh',
}),
onConfirm: async () => {
await $scope.indexPattern.init(true);
$scope.fields = $scope.indexPattern.getNonScriptedFields();
},
title: i18n.translate('kbn.management.editIndexPattern.refreshHeader', {
defaultMessage: 'Refresh field list?',
}),
};
confirmModal(confirmMessage, confirmModalOptions);
npStart.core.overlays
.openConfirm(confirmMessage, confirmModalOptions)
.then(async isConfirmed => {
if (isConfirmed) {
await $scope.indexPattern.init(true);
$scope.fields = $scope.indexPattern.getNonScriptedFields();
}
});
};
$scope.removePattern = function() {
@ -322,12 +325,16 @@ uiModules
confirmButtonText: i18n.translate('kbn.management.editIndexPattern.deleteButton', {
defaultMessage: 'Delete',
}),
onConfirm: doRemove,
title: i18n.translate('kbn.management.editIndexPattern.deleteHeader', {
defaultMessage: 'Delete index pattern?',
}),
};
confirmModal('', confirmModalOptions);
npStart.core.overlays.openConfirm('', confirmModalOptions).then(isConfirmed => {
if (isConfirmed) {
doRemove();
}
});
};
$scope.setDefaultPattern = function() {

View file

@ -38,7 +38,6 @@ function updateObjectsTable($scope, $injector) {
const $http = $injector.get('$http');
const kbnUrl = $injector.get('kbnUrl');
const config = $injector.get('config');
const confirmModalPromise = $injector.get('confirmModalPromise');
const savedObjectsClient = npStart.core.savedObjects.client;
const services = savedObjectManagementRegistry.all().map(obj => obj.service);
@ -54,7 +53,7 @@ function updateObjectsTable($scope, $injector) {
<I18nContext>
<ObjectsTable
savedObjectsClient={savedObjectsClient}
confirmModalPromise={confirmModalPromise}
confirmModalPromise={npStart.core.overlays.openConfirm}
services={services}
indexPatterns={indexPatterns}
$http={$http}

View file

@ -46,13 +46,14 @@ uiRoutes.when('/management/kibana/objects/:service/:id', {
uiModules
.get('apps/management', ['monospaced.elastic'])
.directive('kbnManagementObjectsView', function(kbnIndex, confirmModal) {
.directive('kbnManagementObjectsView', function() {
return {
restrict: 'E',
controller: function($scope, $routeParams, $location, $window, $rootScope, uiCapabilities) {
const serviceObj = savedObjectManagementRegistry.get($routeParams.service);
const service = serviceObj.service;
const savedObjectsClient = npStart.core.savedObjects.client;
const { overlays } = npStart.core;
/**
* Creates a field definition and pushes it to the memo stack. This function
@ -233,7 +234,6 @@ uiModules
.catch(error => fatalError(error, location));
}
const confirmModalOptions = {
onConfirm: doDelete,
confirmButtonText: i18n.translate(
'kbn.management.objects.confirmModalOptions.deleteButtonLabel',
{
@ -244,12 +244,19 @@ uiModules
defaultMessage: 'Delete saved Kibana object?',
}),
};
confirmModal(
i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', {
defaultMessage: "You can't recover deleted objects",
}),
confirmModalOptions
);
overlays
.openConfirm(
i18n.translate('kbn.management.objects.confirmModalOptions.modalDescription', {
defaultMessage: "You can't recover deleted objects",
}),
confirmModalOptions
)
.then(isConfirmed => {
if (isConfirmed) {
doDelete();
}
});
};
$scope.submit = function() {

View file

@ -79,24 +79,25 @@ async function importIndexPattern(doc, indexPatterns, overwriteAll, confirmModal
let newId = await emptyPattern.create(overwriteAll);
if (!newId) {
// We can override and we want to prompt for confirmation
try {
await confirmModalPromise(
i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', {
values: { title: this.title },
defaultMessage: "Are you sure you want to overwrite '{title}'?",
const isConfirmed = await confirmModalPromise(
i18n.translate('kbn.management.indexPattern.confirmOverwriteLabel', {
values: { title: this.title },
defaultMessage: "Are you sure you want to overwrite '{title}'?",
}),
{
title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', {
defaultMessage: 'Overwrite {type}?',
values: { type },
}),
{
title: i18n.translate('kbn.management.indexPattern.confirmOverwriteTitle', {
defaultMessage: 'Overwrite {type}?',
values: { type },
}),
confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', {
defaultMessage: 'Overwrite',
}),
}
);
confirmButtonText: i18n.translate('kbn.management.indexPattern.confirmOverwriteButton', {
defaultMessage: 'Overwrite',
}),
}
);
if (isConfirmed) {
newId = await emptyPattern.create(true);
} catch (err) {
} else {
return;
}
}

View file

@ -46,8 +46,6 @@ export { subscribeWithScope } from 'ui/utils/subscribe_with_scope';
export { EventsProvider } from 'ui/events';
// @ts-ignore
export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav';
// @ts-ignore
export { confirmModalFactory } from 'ui/modals/confirm_modal';
export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router';
// @ts-ignore
export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url';

View file

@ -18,7 +18,6 @@
*/
import angular, { IModule } from 'angular';
import { EuiConfirmModal } from '@elastic/eui';
import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular';
import { AppMountContext, LegacyCoreStart } from 'kibana/public';
@ -26,7 +25,6 @@ import {
AppStateProvider,
AppState,
configureAppAngularModule,
confirmModalFactory,
createTopNavDirective,
createTopNavHelper,
EventsProvider,
@ -93,7 +91,6 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav
createLocalStateModule();
createLocalPersistedStateModule();
createLocalTopNavModule(navigation);
createLocalConfirmModalModule();
const visualizeAngularModule: IModule = angular.module(moduleName, [
...thirdPartyAngularDependencies,
@ -103,18 +100,10 @@ function createLocalAngularModule(core: AppMountContext['core'], navigation: Nav
'app/visualize/PersistedState',
'app/visualize/TopNav',
'app/visualize/State',
'app/visualize/ConfirmModal',
]);
return visualizeAngularModule;
}
function createLocalConfirmModalModule() {
angular
.module('app/visualize/ConfirmModal', ['react'])
.factory('confirmModal', confirmModalFactory)
.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
}
function createLocalStateModule() {
angular
.module('app/visualize/State', [

View file

@ -114,7 +114,6 @@ app.controller('timelion', function(
$timeout,
AppState,
config,
confirmModal,
kbnUrl,
Private
) {
@ -230,7 +229,6 @@ app.controller('timelion', function(
}
const confirmModalOptions = {
onConfirm: doDelete,
confirmButtonText: i18n.translate('timelion.topNavMenu.delete.modal.confirmButtonLabel', {
defaultMessage: 'Delete',
}),
@ -241,12 +239,18 @@ app.controller('timelion', function(
};
$scope.$evalAsync(() => {
confirmModal(
i18n.translate('timelion.topNavMenu.delete.modal.warningText', {
defaultMessage: `You can't recover deleted sheets.`,
}),
confirmModalOptions
);
npStart.core.overlays
.openConfirm(
i18n.translate('timelion.topNavMenu.delete.modal.warningText', {
defaultMessage: `You can't recover deleted sheets.`,
}),
confirmModalOptions
)
.then(isConfirmed => {
if (isConfirmed) {
doDelete();
}
});
});
},
testId: 'timelionDeleteButton',

View file

@ -23,7 +23,6 @@ import '../config';
import '../notify';
import '../private';
import '../promises';
import '../modals';
import '../state_management/app_state';
import '../state_management/global_state';
import '../style_compile';

View file

@ -1,137 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import angular from 'angular';
import expect from '@kbn/expect';
import ngMock from 'ng_mock';
import sinon from 'sinon';
import _ from 'lodash';
describe('ui/modals/confirm_modal', function() {
let confirmModal;
let $rootScope;
beforeEach(function() {
ngMock.module('kibana');
ngMock.inject(function($injector) {
confirmModal = $injector.get('confirmModal');
$rootScope = $injector.get('$rootScope');
});
});
function findByDataTestSubj(dataTestSubj) {
return angular.element(document.body).find(`[data-test-subj=${dataTestSubj}]`);
}
afterEach(function() {
const confirmButton = findByDataTestSubj('confirmModalConfirmButton');
if (confirmButton) {
angular.element(confirmButton).click();
}
});
describe('throws an exception', function() {
it('when no custom confirm button passed', function() {
expect(() => confirmModal('hi', { onConfirm: _.noop })).to.throwError();
});
it('when no custom noConfirm function is passed', function() {
expect(() => confirmModal('hi', { confirmButtonText: 'bye' })).to.throwError();
});
it('when showClose is on but title is not given', function() {
const options = { customConfirmButton: 'b', onConfirm: _.noop, showClose: true };
expect(() => confirmModal('hi', options)).to.throwError();
});
});
it('shows the message', function() {
const myMessage = 'Hi, how are you?';
confirmModal(myMessage, { confirmButtonText: 'GREAT!', onConfirm: _.noop });
$rootScope.$digest();
const message = findByDataTestSubj('confirmModalBodyText')[0].innerText.trim();
expect(message).to.equal(myMessage);
});
describe('shows custom text', function() {
const confirmModalOptions = {
confirmButtonText: 'Troodon',
cancelButtonText: 'Dilophosaurus',
title: 'Dinosaurs',
onConfirm: _.noop,
};
it('for confirm button', () => {
confirmModal("What's your favorite dinosaur?", confirmModalOptions);
$rootScope.$digest();
const confirmButtonText = findByDataTestSubj('confirmModalConfirmButton')[0].innerText.trim();
expect(confirmButtonText).to.equal('Troodon');
});
it('for cancel button', () => {
confirmModal("What's your favorite dinosaur?", confirmModalOptions);
$rootScope.$digest();
const cancelButtonText = findByDataTestSubj('confirmModalCancelButton')[0].innerText.trim();
expect(cancelButtonText).to.equal('Dilophosaurus');
});
it('for title text', () => {
confirmModal("What's your favorite dinosaur?", confirmModalOptions);
$rootScope.$digest();
const titleText = findByDataTestSubj('confirmModalTitleText')[0].innerText.trim();
expect(titleText).to.equal('Dinosaurs');
});
});
describe('callbacks are called:', function() {
const confirmCallback = sinon.spy();
const cancelCallback = sinon.spy();
const confirmModalOptions = {
confirmButtonText: 'bye',
onConfirm: confirmCallback,
onCancel: cancelCallback,
title: 'hi',
};
beforeEach(() => {
confirmCallback.resetHistory();
cancelCallback.resetHistory();
});
it('onCancel', function() {
confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalCancelButton').click();
expect(confirmCallback.called).to.be(false);
expect(cancelCallback.called).to.be(true);
});
it('onConfirm', function() {
confirmModal('hi', confirmModalOptions);
$rootScope.$digest();
findByDataTestSubj('confirmModalConfirmButton').click();
expect(confirmCallback.called).to.be(true);
expect(cancelCallback.called).to.be(false);
});
});
});

View file

@ -1,115 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import expect from '@kbn/expect';
import testSubjSelector from '@kbn/test-subj-selector';
import ngMock from 'ng_mock';
import sinon from 'sinon';
import $ from 'jquery';
describe('ui/modals/confirm_modal_promise', function() {
let $rootScope;
let message;
let confirmModalPromise;
let promise;
beforeEach(function() {
ngMock.module('kibana');
ngMock.inject(function($injector) {
confirmModalPromise = $injector.get('confirmModalPromise');
$rootScope = $injector.get('$rootScope');
});
message = 'woah';
promise = confirmModalPromise(message, { confirmButtonText: 'click me' });
});
afterEach(function() {
$rootScope.$digest();
$(testSubjSelector('confirmModalConfirmButton')).click();
});
describe('before timeout completes', function() {
it('returned promise is not resolved', function() {
const callback = sinon.spy();
promise.then(callback, callback);
$rootScope.$apply();
expect(callback.called).to.be(false);
});
});
describe('after timeout completes', function() {
it('confirmation dialogue is loaded to dom with message', function() {
$rootScope.$digest();
const confirmModalElement = $(testSubjSelector('confirmModal'));
expect(confirmModalElement).to.not.be(undefined);
const htmlString = confirmModalElement[0].innerHTML;
expect(htmlString.indexOf(message)).to.be.greaterThan(0);
});
describe('when confirmed', function() {
it('promise is fulfilled with true', function() {
const confirmCallback = sinon.spy();
const cancelCallback = sinon.spy();
promise.then(confirmCallback, cancelCallback);
$rootScope.$digest();
const confirmButton = $(testSubjSelector('confirmModalConfirmButton'));
confirmButton.click();
expect(confirmCallback.called).to.be(true);
expect(cancelCallback.called).to.be(false);
});
});
describe('when canceled', function() {
it('promise is rejected with false', function() {
const confirmCallback = sinon.spy();
const cancelCallback = sinon.spy();
promise.then(confirmCallback, cancelCallback);
$rootScope.$digest();
const noButton = $(testSubjSelector('confirmModalCancelButton'));
noButton.click();
expect(cancelCallback.called).to.be(true);
expect(confirmCallback.called).to.be(false);
});
});
describe('error is thrown', function() {
it('when no confirm button text is used', function() {
const confirmCallback = sinon.spy();
const cancelCallback = sinon.spy();
confirmModalPromise(message).then(confirmCallback, cancelCallback);
$rootScope.$digest();
sinon.assert.notCalled(confirmCallback);
sinon.assert.calledOnce(cancelCallback);
sinon.assert.calledWithExactly(
cancelCallback,
sinon.match.has('message', sinon.match(/confirmation button text/))
);
});
});
});
});

View file

@ -1,10 +0,0 @@
<confirm-modal
data-test-subj="confirmModal"
on-cancel="onCancel"
on-confirm="onConfirm"
confirm-button-text="confirmButtonText"
cancel-button-text="cancelButtonText"
children="message"
data-title="title"
default-focused-button="defaultFocusedButton"
></confirm-modal>

View file

@ -1,119 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import angular from 'angular';
import { i18n } from '@kbn/i18n';
import { noop } from 'lodash';
import { uiModules } from '../modules';
import template from './confirm_modal.html';
import { ModalOverlay } from './modal_overlay';
const module = uiModules.get('kibana');
import {
EUI_MODAL_CONFIRM_BUTTON as CONFIRM_BUTTON,
EUI_MODAL_CANCEL_BUTTON as CANCEL_BUTTON,
} from '@elastic/eui';
export const ConfirmationButtonTypes = {
CONFIRM: CONFIRM_BUTTON,
CANCEL: CANCEL_BUTTON,
};
export function confirmModalFactory($rootScope, $compile) {
let modalPopover;
const confirmQueue = [];
/**
* @param {String} message - the message to show in the body of the confirmation dialog.
* @param {ConfirmModalOptions} - Options to further customize the dialog.
*/
return function confirmModal(message, customOptions) {
const defaultOptions = {
onCancel: noop,
cancelButtonText: i18n.translate('common.ui.modals.cancelButtonLabel', {
defaultMessage: 'Cancel',
}),
defaultFocusedButton: ConfirmationButtonTypes.CONFIRM,
};
if (!customOptions.confirmButtonText || !customOptions.onConfirm) {
throw new Error('Please specify confirmation button text and onConfirm action');
}
const options = Object.assign(defaultOptions, customOptions);
// Special handling for onClose - if no specific callback was supplied, default to the
// onCancel callback.
options.onClose = customOptions.onClose || options.onCancel;
const confirmScope = $rootScope.$new();
confirmScope.message = message;
confirmScope.defaultFocusedButton = options.defaultFocusedButton;
confirmScope.confirmButtonText = options.confirmButtonText;
confirmScope.cancelButtonText = options.cancelButtonText;
confirmScope.title = options.title;
confirmScope.onConfirm = () => {
destroy();
options.onConfirm();
};
confirmScope.onCancel = () => {
destroy();
options.onCancel();
};
confirmScope.onClose = () => {
destroy();
options.onClose();
};
function showModal(confirmScope) {
const modalInstance = $compile(template)(confirmScope);
modalPopover = new ModalOverlay(modalInstance);
}
if (modalPopover) {
confirmQueue.unshift(confirmScope);
} else {
showModal(confirmScope);
}
function destroy() {
modalPopover.destroy();
modalPopover = undefined;
angular.element(document.body).off('keydown');
confirmScope.$destroy();
if (confirmQueue.length > 0) {
showModal(confirmQueue.pop());
}
}
};
}
/**
* @typedef {Object} ConfirmModalOptions
* @property {String} confirmButtonText
* @property {String=} cancelButtonText
* @property {function} onConfirm
* @property {function=} onCancel
* @property {String=} title - If given, shows a title on the confirm modal.
*/
module.factory('confirmModal', confirmModalFactory);

View file

@ -1,49 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { uiModules } from '../modules';
import './';
const module = uiModules.get('kibana');
/**
* @typedef {Object} PromisifiedConfirmOptions
* @property {String} confirmButtonText
* @property {String=} cancelButtonText
*/
/**
* A "promisified" version of ConfirmModal that binds onCancel and onConfirm to
* Resolve and Reject methods.
*/
module.factory('confirmModalPromise', function(Promise, confirmModal) {
/**
* @param {String} message
* @param {PromisifiedConfirmOptions} customOptions
*/
return (message, customOptions) =>
new Promise((resolve, reject) => {
const defaultOptions = {
onConfirm: resolve,
onCancel: reject,
};
const confirmOptions = Object.assign(defaultOptions, customOptions);
confirmModal(message, confirmOptions);
});
});

View file

@ -1,24 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import './confirm_modal';
import './confirm_modal_promise';
export { ConfirmationButtonTypes } from './confirm_modal';
export { ModalOverlay } from './modal_overlay';

View file

@ -1 +0,0 @@
<div class="euiOverlayMask" data-test-subj="modalOverlay"></div>

View file

@ -1,40 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import angular from 'angular';
import modalOverlayTemplate from './modal_overlay.html';
/**
* Appends the modal to the dom on instantiation, and removes it when destroy is called.
*/
export class ModalOverlay {
constructor(modalElement) {
this.overlayElement = angular.element(modalOverlayTemplate);
this.overlayElement.append(modalElement);
angular.element(document.body).append(this.overlayElement);
}
/**
* Removes the overlay and modal from the dom.
*/
destroy() {
this.overlayElement.remove();
}
}

View file

@ -176,7 +176,11 @@ let isAutoRefreshSelectorEnabled = true;
export const npStart = {
core: {
chrome: {},
chrome: {
overlays: {
openModal: sinon.fake(),
},
},
},
plugins: {
management: {

View file

@ -19,14 +19,12 @@
import 'ngreact';
import { EuiConfirmModal, EuiIcon, EuiIconTip } from '@elastic/eui';
import { EuiIcon, EuiIconTip } from '@elastic/eui';
import { uiModules } from './modules';
const app = uiModules.get('app/kibana', ['react']);
app.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
app.directive('icon', reactDirective => reactDirective(EuiIcon));
app.directive('iconTip', reactDirective =>

View file

@ -26,7 +26,7 @@ import { createSavedObjectClass } from '../saved_object';
import StubIndexPattern from 'test_utils/stub_index_pattern';
import { npStart } from 'ui/new_platform';
import { InvalidJSONProperty } from '../../../../../plugins/kibana_utils/public';
import { npSetup } from '../../new_platform/new_platform.karma_mock';
import { npSetup, npStart as npStartMock } from '../../new_platform/new_platform.karma_mock';
const getConfig = cfg => cfg;
@ -89,18 +89,12 @@ describe('Saved Object', function() {
obj[fName].restore && obj[fName].restore();
}
beforeEach(
ngMock.module(
'kibana',
// Use the native window.confirm instead of our specialized version to make testing
// this easier.
function($provide) {
const overrideConfirm = message =>
window.confirm(message) ? Promise.resolve() : Promise.reject();
$provide.decorator('confirmModalPromise', () => overrideConfirm);
}
)
);
beforeEach(() => {
// Use the native window.confirm instead of our specialized version to make testing
// this easier.
npStartMock.core.overlays.openModal = message =>
window.confirm(message) ? Promise.resolve() : Promise.reject();
});
beforeEach(
ngMock.inject(function($window) {

View file

@ -49,6 +49,7 @@ export function initGraphApp(angularModule, deps) {
storage,
canEditDrillDownUrls,
graphSavePolicy,
overlays,
} = deps;
const app = angularModule;
@ -162,7 +163,7 @@ export function initGraphApp(angularModule, deps) {
});
//======== Controller for basic UI ==================
app.controller('graphuiPlugin', function($scope, $route, $location, confirmModal) {
app.controller('graphuiPlugin', function($scope, $route, $location) {
function handleError(err) {
const toastTitle = i18n.translate('xpack.graph.errorToastTitle', {
defaultMessage: 'Graph Error',
@ -382,23 +383,29 @@ export function initGraphApp(angularModule, deps) {
return;
}
const confirmModalOptions = {
onConfirm: callback,
onCancel: () => {},
confirmButtonText: i18n.translate('xpack.graph.leaveWorkspace.confirmButtonLabel', {
defaultMessage: 'Leave anyway',
}),
title: i18n.translate('xpack.graph.leaveWorkspace.modalTitle', {
defaultMessage: 'Unsaved changes',
}),
'data-test-subj': 'confirmModal',
...options,
};
confirmModal(
text ||
i18n.translate('xpack.graph.leaveWorkspace.confirmText', {
defaultMessage: 'If you leave now, you will lose unsaved changes.',
}),
confirmModalOptions
);
overlays
.openConfirm(
text ||
i18n.translate('xpack.graph.leaveWorkspace.confirmText', {
defaultMessage: 'If you leave now, you will lose unsaved changes.',
}),
confirmModalOptions
)
.then(isConfirmed => {
if (isConfirmed) {
callback();
}
});
}
$scope.confirmWipeWorkspace = canWipeWorkspace;

View file

@ -4,8 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiConfirmModal } from '@elastic/eui';
// inner angular imports
// these are necessary to bootstrap the local angular.
// They can stay even after NP cutover
@ -20,12 +18,12 @@ import {
SavedObjectsClientContract,
ToastsStart,
IUiSettingsClient,
OverlayStart,
} from 'kibana/public';
import {
configureAppAngularModule,
createTopNavDirective,
createTopNavHelper,
confirmModalFactory,
addAppRedirectMessageToUrl,
} from './legacy_imports';
// @ts-ignore
@ -64,6 +62,7 @@ export interface GraphDependencies {
storage: Storage;
canEditDrillDownUrls: boolean;
graphSavePolicy: string;
overlays: OverlayStart;
}
export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => {
@ -120,24 +119,15 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) {
function createLocalAngularModule(navigation: NavigationStart) {
createLocalI18nModule();
createLocalTopNavModule(navigation);
createLocalConfirmModalModule();
const graphAngularModule = angular.module(moduleName, [
...thirdPartyAngularDependencies,
'graphI18n',
'graphTopNav',
'graphConfirmModal',
]);
return graphAngularModule;
}
function createLocalConfirmModalModule() {
angular
.module('graphConfirmModal', ['react'])
.factory('confirmModal', confirmModalFactory)
.directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal));
}
function createLocalTopNavModule(navigation: NavigationStart) {
angular
.module('graphTopNav', ['react'])

View file

@ -11,8 +11,6 @@ export { SavedObject, SavedObjectKibanaServices } from 'ui/saved_objects/types';
// @ts-ignore
export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav';
// @ts-ignore
export { confirmModalFactory } from 'ui/modals/confirm_modal';
// @ts-ignore
export { addAppRedirectMessageToUrl } from 'ui/notify';
export { createSavedObjectClass } from 'ui/saved_objects/saved_object';
export { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public';

View file

@ -50,6 +50,7 @@ export class GraphPlugin implements Plugin {
config: contextCore.uiSettings,
toastNotifications: contextCore.notifications.toasts,
indexPatterns: this.npDataStart!.indexPatterns,
overlays: contextCore.overlays,
});
},
});

View file

@ -352,7 +352,6 @@
"common.ui.flotCharts.thuLabel": "木",
"common.ui.flotCharts.tueLabel": "火",
"common.ui.flotCharts.wedLabel": "水",
"common.ui.modals.cancelButtonLabel": "キャンセル",
"common.ui.paginateControls.pageSizeLabel": "ページサイズ",
"common.ui.paginateControls.scrollTopButtonLabel": "最上部に移動",
"common.ui.savedObjects.confirmModal.overwriteButtonLabel": "上書き",

View file

@ -352,7 +352,6 @@
"common.ui.flotCharts.thuLabel": "周四",
"common.ui.flotCharts.tueLabel": "周二",
"common.ui.flotCharts.wedLabel": "周三",
"common.ui.modals.cancelButtonLabel": "取消",
"common.ui.paginateControls.pageSizeLabel": "页面大小",
"common.ui.paginateControls.scrollTopButtonLabel": "滚动至顶部",
"common.ui.savedObjects.confirmModal.overwriteButtonLabel": "覆盖",