Skip to content

Commit

Permalink
[8.x] [Onboarding] Connection details + Quick Stats (#192636) (#193155)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.x`:
- [[Onboarding] Connection details + Quick Stats
(#192636)](#192636)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Joe
McElroy","email":"joseph.mcelroy@elastic.co"},"sourceCommit":{"committedDate":"2024-09-16T16:03:01Z","message":"[Onboarding]
Connection details + Quick Stats (#192636)\n\n## Summary\r\n\r\nAdding
in the connection details and quickstats for the
search_details\r\npage.\r\n\r\n\r\n![Screenshot 2024-09-11 at 20
36\r\n31](https://github.com/user-attachments/assets/5f030c06-4a98-4d9d-a465-c6719998ca56)\r\n![Screenshot
2024-09-11 at 20
36\r\n27](https://github.com/user-attachments/assets/d96be2f1-bcaa-42e5-9d32-1612e090b916)\r\n![Screenshot
2024-09-11 at 20
36\r\n09](https://github.com/user-attachments/assets/1f7995ae-5a0d-4810-acfb-3fafe33be451)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Liam Thompson
<32779855+leemthompo@users.noreply.github.com>","sha":"4d488818dc5503c40617d01512cd89c05e1e0345","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:EnterpriseSearch","v8.16.0"],"number":192636,"url":"https://github.com/elastic/kibana/pull/192636","mergeCommit":{"message":"[Onboarding]
Connection details + Quick Stats (#192636)\n\n## Summary\r\n\r\nAdding
in the connection details and quickstats for the
search_details\r\npage.\r\n\r\n\r\n![Screenshot 2024-09-11 at 20
36\r\n31](https://github.com/user-attachments/assets/5f030c06-4a98-4d9d-a465-c6719998ca56)\r\n![Screenshot
2024-09-11 at 20
36\r\n27](https://github.com/user-attachments/assets/d96be2f1-bcaa-42e5-9d32-1612e090b916)\r\n![Screenshot
2024-09-11 at 20
36\r\n09](https://github.com/user-attachments/assets/1f7995ae-5a0d-4810-acfb-3fafe33be451)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Liam Thompson
<32779855+leemthompo@users.noreply.github.com>","sha":"4d488818dc5503c40617d01512cd89c05e1e0345"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192636","number":192636,"mergeCommit":{"message":"[Onboarding]
Connection details + Quick Stats (#192636)\n\n## Summary\r\n\r\nAdding
in the connection details and quickstats for the
search_details\r\npage.\r\n\r\n\r\n![Screenshot 2024-09-11 at 20
36\r\n31](https://github.com/user-attachments/assets/5f030c06-4a98-4d9d-a465-c6719998ca56)\r\n![Screenshot
2024-09-11 at 20
36\r\n27](https://github.com/user-attachments/assets/d96be2f1-bcaa-42e5-9d32-1612e090b916)\r\n![Screenshot
2024-09-11 at 20
36\r\n09](https://github.com/user-attachments/assets/1f7995ae-5a0d-4810-acfb-3fafe33be451)\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by:
Liam Thompson
<32779855+leemthompo@users.noreply.github.com>","sha":"4d488818dc5503c40617d01512cd89c05e1e0345"}},{"branch":"8.x","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
joemcelroy and elasticmachine authored Sep 18, 2024
1 parent fac293c commit 137ed13
Show file tree
Hide file tree
Showing 15 changed files with 603 additions and 9 deletions.
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 @@ -219,6 +219,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
searchApplicationsSearch: `${ELASTICSEARCH_DOCS}search-application-client.html`,
searchLabs: `${SEARCH_LABS_URL}`,
searchLabsRepo: `${SEARCH_LABS_REPO}`,
semanticSearch: `${ELASTICSEARCH_DOCS}semantic-search.html`,
searchTemplates: `${ELASTICSEARCH_DOCS}search-template.html`,
semanticTextField: `${ELASTICSEARCH_DOCS}semantic-text.html`,
start: `${ENTERPRISE_SEARCH_DOCS}start.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 @@ -183,6 +183,7 @@ export interface DocLinks {
readonly searchApplicationsSearch: string;
readonly searchLabs: string;
readonly searchLabsRepo: string;
readonly semanticSearch: string;
readonly searchTemplates: string;
readonly semanticTextField: string;
readonly start: string;
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/search_indices/common/doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { DocLinks } from '@kbn/doc-links';

class SearchIndicesDocLinks {
public apiReference: string = '';
public setupSemanticSearch: string = '';

constructor() {}

setDocLinks(newDocLinks: DocLinks) {
this.apiReference = newDocLinks.apiReference;
this.setupSemanticSearch = newDocLinks.enterpriseSearch.semanticSearch;
}
}
export const docLinks = new SearchIndicesDocLinks();
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 {
EuiButtonIcon,
EuiCopy,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { FormattedMessage } from '@kbn/i18n-react';
import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url';

export const ConnectionDetails: React.FC = () => {
const { euiTheme } = useEuiTheme();
const elasticsearchUrl = useElasticsearchUrl();

return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiTitle size="xxxs">
<h1>
<FormattedMessage
id="xpack.searchIndices.connectionDetails.endpointTitle"
defaultMessage="Elasticsearch URL"
/>
</h1>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem css={{ whiteSpace: 'nowrap', overflow: 'hidden' }}>
<p
data-test-subj="connectionDetailsEndpoint"
css={{
color: euiTheme.colors.successText,
padding: `${euiTheme.size.s} ${euiTheme.size.m}`,
backgroundColor: euiTheme.colors.lightestShade,
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{elasticsearchUrl}
</p>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy
textToCopy={elasticsearchUrl}
afterMessage={i18n.translate('xpack.searchIndices.connectionDetails.copyMessage', {
defaultMessage: 'Copied',
})}
>
{(copy) => (
<EuiButtonIcon
onClick={copy}
iconType="copy"
aria-label={i18n.translate('xpack.searchIndices.connectionDetails.copyMessage', {
defaultMessage: 'Copy Elasticsearch URL to clipboard',
})}
/>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@ 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 { ConnectionDetails } from '../connection_details/connection_details';
import { QuickStats } from '../quick_stats/quick_stats';
import { useIndexMapping } from '../../hooks/api/use_index_mappings';
import { DeleteIndexModal } from './delete_index_modal';
import { IndexloadingError } from './details_page_loading_error';

export const SearchIndexDetailsPage = () => {
const indexName = decodeURIComponent(useParams<{ indexName: string }>().indexName);
const { console: consolePlugin, docLinks, application } = useKibana().services;
const { data: index, refetch, isSuccess, isInitialLoading } = useIndex(indexName);

const { data: index, refetch, isError: isIndexError, isInitialLoading } = useIndex(indexName);
const {
data: mappings,
isError: isMappingsError,
isInitialLoading: isMappingsInitialLoading,
} = useIndexMapping(indexName);

const embeddableConsole = useMemo(
() => (consolePlugin?.EmbeddableConsole ? <consolePlugin.EmbeddableConsole /> : null),
Expand Down Expand Up @@ -87,7 +96,7 @@ export const SearchIndexDetailsPage = () => {
/>
</EuiPopover>
);
if (isInitialLoading) {
if (isInitialLoading || isMappingsInitialLoading) {
return (
<SectionLoading>
{i18n.translate('xpack.searchIndices.loadingDescription', {
Expand All @@ -103,9 +112,10 @@ export const SearchIndexDetailsPage = () => {
restrictWidth={false}
data-test-subj="searchIndicesDetailsPage"
grow={false}
bottomBorder={false}
panelled
bottomBorder
>
{!isSuccess || !index ? (
{isIndexError || isMappingsError || !index || !mappings ? (
<IndexloadingError
indexName={indexName}
navigateToIndexListPage={navigateToIndexListPage}
Expand Down Expand Up @@ -156,8 +166,20 @@ export const SearchIndexDetailsPage = () => {
navigateToIndexListPage={navigateToIndexListPage}
/>
)}
<EuiPageTemplate.Section grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
<ConnectionDetails />
</EuiFlexItem>
<EuiFlexItem>{/* TODO: API KEY */}</EuiFlexItem>
</EuiFlexGroup>

<EuiSpacer size="l" />

<div data-test-subj="searchIndexDetailsContent" />
<EuiFlexGroup>
<QuickStats index={index} mappings={mappings} />
</EuiFlexGroup>
</EuiPageTemplate.Section>
</>
)}
{embeddableConsole}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { Mappings } from '../../types';
import { countVectorBasedTypesFromMappings } from './mappings_convertor';

describe('mappings convertor', () => {
it('should count vector based types from mappings', () => {
const mappings = {
mappings: {
properties: {
field1: {
type: 'dense_vector',
},
field2: {
type: 'dense_vector',
},
field3: {
type: 'sparse_vector',
},
field4: {
type: 'dense_vector',
},
field5: {
type: 'semantic_text',
},
},
},
};
const result = countVectorBasedTypesFromMappings(mappings as unknown as Mappings);
expect(result).toEqual({
dense_vector: 3,
sparse_vector: 1,
semantic_text: 1,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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 type {
MappingProperty,
MappingPropertyBase,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Mappings } from '../../types';

interface VectorFieldTypes {
semantic_text: number;
dense_vector: number;
sparse_vector: number;
}

export function countVectorBasedTypesFromMappings(mappings: Mappings): VectorFieldTypes {
const typeCounts: VectorFieldTypes = {
semantic_text: 0,
dense_vector: 0,
sparse_vector: 0,
};

const typeCountKeys = Object.keys(typeCounts);

function recursiveCount(fields: MappingProperty | Mappings | MappingPropertyBase['fields']) {
if (!fields) {
return;
}
if ('mappings' in fields) {
recursiveCount(fields.mappings);
}
if ('properties' in fields && fields.properties) {
Object.keys(fields.properties).forEach((key) => {
const value = (fields.properties as Record<string, MappingProperty>)?.[key];

if (value && value.type) {
if (typeCountKeys.includes(value.type)) {
const type = value.type as keyof VectorFieldTypes;
typeCounts[type] = typeCounts[type] + 1;
}

if ('fields' in value) {
recursiveCount(value.fields);
}

if ('properties' in value) {
recursiveCount(value.properties);
}
} else if (value.properties || value.fields) {
recursiveCount(value);
}
});
}
}

recursiveCount(mappings);
return typeCounts;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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 {
EuiAccordion,
EuiDescriptionList,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiText,
useEuiTheme,
useGeneratedHtmlId,
} from '@elastic/eui';

interface BaseQuickStatProps {
icon: string;
iconColor: string;
title: string;
secondaryTitle: React.ReactNode;
open: boolean;
content?: React.ReactNode;
stats: Array<{
title: string;
description: NonNullable<React.ReactNode>;
}>;
setOpen: (open: boolean) => void;
first?: boolean;
}

export const QuickStat: React.FC<BaseQuickStatProps> = ({
icon,
title,
stats,
open,
setOpen,
first,
secondaryTitle,
iconColor,
content,
...rest
}) => {
const { euiTheme } = useEuiTheme();

const id = useGeneratedHtmlId({
prefix: 'formAccordion',
suffix: title,
});

return (
<EuiAccordion
forceState={open ? 'open' : 'closed'}
onToggle={() => setOpen(!open)}
paddingSize="none"
id={id}
buttonElement="div"
arrowDisplay="right"
{...rest}
css={{
borderLeft: euiTheme.border.thin,
...(first ? { borderLeftWidth: 0 } : {}),
'.euiAccordion__arrow': {
marginRight: euiTheme.size.s,
},
'.euiAccordion__triggerWrapper': {
background: euiTheme.colors.ghost,
},
'.euiAccordion__children': {
borderTop: euiTheme.border.thin,
padding: euiTheme.size.m,
},
}}
buttonContent={
<EuiPanel hasShadow={false} hasBorder={false} paddingSize="s">
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type={icon} color={iconColor} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText>
<h4>{title}</h4>
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText color="subdued">{secondaryTitle}</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
}
>
{content ? (
content
) : (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiDescriptionList
type="column"
listItems={stats}
columnWidths={[3, 1]}
compressed
descriptionProps={{
color: 'subdued',
}}
/>
</EuiFlexItem>
</EuiFlexGroup>
)}
</EuiAccordion>
);
};
Loading

0 comments on commit 137ed13

Please sign in to comment.