Skip to content

Commit

Permalink
[Serverless Search] Index Management - Index Details Overview (#173581)
Browse files Browse the repository at this point in the history
## Summary

This PR implements a new Overview tab for an Index details in Index
Management for Serverless.

It has data panels for information about the index and optional empty
states if the index has no documents. (see Screenshots)


### Screenshots

Index with data

![image](https://github.com/elastic/kibana/assets/1972968/848d7f50-8332-4119-b23b-7e4569a2eaba)

Index without data

![image](https://github.com/elastic/kibana/assets/1972968/a126f94f-1112-4738-abd8-5d40d96d35dc)

![image](https://github.com/elastic/kibana/assets/1972968/e5f11f2f-f5a4-421e-b212-ce65307756d4)

![image](https://github.com/elastic/kibana/assets/1972968/bd7d7970-8e4c-4245-baec-8ab9d3625dbd)

Connector Index without data

![image](https://github.com/elastic/kibana/assets/1972968/dc0d97ed-110b-4566-bddd-f2c145d2eadf)

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
  • Loading branch information
TattdCodeMonkey authored Dec 20, 2023
1 parent 6bec710 commit 5798255
Show file tree
Hide file tree
Showing 26 changed files with 1,278 additions and 18 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/serverless_search/common/doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ESDocLinks {
public roleDescriptors: string = '';
public securityApis: string = '';
public ingestionPipelines: string = '';
public dataStreams: string = '';
// Client links
public elasticsearchClients: string = '';
// go
Expand Down Expand Up @@ -61,6 +62,7 @@ class ESDocLinks {
this.roleDescriptors = newDocLinks.serverlessSecurity.apiKeyPrivileges;
this.securityApis = newDocLinks.apis.securityApis;
this.ingestionPipelines = newDocLinks.ingest.pipelines;
this.dataStreams = newDocLinks.elasticsearch.dataStreams;

// Client links
this.elasticsearchClients = newDocLinks.serverlessClients.clientLib;
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/serverless_search/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
* 2.0.
*/

import { IndicesIndexState, IndicesStatsIndicesStats } from '@elastic/elasticsearch/lib/api/types';
import { Connector } from '@kbn/search-connectors/types/connectors';

export interface CreateAPIKeyArgs {
expiration?: string;
metadata?: Record<string, any>;
Expand All @@ -20,3 +23,11 @@ export interface IndexData {
export interface FetchIndicesResult {
indices: IndexData[];
}

export interface FetchIndexResult {
index: IndicesIndexState & {
connector?: Connector;
count: number;
stats?: IndicesStatsIndicesStats;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { EuiBadgeGroup, EuiBadge } from '@elastic/eui';

export interface BadgeListProps {
badges: React.ReactNode[];
maxBadgesToDisplay?: number;
}

export const BadgeList = ({ badges, maxBadgesToDisplay }: BadgeListProps) => {
const maxBadges = maxBadgesToDisplay ?? 3;
if (badges.length === 0) {
return <></>;
}

const badgesToDisplay = badges.slice(0, maxBadges);
return (
<EuiBadgeGroup gutterSize="s" css={{ width: '100%' }}>
{badgesToDisplay.map((badge) => badge)}
{badges.length > maxBadges && (
<EuiBadge color="hollow">+{badges.length - maxBadges}</EuiBadge>
)}
</EuiBadgeGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import {
import { useConnectors } from '../../hooks/api/use_connectors';
import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { useKibanaServices } from '../../hooks/use_kibana';
import { EDIT_CONNECTOR_PATH } from '../connectors_router';
import { EDIT_CONNECTOR_PATH } from '../../constants';
import { DeleteConnectorModal } from './delete_connector_modal';

export const ConnectorsTable: React.FC = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ import {
import { i18n } from '@kbn/i18n';
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { PLUGIN_ID } from '../../../../common';

import { useConnectorTypes } from '../../hooks/api/use_connector_types';
import { useKibanaServices } from '../../hooks/use_kibana';
import { useCreateConnector } from '../../hooks/api/use_create_connector';
import { useAssetBasePath } from '../../hooks/use_asset_base_path';

export const EmptyConnectorsPrompt: React.FC = () => {
const { http } = useKibanaServices();
const { data: connectorTypes } = useConnectorTypes();
const assetBasePath = http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets`);
const { createConnector, isLoading } = useCreateConnector();

const assetBasePath = useAssetBasePath();
const connectorsPath = assetBasePath + '/connectors.svg';
return (
<EuiFlexGroup alignItems="center" direction="column">
Expand Down Expand Up @@ -167,6 +169,8 @@ export const EmptyConnectorsPrompt: React.FC = () => {
data-test-subj="serverlessSearchEmptyConnectorsPromptCreateConnectorButton"
fill
iconType="plusInCircleFilled"
onClick={() => createConnector()}
isLoading={isLoading}
>
{i18n.translate('xpack.serverlessSearch.connectorsEmpty.createConnector', {
defaultMessage: 'Create connector',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import React from 'react';
import { EditConnector } from './connectors/edit_connector';
import { ConnectorsOverview } from './connectors_overview';

export const BASE_CONNECTORS_PATH = 'connectors';
export const EDIT_CONNECTOR_PATH = `${BASE_CONNECTORS_PATH}/:id`;

export const ConnectorsRouter: React.FC = () => {
return (
<Routes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ export const IndexDocuments: React.FC<IndexDocumentsProps> = ({ indexName }) =>
/>
);
};

// Default Export is needed to lazy load this react component
// eslint-disable-next-line import/no-default-export
export default IndexDocuments;
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@
*/

import { IndexDetailsTab } from '@kbn/index-management-plugin/common/constants';
import React from 'react';
import React, { Suspense, lazy } from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { CoreStart } from '@kbn/core-lifecycle-browser';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';

import { ServerlessSearchPluginStartDependencies } from '../../../types';
import { IndexDocuments } from './documents';

export const createIndexOverviewContent = (
const IndexDocuments = lazy(() => import('./documents'));

export const createIndexDocumentsContent = (
core: CoreStart,
services: ServerlessSearchPluginStartDependencies
): IndexDetailsTab => {
Expand All @@ -34,7 +37,9 @@ export const createIndexOverviewContent = (
<KibanaContextProvider services={{ ...core, ...services }}>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<IndexDocuments indexName={index.name} />
<Suspense fallback={<EuiLoadingSpinner />}>
<IndexDocuments indexName={index.name} />
</Suspense>
</QueryClientProvider>
</KibanaContextProvider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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, { useMemo, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiEmptyPrompt,
EuiFlexItem,
EuiFlexGroup,
EuiIcon,
EuiText,
EuiButtonEmpty,
EuiLink,
EuiPanel,
EuiTitle,
EuiSpacer,
EuiSteps,
} from '@elastic/eui';
import { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
import {
CodeBox,
getConsoleRequest,
getLanguageDefinitionCodeSnippet,
LanguageDefinition,
LanguageDefinitionSnippetArguments,
} from '@kbn/search-api-panels';

import { BACK_LABEL } from '../../../../common/i18n_string';

import { useAssetBasePath } from '../../hooks/use_asset_base_path';
import { useKibanaServices } from '../../hooks/use_kibana';
import { javaDefinition } from '../languages/java';
import { languageDefinitions } from '../languages/languages';
import { LanguageGrid } from '../languages/language_grid';
import {
API_KEY_PLACEHOLDER,
CLOUD_ID_PLACEHOLDER,
ELASTICSEARCH_URL_PLACEHOLDER,
} from '../../constants';

import { ApiKeyPanel } from '../api_key/api_key';

export interface APIIndexEmptyPromptProps {
indexName: string;
onBackClick?: () => void;
}

export const APIIndexEmptyPrompt = ({ indexName, onBackClick }: APIIndexEmptyPromptProps) => {
const { application, cloud, share } = useKibanaServices();
const assetBasePath = useAssetBasePath();
const [selectedLanguage, setSelectedLanguage] =
React.useState<LanguageDefinition>(javaDefinition);
const [clientApiKey, setClientApiKey] = useState<string>(API_KEY_PLACEHOLDER);
const { elasticsearchURL, cloudId } = useMemo(() => {
return {
elasticsearchURL: cloud?.elasticsearchUrl ?? ELASTICSEARCH_URL_PLACEHOLDER,
cloudId: cloud?.cloudId ?? CLOUD_ID_PLACEHOLDER,
};
}, [cloud]);
const codeSnippetArguments: LanguageDefinitionSnippetArguments = {
url: elasticsearchURL,
apiKey: clientApiKey,
cloudId,
indexName,
};

const apiIngestSteps: EuiContainedStepProps[] = [
{
title: i18n.translate(
'xpack.serverlessSearch.indexManagement.indexDetails.overview.emptyPrompt.api.ingest.title',
{ defaultMessage: 'Ingest data via API using a programming language client' }
),
children: (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<LanguageGrid
assetBasePath={assetBasePath}
setSelectedLanguage={setSelectedLanguage}
languages={languageDefinitions}
selectedLanguage={selectedLanguage.id}
/>
</EuiFlexItem>
<EuiFlexItem>
<CodeBox
languages={languageDefinitions}
codeSnippet={getLanguageDefinitionCodeSnippet(
selectedLanguage,
'ingestDataIndex',
codeSnippetArguments
)}
consoleRequest={getConsoleRequest('ingestDataIndex')}
selectedLanguage={selectedLanguage}
setSelectedLanguage={setSelectedLanguage}
assetBasePath={assetBasePath}
application={application}
sharePlugin={share}
/>
</EuiFlexItem>
</EuiFlexGroup>
),
},
{
title: i18n.translate(
'xpack.serverlessSearch.indexManagement.indexDetails.overview.emptyPrompt.api.apiKey.title',
{ defaultMessage: 'Prepare an API key' }
),
children: <ApiKeyPanel setClientApiKey={setClientApiKey} />,
},
];

return (
<EuiPanel>
<EuiButtonEmpty
data-test-subj="serverlessSearchAPIIndexEmptyPromptBackButton"
onClick={onBackClick}
iconSide="left"
iconType="arrowLeft"
>
{BACK_LABEL}
</EuiButtonEmpty>
<EuiEmptyPrompt
icon={<EuiIcon type="addDataApp" size="xl" />}
title={
<EuiTitle size="xs">
<h5>
<FormattedMessage
id="xpack.serverlessSearch.indexManagement.indexDetails.overview.emptyPrompt.api.title"
defaultMessage="Ingest content"
/>
</h5>
</EuiTitle>
}
body={
<EuiText>
<p>
<FormattedMessage
id="xpack.serverlessSearch.indexManagement.indexDetails.overview.emptyPrompt.api.body"
defaultMessage="Customize these variables to match your content. For a full setup guide, visit our {getStartedLink} guide."
values={{
getStartedLink: (
<EuiLink
data-test-subj="serverlessSearchAPIIndexEmptyPromptGetStartedLink"
onClick={() => application.navigateToApp('elasticsearch')}
>
{i18n.translate(
'xpack.serverlessSearch.indexManagement.indexDetails.overview.emptyPrompt.api.body.getStartedLink',
{ defaultMessage: 'Get started' }
)}
</EuiLink>
),
}}
/>
</p>
</EuiText>
}
/>
<EuiSpacer />
<EuiSteps steps={apiIngestSteps} titleSize="xs" />
</EuiPanel>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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 { EuiButtonEmpty, EuiPanel } from '@elastic/eui';

import { BACK_LABEL } from '../../../../common/i18n_string';
import { EmptyConnectorsPrompt } from '../connectors/empty_connectors_prompt';

interface ConnectorIndexEmptyPromptProps {
indexName: string;
onBackClick?: () => void;
}

export const ConnectorIndexEmptyPrompt = ({ onBackClick }: ConnectorIndexEmptyPromptProps) => {
return (
<EuiPanel>
<EuiButtonEmpty
data-test-subj="serverlessSearchConnectorIndexEmptyPromptBackButton"
onClick={onBackClick}
iconSide="left"
iconType="arrowLeft"
>
{BACK_LABEL}
</EuiButtonEmpty>
<EmptyConnectorsPrompt />
</EuiPanel>
);
};
Loading

0 comments on commit 5798255

Please sign in to comment.