[App Search] Migrate Delete Domain Panel (#107795)
* Add deleteDomain action to CrawlerSingleDomainLogic * New DeleteDomainPanel component * Added DeleteDomainPanel to CrawlerSingleDomain * Missing tests for DeleteDomainPanel * Abstract getDeleteDomainConfirmationMessage
This commit is contained in:
parent
58054c3325
commit
be5f538a1b
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { setMockActions, setMockValues } from '../../../../__mocks__/kea_logic';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiButton } from '@elastic/eui';
|
||||
|
||||
import { DeleteDomainPanel } from './delete_domain_panel';
|
||||
|
||||
const MOCK_VALUES = {
|
||||
domain: { id: '9876' },
|
||||
};
|
||||
|
||||
const MOCK_ACTIONS = {
|
||||
deleteDomain: jest.fn(),
|
||||
};
|
||||
|
||||
describe('DeleteDomainPanel', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('contains a button to delete the domain', () => {
|
||||
setMockValues(MOCK_VALUES);
|
||||
setMockActions(MOCK_ACTIONS);
|
||||
|
||||
const confirmSpy = jest.spyOn(window, 'confirm');
|
||||
confirmSpy.mockImplementation(jest.fn(() => true));
|
||||
|
||||
const wrapper = shallow(<DeleteDomainPanel />);
|
||||
wrapper.find(EuiButton).simulate('click');
|
||||
|
||||
expect(MOCK_ACTIONS.deleteDomain).toHaveBeenCalledWith(MOCK_VALUES.domain);
|
||||
});
|
||||
|
||||
it("doesn't throw if the users chooses not to confirm", () => {
|
||||
setMockValues(MOCK_VALUES);
|
||||
|
||||
const confirmSpy = jest.spyOn(window, 'confirm');
|
||||
confirmSpy.mockImplementation(jest.fn(() => false));
|
||||
|
||||
const wrapper = shallow(<DeleteDomainPanel />);
|
||||
wrapper.find(EuiButton).simulate('click');
|
||||
});
|
||||
|
||||
// The user should never encounter this state, the containing AppSearchTemplate should be loading until
|
||||
// the relevant domain has been loaded. However we must account for the possibility in this component.
|
||||
it('is empty if domain has not yet been set', () => {
|
||||
setMockValues({
|
||||
...MOCK_VALUES,
|
||||
domain: null,
|
||||
});
|
||||
|
||||
const wrapper = shallow(<DeleteDomainPanel />);
|
||||
|
||||
expect(wrapper.isEmptyRender()).toBe(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic';
|
||||
import { getDeleteDomainSuccessMessage } from '../utils';
|
||||
|
||||
export const DeleteDomainPanel: React.FC = ({}) => {
|
||||
const { domain } = useValues(CrawlerSingleDomainLogic);
|
||||
const { deleteDomain } = useActions(CrawlerSingleDomainLogic);
|
||||
|
||||
if (!domain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.description"
|
||||
defaultMessage="Remove this domain from your crawler. This will also delete all entry points and crawl
|
||||
rules you have setup. {cannotUndoMessage}."
|
||||
values={{
|
||||
cannotUndoMessage: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.cannotUndoMessage',
|
||||
{
|
||||
defaultMessage: 'This cannot be undone',
|
||||
}
|
||||
)}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButton
|
||||
color="danger"
|
||||
iconType="trash"
|
||||
onClick={() => {
|
||||
if (confirm(getDeleteDomainSuccessMessage(domain.url))) {
|
||||
deleteDomain(domain);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.deleteDomainPanel.deleteDomainButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Delete domain',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -23,6 +23,8 @@ import { generateEnginePath } from '../../engine';
|
|||
import { CrawlerOverviewLogic } from '../crawler_overview_logic';
|
||||
import { CrawlerDomain } from '../types';
|
||||
|
||||
import { getDeleteDomainConfirmationMessage } from '../utils';
|
||||
|
||||
import { CustomFormattedTimestamp } from './custom_formatted_timestamp';
|
||||
|
||||
export const DomainsTable: React.FC = () => {
|
||||
|
@ -101,20 +103,7 @@ export const DomainsTable: React.FC = () => {
|
|||
icon: 'trash',
|
||||
color: 'danger',
|
||||
onClick: (domain) => {
|
||||
if (
|
||||
window.confirm(
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.confirmationPopupMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Are you sure you want to remove the domain "{domainUrl}" and all of its settings?',
|
||||
values: {
|
||||
domainUrl: domain.url,
|
||||
},
|
||||
}
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (window.confirm(getDeleteDomainConfirmationMessage(domain.url))) {
|
||||
deleteDomain(domain);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,8 +7,6 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages';
|
||||
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
|
@ -21,18 +19,11 @@ import {
|
|||
CrawlRequestFromServer,
|
||||
CrawlerStatus,
|
||||
} from './types';
|
||||
import { crawlerDataServerToClient, crawlRequestServerToClient } from './utils';
|
||||
|
||||
export const DELETE_DOMAIN_MESSAGE = (domainUrl: string) =>
|
||||
i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.domainsTable.action.delete.successMessage',
|
||||
{
|
||||
defaultMessage: "Domain '{domainUrl}' was deleted",
|
||||
values: {
|
||||
domainUrl,
|
||||
},
|
||||
}
|
||||
);
|
||||
import {
|
||||
crawlerDataServerToClient,
|
||||
crawlRequestServerToClient,
|
||||
getDeleteDomainSuccessMessage,
|
||||
} from './utils';
|
||||
|
||||
const POLLING_DURATION = 1000;
|
||||
const POLLING_DURATION_ON_FAILURE = 5000;
|
||||
|
@ -145,7 +136,7 @@ export const CrawlerOverviewLogic = kea<
|
|||
);
|
||||
const crawlerData = crawlerDataServerToClient(response);
|
||||
actions.onReceiveCrawlerData(crawlerData);
|
||||
flashSuccessToast(DELETE_DOMAIN_MESSAGE(domain.url));
|
||||
flashSuccessToast(getDeleteDomainSuccessMessage(domain.url));
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import { getPageHeaderActions } from '../../../test_helpers';
|
|||
|
||||
import { CrawlerStatusBanner } from './components/crawler_status_banner';
|
||||
import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator';
|
||||
import { DeleteDomainPanel } from './components/delete_domain_panel';
|
||||
import { CrawlerOverview } from './crawler_overview';
|
||||
import { CrawlerSingleDomain } from './crawler_single_domain';
|
||||
|
||||
|
@ -50,6 +51,7 @@ describe('CrawlerSingleDomain', () => {
|
|||
it('renders', () => {
|
||||
const wrapper = shallow(<CrawlerSingleDomain />);
|
||||
|
||||
expect(wrapper.find(DeleteDomainPanel)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiCode).render().text()).toContain('https://elastic.co');
|
||||
expect(wrapper.prop('pageHeader').pageTitle).toEqual('https://elastic.co');
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useParams } from 'react-router-dom';
|
|||
|
||||
import { useActions, useValues } from 'kea';
|
||||
|
||||
import { EuiCode, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiCode, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -20,6 +20,7 @@ import { AppSearchPageTemplate } from '../layout';
|
|||
|
||||
import { CrawlerStatusBanner } from './components/crawler_status_banner';
|
||||
import { CrawlerStatusIndicator } from './components/crawler_status_indicator/crawler_status_indicator';
|
||||
import { DeleteDomainPanel } from './components/delete_domain_panel';
|
||||
import { CRAWLER_TITLE } from './constants';
|
||||
import { CrawlerSingleDomainLogic } from './crawler_single_domain_logic';
|
||||
|
||||
|
@ -48,6 +49,19 @@ export const CrawlerSingleDomain: React.FC = () => {
|
|||
>
|
||||
<CrawlerStatusBanner />
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="s">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.singleDomain.deleteDomainTitle',
|
||||
{
|
||||
defaultMessage: 'Delete domain',
|
||||
}
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<DeleteDomainPanel />
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiCode>{JSON.stringify(domain, null, 2)}</EuiCode>
|
||||
</AppSearchPageTemplate>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
LogicMounter,
|
||||
mockHttpValues,
|
||||
mockFlashMessageHelpers,
|
||||
mockKibanaValues,
|
||||
} from '../../../__mocks__/kea_logic';
|
||||
import '../../__mocks__/engine_logic.mock';
|
||||
|
||||
|
@ -25,7 +26,7 @@ const DEFAULT_VALUES: CrawlerSingleDomainValues = {
|
|||
describe('CrawlerSingleDomainLogic', () => {
|
||||
const { mount } = new LogicMounter(CrawlerSingleDomainLogic);
|
||||
const { http } = mockHttpValues;
|
||||
const { flashAPIErrors } = mockFlashMessageHelpers;
|
||||
const { flashAPIErrors, flashSuccessToast } = mockFlashMessageHelpers;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -53,6 +54,33 @@ describe('CrawlerSingleDomainLogic', () => {
|
|||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
describe('deleteDomain', () => {
|
||||
it('flashes a success toast and redirects the user to the crawler overview on success', async () => {
|
||||
const { navigateToUrl } = mockKibanaValues;
|
||||
|
||||
http.delete.mockReturnValue(Promise.resolve());
|
||||
|
||||
CrawlerSingleDomainLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain);
|
||||
await nextTick();
|
||||
|
||||
expect(http.delete).toHaveBeenCalledWith(
|
||||
'/api/app_search/engines/some-engine/crawler/domains/1234'
|
||||
);
|
||||
|
||||
expect(flashSuccessToast).toHaveBeenCalled();
|
||||
expect(navigateToUrl).toHaveBeenCalledWith('/engines/some-engine/crawler');
|
||||
});
|
||||
|
||||
it('calls flashApiErrors when there is an error', async () => {
|
||||
http.delete.mockReturnValue(Promise.reject('error'));
|
||||
|
||||
CrawlerSingleDomainLogic.actions.deleteDomain({ id: '1234' } as CrawlerDomain);
|
||||
await nextTick();
|
||||
|
||||
expect(flashAPIErrors).toHaveBeenCalledWith('error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchDomainData', () => {
|
||||
it('updates logic with data that has been converted from server to client', async () => {
|
||||
jest.spyOn(CrawlerSingleDomainLogic.actions, 'onReceiveDomainData');
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
import { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { flashAPIErrors } from '../../../shared/flash_messages';
|
||||
import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages';
|
||||
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { EngineLogic } from '../engine';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import { ENGINE_CRAWLER_PATH } from '../../routes';
|
||||
import { EngineLogic, generateEnginePath } from '../engine';
|
||||
|
||||
import { CrawlerDomain } from './types';
|
||||
import { crawlerDomainServerToClient } from './utils';
|
||||
import { crawlerDomainServerToClient, getDeleteDomainSuccessMessage } from './utils';
|
||||
|
||||
export interface CrawlerSingleDomainValues {
|
||||
dataLoading: boolean;
|
||||
|
@ -21,6 +23,7 @@ export interface CrawlerSingleDomainValues {
|
|||
}
|
||||
|
||||
interface CrawlerSingleDomainActions {
|
||||
deleteDomain(domain: CrawlerDomain): { domain: CrawlerDomain };
|
||||
fetchDomainData(domainId: string): { domainId: string };
|
||||
onReceiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain };
|
||||
}
|
||||
|
@ -30,6 +33,7 @@ export const CrawlerSingleDomainLogic = kea<
|
|||
>({
|
||||
path: ['enterprise_search', 'app_search', 'crawler', 'crawler_single_domain'],
|
||||
actions: {
|
||||
deleteDomain: (domain) => ({ domain }),
|
||||
fetchDomainData: (domainId) => ({ domainId }),
|
||||
onReceiveDomainData: (domain) => ({ domain }),
|
||||
},
|
||||
|
@ -48,6 +52,19 @@ export const CrawlerSingleDomainLogic = kea<
|
|||
],
|
||||
},
|
||||
listeners: ({ actions }) => ({
|
||||
deleteDomain: async ({ domain }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
||||
try {
|
||||
await http.delete(`/api/app_search/engines/${engineName}/crawler/domains/${domain.id}`);
|
||||
|
||||
flashSuccessToast(getDeleteDomainSuccessMessage(domain.url));
|
||||
KibanaLogic.values.navigateToUrl(generateEnginePath(ENGINE_CRAWLER_PATH));
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
}
|
||||
},
|
||||
fetchDomainData: async ({ domainId }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { engineName } = EngineLogic.values;
|
||||
|
|
|
@ -23,6 +23,8 @@ import {
|
|||
crawlerDataServerToClient,
|
||||
crawlDomainValidationToResult,
|
||||
crawlRequestServerToClient,
|
||||
getDeleteDomainConfirmationMessage,
|
||||
getDeleteDomainSuccessMessage,
|
||||
} from './utils';
|
||||
|
||||
const DEFAULT_CRAWL_RULE: CrawlRule = {
|
||||
|
@ -223,3 +225,17 @@ describe('crawlDomainValidationToResult', () => {
|
|||
} as CrawlerDomainValidationStep);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDeleteDomainConfirmationMessage', () => {
|
||||
it('includes the url', () => {
|
||||
expect(getDeleteDomainConfirmationMessage('https://elastic.co/')).toContain(
|
||||
'https://elastic.co'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDeleteDomainSuccessMessage', () => {
|
||||
it('includes the url', () => {
|
||||
expect(getDeleteDomainSuccessMessage('https://elastic.co/')).toContain('https://elastic.co');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
CrawlerDomain,
|
||||
CrawlerDomainFromServer,
|
||||
|
@ -101,3 +103,28 @@ export function crawlDomainValidationToResult(
|
|||
state: 'valid',
|
||||
};
|
||||
}
|
||||
|
||||
export const getDeleteDomainConfirmationMessage = (domainUrl: string) => {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.confirmationPopupMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Are you sure you want to remove the domain "{domainUrl}" and all of its settings?',
|
||||
values: {
|
||||
domainUrl,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const getDeleteDomainSuccessMessage = (domainUrl: string) => {
|
||||
return i18n.translate(
|
||||
'xpack.enterpriseSearch.appSearch.crawler.action.deleteDomain.successMessage',
|
||||
{
|
||||
defaultMessage: "Domain '{domainUrl}' was deleted",
|
||||
values: {
|
||||
domainUrl,
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue