mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Enterprise Search] Confirmation modal for deleting Crawler domains in Kibana Content app (#136481)
This commit is contained in:
parent
7f55a1a9e0
commit
fef8e72286
8 changed files with 276 additions and 59 deletions
|
@ -10,12 +10,19 @@ import { HttpLogic } from '../../../shared/http';
|
|||
|
||||
import { CrawlerDomain } from './types';
|
||||
|
||||
export interface GetCrawlerDomainsArgs {
|
||||
export interface DeleteCrawlerDomainArgs {
|
||||
domain: CrawlerDomain;
|
||||
indexName: string;
|
||||
}
|
||||
|
||||
export const deleteCrawlerDomain = async ({ domain, indexName }: GetCrawlerDomainsArgs) => {
|
||||
export interface DeleteCrawlerDomainResponse {
|
||||
domain: CrawlerDomain;
|
||||
}
|
||||
|
||||
export const deleteCrawlerDomain = async ({
|
||||
domain,
|
||||
indexName,
|
||||
}: DeleteCrawlerDomainArgs): Promise<DeleteCrawlerDomainResponse> => {
|
||||
await HttpLogic.values.http.delete(
|
||||
`/internal/enterprise_search/indices/${indexName}/crawler/domains/${domain.id}`
|
||||
);
|
||||
|
|
|
@ -22,7 +22,8 @@ import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
|
|||
import { CrawlCustomSettingsFlyout } from '../search_index/crawler/crawl_custom_settings_flyout/crawl_custom_settings_flyout';
|
||||
import { CrawlerStatusIndicator } from '../search_index/crawler/crawler_status_indicator/crawler_status_indicator';
|
||||
import { CrawlerStatusBanner } from '../search_index/crawler/domain_management/crawler_status_banner';
|
||||
import { getDeleteDomainConfirmationMessage } from '../search_index/crawler/utils';
|
||||
import { DeleteDomainModal } from '../search_index/crawler/domain_management/delete_domain_modal';
|
||||
import { DeleteDomainModalLogic } from '../search_index/crawler/domain_management/delete_domain_modal_logic';
|
||||
import { IndexNameLogic } from '../search_index/index_name_logic';
|
||||
import { SearchIndexTabId } from '../search_index/search_index';
|
||||
import { baseBreadcrumbs } from '../search_indices';
|
||||
|
@ -40,8 +41,9 @@ export const CrawlerDomainDetail: React.FC = () => {
|
|||
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const crawlerDomainDetailLogic = CrawlerDomainDetailLogic({ domainId });
|
||||
const { deleteLoading, domain, getLoading } = useValues(crawlerDomainDetailLogic);
|
||||
const { fetchDomainData, deleteDomain } = useActions(crawlerDomainDetailLogic);
|
||||
const { domain, getLoading } = useValues(crawlerDomainDetailLogic);
|
||||
const { fetchDomainData } = useActions(crawlerDomainDetailLogic);
|
||||
const { showModal } = useActions(DeleteDomainModalLogic);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDomainData(domainId);
|
||||
|
@ -58,11 +60,11 @@ export const CrawlerDomainDetail: React.FC = () => {
|
|||
rightSideItems: [
|
||||
<CrawlerStatusIndicator />,
|
||||
<EuiButton
|
||||
isLoading={getLoading || deleteLoading}
|
||||
isLoading={getLoading}
|
||||
color="danger"
|
||||
onClick={() => {
|
||||
if (window.confirm(getDeleteDomainConfirmationMessage(domainUrl))) {
|
||||
deleteDomain();
|
||||
if (domain) {
|
||||
showModal(domain);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -110,6 +112,7 @@ export const CrawlerDomainDetail: React.FC = () => {
|
|||
<DeduplicationPanel />
|
||||
</>
|
||||
)}
|
||||
<DeleteDomainModal />
|
||||
<CrawlCustomSettingsFlyout />
|
||||
</EnterpriseSearchContentPageTemplate>
|
||||
);
|
||||
|
|
|
@ -9,12 +9,19 @@ import { kea, MakeLogicType } from 'kea';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { HttpError, Status } from '../../../../../common/types/api';
|
||||
|
||||
import { generateEncodedPath } from '../../../shared/encode_path_params';
|
||||
|
||||
import { flashAPIErrors, flashSuccessToast } from '../../../shared/flash_messages';
|
||||
|
||||
import { HttpLogic } from '../../../shared/http';
|
||||
import { KibanaLogic } from '../../../shared/kibana';
|
||||
import {
|
||||
DeleteCrawlerDomainApiLogic,
|
||||
DeleteCrawlerDomainArgs,
|
||||
DeleteCrawlerDomainResponse,
|
||||
} from '../../api/crawler/delete_crawler_domain_api_logic';
|
||||
import {
|
||||
CrawlerDomain,
|
||||
CrawlerDomainFromServer,
|
||||
|
@ -33,14 +40,17 @@ export interface CrawlerDomainDetailProps {
|
|||
|
||||
export interface CrawlerDomainDetailValues {
|
||||
deleteLoading: boolean;
|
||||
deleteStatus: Status;
|
||||
domain: CrawlerDomain | null;
|
||||
domainId: string;
|
||||
getLoading: boolean;
|
||||
}
|
||||
|
||||
interface CrawlerDomainDetailActions {
|
||||
deleteApiError(error: HttpError): HttpError;
|
||||
deleteApiSuccess(response: DeleteCrawlerDomainResponse): DeleteCrawlerDomainResponse;
|
||||
deleteDomain(): void;
|
||||
deleteDomainComplete(): void;
|
||||
deleteMakeRequest(args: DeleteCrawlerDomainArgs): DeleteCrawlerDomainArgs;
|
||||
fetchDomainData(domainId: string): { domainId: string };
|
||||
receiveDomainData(domain: CrawlerDomain): { domain: CrawlerDomain };
|
||||
submitDeduplicationUpdate(payload: { enabled?: boolean; fields?: string[] }): {
|
||||
|
@ -56,6 +66,17 @@ export const CrawlerDomainDetailLogic = kea<
|
|||
MakeLogicType<CrawlerDomainDetailValues, CrawlerDomainDetailActions>
|
||||
>({
|
||||
path: ['enterprise_search', 'crawler', 'crawler_domain_detail_logic'],
|
||||
connect: {
|
||||
actions: [
|
||||
DeleteCrawlerDomainApiLogic,
|
||||
[
|
||||
'apiError as deleteApiError',
|
||||
'apiSuccess as deleteApiSuccess',
|
||||
'makeRequest as deleteMakeRequest',
|
||||
],
|
||||
],
|
||||
values: [DeleteCrawlerDomainApiLogic, ['status as deleteStatus']],
|
||||
},
|
||||
actions: {
|
||||
deleteDomain: () => true,
|
||||
deleteDomainComplete: () => true,
|
||||
|
@ -67,13 +88,6 @@ export const CrawlerDomainDetailLogic = kea<
|
|||
updateSitemaps: (sitemaps) => ({ sitemaps }),
|
||||
},
|
||||
reducers: ({ props }) => ({
|
||||
deleteLoading: [
|
||||
false,
|
||||
{
|
||||
deleteDomain: () => true,
|
||||
deleteDomainComplete: () => false,
|
||||
},
|
||||
],
|
||||
domain: [
|
||||
null,
|
||||
{
|
||||
|
@ -94,34 +108,44 @@ export const CrawlerDomainDetailLogic = kea<
|
|||
},
|
||||
],
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
deleteLoading: [
|
||||
() => [selectors.deleteStatus],
|
||||
(deleteStatus: Status) => deleteStatus === Status.LOADING,
|
||||
],
|
||||
}),
|
||||
listeners: ({ actions, values }) => ({
|
||||
deleteDomain: async () => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { domain, domainId } = values;
|
||||
const { domain } = values;
|
||||
const { indexName } = IndexNameLogic.values;
|
||||
try {
|
||||
await http.delete(
|
||||
`/internal/enterprise_search/indices/${indexName}/crawler/domains/${domainId}`
|
||||
);
|
||||
flashSuccessToast(
|
||||
i18n.translate('xpack.enterpriseSearch.crawler.action.deleteDomain.successMessage', {
|
||||
defaultMessage: "Domain '{domainUrl}' was deleted",
|
||||
values: {
|
||||
domainUrl: domain?.url,
|
||||
},
|
||||
})
|
||||
);
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName,
|
||||
tabId: SearchIndexTabId.DOMAIN_MANAGEMENT,
|
||||
})
|
||||
);
|
||||
} catch (e) {
|
||||
flashAPIErrors(e);
|
||||
if (domain) {
|
||||
actions.deleteMakeRequest({
|
||||
domain,
|
||||
indexName,
|
||||
});
|
||||
}
|
||||
actions.deleteDomainComplete();
|
||||
},
|
||||
deleteApiSuccess: ({ domain }) => {
|
||||
const { indexName } = IndexNameLogic.values;
|
||||
flashSuccessToast(
|
||||
i18n.translate('xpack.enterpriseSearch.crawler.action.deleteDomain.successMessage', {
|
||||
defaultMessage: "Domain '{domainUrl}' was deleted",
|
||||
values: {
|
||||
domainUrl: domain?.url,
|
||||
},
|
||||
})
|
||||
);
|
||||
KibanaLogic.values.navigateToUrl(
|
||||
generateEncodedPath(SEARCH_INDEX_TAB_PATH, {
|
||||
indexName,
|
||||
tabId: SearchIndexTabId.DOMAIN_MANAGEMENT,
|
||||
})
|
||||
);
|
||||
},
|
||||
deleteApiError: (error) => {
|
||||
flashAPIErrors(error);
|
||||
},
|
||||
|
||||
fetchDomainData: async ({ domainId }) => {
|
||||
const { http } = HttpLogic.values;
|
||||
const { indexName } = IndexNameLogic.values;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiButtonEmpty,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { CANCEL_BUTTON_LABEL } from '../../../../../shared/constants';
|
||||
|
||||
import { DeleteCrawlerDomainApiLogic } from '../../../../api/crawler/delete_crawler_domain_api_logic';
|
||||
|
||||
import { DeleteDomainModalLogic } from './delete_domain_modal_logic';
|
||||
|
||||
export const DeleteDomainModal: React.FC = () => {
|
||||
DeleteCrawlerDomainApiLogic.mount();
|
||||
const { deleteDomain, hideModal } = useActions(DeleteDomainModalLogic);
|
||||
const { domain, isLoading, isHidden } = useValues(DeleteDomainModalLogic);
|
||||
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiModal
|
||||
onClose={hideModal}
|
||||
aria-label={i18n.translate('xpack.enterpriseSearch.crawler.deleteDomainModal.title', {
|
||||
defaultMessage: 'Delete domain',
|
||||
})}
|
||||
>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
{i18n.translate('xpack.enterpriseSearch.crawler.deleteDomainModal.title', {
|
||||
defaultMessage: 'Delete domain',
|
||||
})}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.enterpriseSearch.crawler.deleteDomainModal.description"
|
||||
defaultMessage="Remove the domain {domainUrl} from your crawler. This will also delete all entry points and crawl rules you have set up. Any documents related to this domain will be removed on the next crawl. {thisCannotBeUndoneMessage}"
|
||||
values={{
|
||||
domainUrl: <strong>{domain?.url}</strong>,
|
||||
thisCannotBeUndoneMessage: (
|
||||
<strong>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.crawler.deleteDomainModal.thisCannotBeUndoneMessage',
|
||||
{
|
||||
defaultMessage: 'This cannot be undone.',
|
||||
}
|
||||
)}
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty onClick={hideModal}>{CANCEL_BUTTON_LABEL}</EuiButtonEmpty>
|
||||
<EuiButton onClick={deleteDomain} isLoading={isLoading} color="danger" fill>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.crawler.deleteDomainModal.deleteDomainButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Delete domain',
|
||||
}
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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 { kea, MakeLogicType } from 'kea';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { Status } from '../../../../../../../common/types/api';
|
||||
import { Actions } from '../../../../../shared/api_logic/create_api_logic';
|
||||
import { flashAPIErrors, flashSuccessToast } from '../../../../../shared/flash_messages';
|
||||
import {
|
||||
DeleteCrawlerDomainApiLogic,
|
||||
DeleteCrawlerDomainResponse,
|
||||
DeleteCrawlerDomainArgs,
|
||||
} from '../../../../api/crawler/delete_crawler_domain_api_logic';
|
||||
import { CrawlerDomain } from '../../../../api/crawler/types';
|
||||
import { IndexNameLogic } from '../../index_name_logic';
|
||||
import { CrawlerLogic } from '../crawler_logic';
|
||||
|
||||
interface DeleteDomainModalValues {
|
||||
domain: CrawlerDomain | null;
|
||||
isHidden: boolean;
|
||||
isLoading: boolean;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
type DeleteDomainModalActions = Pick<
|
||||
Actions<DeleteCrawlerDomainArgs, DeleteCrawlerDomainResponse>,
|
||||
'apiError' | 'apiSuccess' | 'makeRequest'
|
||||
> & {
|
||||
deleteDomain(): void;
|
||||
hideModal(): void;
|
||||
showModal(domain: CrawlerDomain): { domain: CrawlerDomain };
|
||||
};
|
||||
|
||||
export const DeleteDomainModalLogic = kea<
|
||||
MakeLogicType<DeleteDomainModalValues, DeleteDomainModalActions>
|
||||
>({
|
||||
path: ['enterprise_search', 'delete_domain_modal'],
|
||||
connect: {
|
||||
actions: [DeleteCrawlerDomainApiLogic, ['apiError', 'apiSuccess']],
|
||||
values: [DeleteCrawlerDomainApiLogic, ['status']],
|
||||
},
|
||||
actions: {
|
||||
deleteDomain: () => true,
|
||||
hideModal: () => true,
|
||||
showModal: (domain) => ({ domain }),
|
||||
},
|
||||
reducers: {
|
||||
domain: [
|
||||
null,
|
||||
{
|
||||
showModal: (_, { domain }) => domain,
|
||||
},
|
||||
],
|
||||
isHidden: [
|
||||
true,
|
||||
{
|
||||
apiError: () => true,
|
||||
apiSuccess: () => true,
|
||||
hideModal: () => true,
|
||||
showModal: () => false,
|
||||
},
|
||||
],
|
||||
},
|
||||
listeners: ({ values }) => ({
|
||||
apiError: (error) => {
|
||||
flashAPIErrors(error);
|
||||
},
|
||||
apiSuccess: ({ domain }) => {
|
||||
flashSuccessToast(
|
||||
i18n.translate('xpack.enterpriseSearch.crawler.domainsTable.action.delete.successMessage', {
|
||||
defaultMessage: "Successfully deleted domain '{domainUrl}'",
|
||||
values: {
|
||||
domainUrl: domain.url,
|
||||
},
|
||||
})
|
||||
);
|
||||
CrawlerLogic.actions.fetchCrawlerData();
|
||||
},
|
||||
deleteDomain: () => {
|
||||
const { domain } = values;
|
||||
const { indexName } = IndexNameLogic.values;
|
||||
if (domain) {
|
||||
DeleteCrawlerDomainApiLogic.actions.makeRequest({ domain, indexName });
|
||||
}
|
||||
},
|
||||
}),
|
||||
selectors: ({ selectors }) => ({
|
||||
isLoading: [
|
||||
() => [selectors.status],
|
||||
(status: DeleteDomainModalValues['status']) => status === Status.LOADING,
|
||||
],
|
||||
}),
|
||||
});
|
|
@ -18,6 +18,7 @@ import { GetCrawlerDomainsApiLogic } from '../../../../api/crawler/get_crawler_d
|
|||
|
||||
import { AddDomainFlyout } from './add_domain/add_domain_flyout';
|
||||
import { CrawlerStatusBanner } from './crawler_status_banner';
|
||||
import { DeleteDomainModal } from './delete_domain_modal';
|
||||
import { DomainManagementLogic } from './domain_management_logic';
|
||||
import { DomainsPanel } from './domains_panel';
|
||||
import { EmptyStatePanel } from './empty_state_panel';
|
||||
|
@ -36,6 +37,7 @@ export const SearchIndexDomainManagement: React.FC = () => {
|
|||
<EuiSpacer />
|
||||
<CrawlerStatusBanner />
|
||||
{domains.length > 0 ? <DomainsPanel /> : <EmptyStatePanel />}
|
||||
<DeleteDomainModal />
|
||||
<AddDomainFlyout />
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -66,9 +66,10 @@ const values = {
|
|||
|
||||
const actions = {
|
||||
// CrawlerDomainsLogic
|
||||
deleteDomain: jest.fn(),
|
||||
fetchCrawlerDomainsData: jest.fn(),
|
||||
onPaginate: jest.fn(),
|
||||
// DeleteDomainModalLogic
|
||||
showModal: jest.fn(),
|
||||
};
|
||||
|
||||
describe('DomainsTable', () => {
|
||||
|
@ -161,21 +162,9 @@ describe('DomainsTable', () => {
|
|||
|
||||
describe('delete action', () => {
|
||||
it('clicking the action and confirming deletes the domain', () => {
|
||||
jest.spyOn(global, 'confirm').mockReturnValueOnce(true);
|
||||
|
||||
getDeleteAction().simulate('click');
|
||||
|
||||
expect(actions.deleteDomain).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ id: '1234' })
|
||||
);
|
||||
});
|
||||
|
||||
it('clicking the action and not confirming does not delete the engine', () => {
|
||||
jest.spyOn(global, 'confirm').mockReturnValueOnce(false);
|
||||
|
||||
getDeleteAction().simulate('click');
|
||||
|
||||
expect(actions.deleteDomain).not.toHaveBeenCalled();
|
||||
expect(actions.showModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,14 +26,14 @@ import { CrawlerDomain } from '../../../../api/crawler/types';
|
|||
import { SEARCH_INDEX_CRAWLER_DOMAIN_DETAIL_PATH } from '../../../../routes';
|
||||
import { IndexNameLogic } from '../../index_name_logic';
|
||||
|
||||
import { getDeleteDomainConfirmationMessage } from '../utils';
|
||||
|
||||
import { DeleteDomainModalLogic } from './delete_domain_modal_logic';
|
||||
import { DomainManagementLogic } from './domain_management_logic';
|
||||
|
||||
export const DomainsTable: React.FC = () => {
|
||||
const { indexName } = useValues(IndexNameLogic);
|
||||
const { domains, meta, isLoading } = useValues(DomainManagementLogic);
|
||||
const { deleteDomain, onPaginate } = useActions(DomainManagementLogic);
|
||||
const { onPaginate } = useActions(DomainManagementLogic);
|
||||
const { showModal } = useActions(DeleteDomainModalLogic);
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<CrawlerDomain>> = [
|
||||
{
|
||||
|
@ -106,9 +106,7 @@ export const DomainsTable: React.FC = () => {
|
|||
icon: 'trash',
|
||||
color: 'danger',
|
||||
onClick: (domain) => {
|
||||
if (window.confirm(getDeleteDomainConfirmationMessage(domain.url))) {
|
||||
deleteDomain(domain);
|
||||
}
|
||||
showModal(domain);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue