Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Onboarding] Add delete index action to search index detail page #192428

Merged
merged 12 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/get_doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
elasticStackGetStarted: isServerless
? `${SERVERLESS_DOCS}`
: `${ELASTIC_WEBSITE_URL}guide/en/index.html`,
apiReference: `${ELASTIC_WEBSITE_URL}guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/${DOC_LINK_VERSION}/api-reference.html`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would link this to current, like the other URLS

Suggested change
apiReference: `${ELASTIC_WEBSITE_URL}guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/${DOC_LINK_VERSION}/api-reference.html`,
apiReference: `${ELASTIC_WEBSITE_URL}guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/current/api-reference.html`,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 13ac078

upgrade: {
upgradingStackOnPrem: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack/current/upgrading-elastic-stack-on-prem.html`,
upgradingStackOnCloud: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack/current/upgrade-elastic-stack-for-elastic-cloud.html`,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface DocLinksMeta {
export interface DocLinks {
readonly settings: string;
readonly elasticStackGetStarted: string;
readonly apiReference: string;
readonly upgrade: {
readonly upgradingStackOnPrem: string;
readonly upgradingStackOnCloud: string;
Expand Down
19 changes: 19 additions & 0 deletions x-pack/plugins/search_indices/common/doc_links.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { DocLinks } from '@kbn/doc-links';

class SearchIndicesDocLinks {
public apiReference: string = '';

constructor() {}

setDocLinks(newDocLinks: DocLinks) {
this.apiReference = newDocLinks.apiReference;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joemcelroy Will we need to worry about showing different links here based on serverless and stack?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will do eventually. ATM we rely on stack URLs, waiting on @leemthompo guidance on how that will work out :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lcawl knows more about this topic than me, and I know the autogenerated API refs are coming along nicely :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have the isServerless option too IIUC

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the doc link service can have two "flavours" of links so that's definitely something that already exists.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lcawl would you recommend linking to the autogenerated API refs today, or circling back in a while on that one?

}
}
export const docLinks = new SearchIndicesDocLinks();
1 change: 1 addition & 0 deletions x-pack/plugins/search_indices/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
],
"requiredBundles": [
"kibanaReact",
"esUiShared"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* 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, { Fragment, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useDeleteIndex } from '../../hooks/api/use_delete_index';
interface DeleteIndexModelProps {
onCancel: () => void;
indexName: string;
navigateToIndexListPage: () => void;
}
export const DeleteIndexModal: React.FC<DeleteIndexModelProps> = ({
onCancel,
indexName,
navigateToIndexListPage,
}) => {
const { mutate, isLoading, isSuccess } = useDeleteIndex(indexName);
useEffect(() => {
if (isSuccess) {
navigateToIndexListPage();
}
}, [navigateToIndexListPage, isSuccess]);
return (
<EuiConfirmModal
data-test-subj="deleteIndexActionModal"
title={i18n.translate(
'xpack.searchIndices.indexActionsMenu.deleteIndex.confirmModal.modalTitle',
{
defaultMessage: 'Delete {index}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the modal title should be more general and simply read 'Delete index'—especially since we are also including the index name in the body of the modal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 5dde7ca

values: { index: indexName },
}
)}
onCancel={onCancel}
onConfirm={() => mutate()}
isLoading={isLoading}
buttonColor="danger"
confirmButtonDisabled={false}
cancelButtonText={i18n.translate(
'xpack.searchIndices.indexActionsMenu.deleteIndex.confirmModal.cancelButtonText',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.searchIndices.indexActionsMenu.deleteIndex.confirmModal.confirmButtonText',
{
defaultMessage: 'Delete index',
}
)}
>
<Fragment>
<p>
<FormattedMessage
id="xpack.searchIndices.indexActionsMenu.deleteIndex.deleteDescription"
defaultMessage="You are about to delete this index:"
/>
</p>
<ul>
<li key={indexName}>{indexName}</li>
TattdCodeMonkey marked this conversation as resolved.
Show resolved Hide resolved
</ul>

<p>
<FormattedMessage
id="xpack.searchIndices.indexActionsMenu.deleteIndex.deleteWarningDescription"
defaultMessage="You can't recover a deleted index. Make sure you have appropriate backups."
/>
</p>
</Fragment>
</EuiConfirmModal>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,151 @@
* 2.0.
*/

import { EuiPageSection, EuiSpacer, EuiButton, EuiPageTemplate } from '@elastic/eui';
import React, { useCallback, useMemo } from 'react';
import {
EuiPageSection,
EuiSpacer,
EuiButton,
EuiPageTemplate,
EuiFlexItem,
EuiFlexGroup,
EuiPopover,
EuiButtonIcon,
EuiContextMenuItem,
EuiContextMenuPanel,
EuiText,
EuiIcon,
EuiButtonEmpty,
} from '@elastic/eui';
import React, { useCallback, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { SectionLoading } from '@kbn/es-ui-shared-plugin/public';
import { useIndex } from '../../hooks/api/use_index';
import { useKibana } from '../../hooks/use_kibana';
import { DeleteIndexModal } from './delete_index_modal';

export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const { console: consolePlugin, application } = useKibana().services;

const { data: index } = useIndex(indexName);
const { console: consolePlugin, docLinks, application } = useKibana().services;
const { data: index, refetch, isLoading, isSuccess } = useIndex(indexName);
const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
[consolePlugin]
);
const navigateToIndexListPage = useCallback(() => {
application.navigateToApp('management', { deepLinkId: 'index_management' });
}, [application]);

const [showMoreOptions, setShowMoreOptions] = useState<boolean>(false);
const [isShowingDeleteModal, setShowDeleteIndexModal] = useState<boolean>(false);
const moreOptionsPopover = (
<EuiPopover
isOpen={showMoreOptions}
closePopover={() => setShowMoreOptions(!showMoreOptions)}
button={
<EuiButtonIcon
iconType="boxesVertical"
onClick={() => setShowMoreOptions(!showMoreOptions)}
size="m"
data-test-subj="moreOptionsActionButton"
aria-label={i18n.translate('xpack.searchIndices.moreOptions.ariaLabel', {
defaultMessage: 'More options',
})}
/>
}
>
<EuiContextMenuPanel
data-test-subj="moreOptionsContextMenu"
items={[
<EuiContextMenuItem
key="trash"
icon={<EuiIcon type="trash" color="danger" />}
onClick={() => {
setShowDeleteIndexModal(!isShowingDeleteModal);
}}
size="s"
color="danger"
data-test-subj="moreOptionsDeleteIndex"
>
<EuiText size="s" color="danger">
{i18n.translate('xpack.searchIndices.moreOptions.deleteIndexLabel', {
defaultMessage: 'Delete Index',
})}
</EuiText>
</EuiContextMenuItem>,
]}
/>
</EuiPopover>
);

if (isLoading && !index) {
TattdCodeMonkey marked this conversation as resolved.
Show resolved Hide resolved
return (
<SectionLoading>
{i18n.translate('xpack.searchIndices.loadingDescription', {
defaultMessage: 'Loading index details…',
})}
</SectionLoading>
);
}
const pageloadingError = (
TattdCodeMonkey marked this conversation as resolved.
Show resolved Hide resolved
<EuiPageTemplate.EmptyPrompt
data-test-subj="pageLoadError"
color="danger"
iconType="warning"
title={
<h2>
<FormattedMessage
id="xpack.searchIndices.pageLoaError.errorTitle"
defaultMessage="Unable to load index details"
/>
</h2>
}
body={
<EuiText color="subdued">
<FormattedMessage
id="xpack.searchIndices.pageLoadError.description"
defaultMessage="We encountered an error loading data for index {indexName}. Make sure that the index name in the URL is correct and try again."
values={{
indexName,
}}
/>
</EuiText>
}
actions={
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
color="danger"
iconType="arrowLeft"
onClick={() => navigateToIndexListPage()}
data-test-subj="loadingErrorBackToIndicesButton"
>
<FormattedMessage
id="xpack.searchIndices.pageLoadError.backToIndicesButtonLabel"
defaultMessage="Back to indices"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
iconSide="right"
onClick={() => refetch}
iconType="refresh"
color="danger"
data-test-subj="reloadButton"
>
<FormattedMessage
id="xpack.searchIndices.pageLoadError.reloadButtonLabel"
defaultMessage="Reload"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
}
/>
);

return (
<EuiPageTemplate
offset={0}
Expand All @@ -32,27 +158,57 @@ export const SearchIndexDetailsPage = () => {
grow={false}
bottomBorder={false}
>
<EuiPageSection>
<EuiButton
data-test-subj="searchIndexDetailsBackToIndicesButton"
color="text"
iconType="arrowLeft"
onClick={navigateToIndexListPage}
>
<FormattedMessage
id="xpack.searchIndices.backToIndicesButtonLabel"
defaultMessage="Back to indices"
{!isSuccess || !index ? (
pageloadingError
) : (
<>
<EuiPageSection>
<EuiButton
data-test-subj="backToIndicesButton"
color="text"
iconType="arrowLeft"
onClick={() => navigateToIndexListPage()}
>
<FormattedMessage
id="xpack.searchIndices.backToIndicesButtonLabel"
defaultMessage="Back to indices"
/>
</EuiButton>
</EuiPageSection>
<EuiPageTemplate.Header
data-test-subj="searchIndexDetailsHeader"
pageTitle={index?.name}
rightSideItems={[
<EuiFlexGroup>
<EuiFlexItem>
<EuiButtonEmpty
href={docLinks.links.apiReference}
target="_blank"
iconType="documentation"
data-test-subj="ApiReferenceDoc"
>
{i18n.translate('xpack.searchIndices.indexActionsMenu.apiReference.docLink', {
defaultMessage: 'API Reference',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem>{moreOptionsPopover}</EuiFlexItem>
</EuiFlexGroup>,
]}
/>
</EuiButton>
</EuiPageSection>
<EuiPageTemplate.Header
data-test-subj="searchIndexDetailsHeader"
pageTitle={index?.name}
rightSideItems={[]}
/>
<EuiSpacer size="l" />
<EuiSpacer size="l" />

{isShowingDeleteModal && (
<DeleteIndexModal
onCancel={() => setShowDeleteIndexModal(!isShowingDeleteModal)}
indexName={indexName}
navigateToIndexListPage={navigateToIndexListPage}
/>
)}

<div data-test-subj="searchIndexDetailsContent" />
<div data-test-subj="searchIndexDetailsContent" />
</>
)}
{embeddableConsole}
</EuiPageTemplate>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { AcknowledgedResponseBase } from '@elastic/elasticsearch/lib/api/types';
import { useMutation } from '@tanstack/react-query';
import { useKibana } from '../use_kibana';

export const useDeleteIndex = (indexName: string) => {
const { http } = useKibana().services;
const indices = [indexName];
const body = JSON.stringify({
indices,
});
const result = useMutation({
mutationFn: async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also invalidate the index from querycache, onSuccess. see this doc https://tanstack.com/query/v4/docs/framework/react/guides/invalidations-from-mutations

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 13ac078

const response = await http.post<AcknowledgedResponseBase>(
`/api/index_management/indices/delete`,
{
body,
}
);
return response.acknowledged;
},
});
return { ...result };
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const useIndex = (indexName: string) => {
refetchInterval: POLLING_INTERVAL,
refetchIntervalInBackground: true,
refetchOnWindowFocus: 'always',
retry: true,
retry: false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When retry is true, the api try to refetch even if there is a fetch error. So there is no way to confirm if fetchIndex had a failure since isLoading & isSuccess will be false.
Setting retry to false, would stop refetch when last fetch failed and will save the error which can help to show error message in front end.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would suggest retry to be capped to a number like 3 rather than true so that errors are bubbled to the UI when it consistently throws an error

https://tanstack.com/query/latest/docs/framework/react/reference/useQuery#usequery

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated in 13ac078

queryFn: () =>
http.fetch<Index>(`/internal/index_management/indices/${encodeURIComponent(indexName)}`),
});
Expand Down
Loading