Skip to content

Commit

Permalink
[Enterprise Search] Search Application schema conflict warnings (#157282
Browse files Browse the repository at this point in the history
)

## Summary

Adds warnings through the search applications UI when a search
application has schema conflicts

<details>
<summary>🖼️ screenshots</summary>

![Screen Shot 2023-05-10 at 09 37
55](https://github.com/elastic/kibana/assets/1699281/452c7950-248a-4e3a-bfe3-631718d4d981)
![Screen Shot 2023-05-10 at 09 38
05](https://github.com/elastic/kibana/assets/1699281/be3b51e6-3830-45bc-877e-635ca3c8c938)
![Screen Shot 2023-05-10 at 09 38
09](https://github.com/elastic/kibana/assets/1699281/bfcf56d3-c2d5-49b0-b7e0-3081c2ad773e)
![Screen Shot 2023-05-10 at 09 38
12](https://github.com/elastic/kibana/assets/1699281/8515ba20-d888-4678-a1d0-b0e897c70602)
![Screen Shot 2023-05-10 at 09 38
18](https://github.com/elastic/kibana/assets/1699281/1695be43-5780-46bb-8a4e-114e09bc308b)

</details>

### Checklist

Delete any items that are not applicable to this PR.

- [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)
- [x] [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
- [x] 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))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Julian Rosado <julian.rosado@elastic.co>
  • Loading branch information
Sloane Perrault and julianrosado authored May 11, 2023
1 parent f2c38dd commit a520001
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ const getTabBreadCrumb = (tabId: string) => {
};

export const EngineConnect: React.FC = () => {
const { engineName, isLoadingEngine } = useValues(EngineViewLogic);
const { engineName, isLoadingEngine, hasSchemaConflicts } = useValues(EngineViewLogic);
const { connectTabId = SearchApplicationConnectTabs.API } = useParams<{
connectTabId?: string;
}>();
const onTabClick = (tab: SearchApplicationConnectTabs) => () => {
KibanaLogic.values.navigateToUrl(
generateEncodedPath(SEARCH_APPLICATION_CONTENT_PATH, {
engineName,
connectTabId: tab,
engineName,
})
);
};
Expand All @@ -76,6 +76,7 @@ export const EngineConnect: React.FC = () => {
rightSideItems: [],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
<EngineError notFound />
</EnterpriseSearchEnginesPageTemplate>
Expand All @@ -101,6 +102,7 @@ export const EngineConnect: React.FC = () => {
],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
{connectTabId === SearchApplicationConnectTabs.API && <SearchApplicationAPI />}
</EnterpriseSearchEnginesPageTemplate>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* 2.0.
*/

import React, { useEffect, useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo } from 'react';

import { useActions, useValues } from 'kea';
import { useValues } from 'kea';

import {
EuiBadge,
Expand Down Expand Up @@ -45,8 +45,6 @@ import { generateEncodedPath } from '../../../shared/encode_path_params';
import { KibanaLogic } from '../../../shared/kibana';
import { EuiLinkTo } from '../../../shared/react_router_helpers';

import { EngineIndicesLogic } from './engine_indices_logic';

import { EngineViewLogic } from './engine_view_logic';

const SchemaFieldDetails: React.FC<{ schemaField: SchemaField }> = ({ schemaField }) => {
Expand Down Expand Up @@ -147,17 +145,16 @@ const SchemaFieldDetails: React.FC<{ schemaField: SchemaField }> = ({ schemaFiel
css={{ '& .euiTable': { backgroundColor: 'transparent' } }}
columns={columns}
items={schemaField.indices}
responsive={false}
/>
</EuiFlexGroup>
</EuiPanel>
);
};

export const EngineSchema: React.FC = () => {
const { engineName } = useValues(EngineIndicesLogic);
const [onlyShowConflicts, setOnlyShowConflicts] = useState<boolean>(false);
const { isLoadingEngineSchema, schemaFields } = useValues(EngineViewLogic);
const { fetchEngineSchema } = useActions(EngineViewLogic);
const { isLoadingEngineSchema, schemaFields, hasSchemaConflicts } = useValues(EngineViewLogic);

const [isFilterByPopoverOpen, setIsFilterByPopoverOpen] = useState<boolean>(false);
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<Record<string, JSX.Element>>(
Expand Down Expand Up @@ -201,10 +198,6 @@ export const EngineSchema: React.FC = () => {
? schemaFieldsMaybeWithConflicts.length - filteredSchemaFields.length
: 0;

useEffect(() => {
fetchEngineSchema({ engineName });
}, [engineName]);

const toggleDetails = (schemaField: SchemaField) => {
const newItemIdToExpandedRowMap = { ...itemIdToExpandedRowMap };
if (itemIdToExpandedRowMap[schemaField.name]) {
Expand All @@ -224,7 +217,7 @@ export const EngineSchema: React.FC = () => {
if (type !== 'conflict') return null;
return <EuiIcon type="error" color="danger" />;
},
width: '2%',
width: '24px',
},
{
name: i18n.translate('xpack.enterpriseSearch.content.engine.schema.field_name.columnTitle', {
Expand All @@ -238,7 +231,6 @@ export const EngineSchema: React.FC = () => {
</EuiText>
</EuiFlexGroup>
),
width: '43%',
},
{
name: i18n.translate('xpack.enterpriseSearch.content.engine.schema.field_type.columnTitle', {
Expand Down Expand Up @@ -267,7 +259,7 @@ export const EngineSchema: React.FC = () => {
</EuiFlexGroup>
);
},
width: '30%',
width: '180px',
},
{
name: i18n.translate(
Expand Down Expand Up @@ -296,15 +288,16 @@ export const EngineSchema: React.FC = () => {
</EuiBadge>
);
},
width: '15%',
width: '110px',
},
{
isExpander: true,
render: (schemaField: SchemaField) => {
const { name, type, indices } = schemaField;
if (type === 'conflict' || indices.some((i) => i.type === 'unmapped')) {
const icon = itemIdToExpandedRowMap[name] ? 'arrowUp' : 'arrowDown';
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
<EuiButtonEmpty
size="s"
color="primary"
Expand All @@ -324,7 +317,7 @@ export const EngineSchema: React.FC = () => {
}
return null;
},
width: '10%',
width: '115px',
},
];
const filterButton = (
Expand All @@ -346,7 +339,32 @@ export const EngineSchema: React.FC = () => {
return (
<>
<EuiFlexGroup direction="column" gutterSize="l">
<EuiFlexGroup>
{hasSchemaConflicts && (
<EuiCallOut
title={i18n.translate(
'xpack.enterpriseSearch.content.applications.schema.conflictsCallOut.title',
{ defaultMessage: 'Potential field mapping issues found' }
)}
iconType="error"
color="danger"
>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.content.applications.schema.conflictsCallOut.description"
defaultMessage="Schema field type conflicts can be resolved by navigating to the source index directly and updating the field type of the conflicting field(s) to match that of the other source indices."
/>
</p>
{!onlyShowConflicts && (
<EuiButton color="danger" fill onClick={toggleOnlyShowConflicts}>
<FormattedMessage
id="xpack.enterpriseSearch.content.applications.schema.conflictsCallOut.button"
defaultMessage="View conflicts"
/>
</EuiButton>
)}
</EuiCallOut>
)}
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween">
<EuiSwitch
label={i18n.translate(
'xpack.enterpriseSearch.content.engine.schema.onlyShowConflicts',
Expand Down Expand Up @@ -420,6 +438,7 @@ export const EngineSchema: React.FC = () => {
itemId="name"
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
isExpandable
responsive={false}
/>
{totalConflictsHiddenByTypeFilters > 0 && (
<EuiCallOut
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
EuiPanel,
EuiPopover,
EuiSpacer,
EuiText,
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
Expand Down Expand Up @@ -112,14 +113,16 @@ class InternalEngineTransporter implements Transporter {

interface ConfigurationPopOverProps {
engineName: string;
hasSchemaConflicts: boolean;
setCloseConfiguration: () => void;
showConfiguration: boolean;
}

const ConfigurationPopover: React.FC<ConfigurationPopOverProps> = ({
engineName,
showConfiguration,
hasSchemaConflicts,
setCloseConfiguration,
showConfiguration,
}) => {
const { navigateToUrl } = useValues(KibanaLogic);
const { engineData } = useValues(EngineViewLogic);
Expand Down Expand Up @@ -184,7 +187,7 @@ const ConfigurationPopover: React.FC<ConfigurationPopOverProps> = ({
</EuiContextMenuItem>
<EuiContextMenuItem
key="Schema"
icon="kqlField"
icon={hasSchemaConflicts ? <EuiIcon type="warning" color="danger" /> : 'kqlField'}
onClick={() =>
navigateToUrl(
generateEncodedPath(SEARCH_APPLICATION_CONTENT_PATH, {
Expand All @@ -194,12 +197,20 @@ const ConfigurationPopover: React.FC<ConfigurationPopOverProps> = ({
)
}
>
{i18n.translate(
'xpack.enterpriseSearch.content.engine.searchPreview.configuration.content.Schema',
{
defaultMessage: 'Schema',
}
)}
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<FormattedMessage
id="xpack.enterpriseSearch.content.engine.searchPreview.configuration.content.schema"
defaultMessage="Schema"
/>
{hasSchemaConflicts && (
<EuiText size="s" color="danger">
<FormattedMessage
id="xpack.enterpriseSearch.content.engine.searchPreview.configuration.content.schemaConflict"
defaultMessage="Conflict"
/>
</EuiText>
)}
</EuiFlexGroup>
</EuiContextMenuItem>

<EuiPanel color="transparent" paddingSize="s">
Expand Down Expand Up @@ -282,7 +293,7 @@ export const EngineSearchPreview: React.FC = () => {
// const [showAPICallFlyout, setShowAPICallFlyout] = useState<boolean>(false); Uncomment when view this API call is needed
const [showConfigurationPopover, setShowConfigurationPopover] = useState<boolean>(false);
// const [lastAPICall, setLastAPICall] = useState<null | APICallData>(null); Uncomment when view this API call is needed
const { engineName, isLoadingEngine } = useValues(EngineViewLogic);
const { engineName, isLoadingEngine, hasSchemaConflicts } = useValues(EngineViewLogic);
const { resultFields, sortableFields } = useValues(EngineSearchPreviewLogic);
const { engineData } = useValues(EngineIndicesLogic);

Expand Down Expand Up @@ -326,13 +337,15 @@ export const EngineSearchPreview: React.FC = () => {
<>
<ConfigurationPopover
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
showConfiguration={showConfigurationPopover}
setCloseConfiguration={() => setShowConfigurationPopover(!showConfigurationPopover)}
/>
</>,
],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
<DocumentProvider>
<SearchProvider config={config}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,13 @@ import { SearchApplicationContent } from './search_application_content';

export const EngineView: React.FC = () => {
const { fetchEngine, closeDeleteEngineModal } = useActions(EngineViewLogic);
const { engineName, fetchEngineApiError, fetchEngineApiStatus, isDeleteModalVisible } =
useValues(EngineViewLogic);
const {
engineName,
fetchEngineApiError,
fetchEngineApiStatus,
hasSchemaConflicts,
isDeleteModalVisible,
} = useValues(EngineViewLogic);
const { tabId = EngineViewTabs.PREVIEW } = useParams<{
tabId?: string;
}>();
Expand All @@ -61,6 +66,7 @@ export const EngineView: React.FC = () => {
}}
engineName={engineName}
emptyState={<EngineError error={fetchEngineApiError} />}
hasSchemaConflicts={hasSchemaConflicts}
/>
);
}
Expand Down Expand Up @@ -97,6 +103,7 @@ export const EngineView: React.FC = () => {
rightSideItems: [],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
<EngineError notFound />
</EnterpriseSearchEnginesPageTemplate>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import { EngineViewLogic, EngineViewValues } from './engine_view_logic';
const DEFAULT_VALUES: EngineViewValues = {
engineData: undefined,
engineName: 'my-test-engine',
engineSchemaData: undefined,
fetchEngineApiError: undefined,
fetchEngineApiStatus: Status.IDLE,
isDeleteModalVisible: false,
isLoadingEngine: true,
engineSchemaData: undefined,
fetchEngineSchemaApiError: undefined,
fetchEngineSchemaApiStatus: Status.IDLE,
hasSchemaConflicts: false,
isDeleteModalVisible: false,
isLoadingEngine: true,
isLoadingEngineSchema: true,
schemaFields: [],
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface EngineViewValues {
fetchEngineApiStatus: typeof FetchEngineApiLogic.values.status;
fetchEngineSchemaApiError?: typeof FetchEngineFieldCapabilitiesApiLogic.values.error;
fetchEngineSchemaApiStatus: typeof FetchEngineFieldCapabilitiesApiLogic.values.status;
hasSchemaConflicts: boolean;
isDeleteModalVisible: boolean;
isLoadingEngine: boolean;
isLoadingEngineSchema: boolean;
Expand Down Expand Up @@ -78,6 +79,9 @@ export const EngineViewLogic = kea<MakeLogicType<EngineViewValues, EngineViewAct
actions.closeDeleteEngineModal();
KibanaLogic.values.navigateToUrl(ENGINES_PATH);
},
fetchEngine: ({ engineName }) => {
actions.fetchEngineSchema({ engineName });
},
}),
path: ['enterprise_search', 'content', 'engine_view_logic'],
reducers: () => ({
Expand All @@ -90,6 +94,10 @@ export const EngineViewLogic = kea<MakeLogicType<EngineViewValues, EngineViewAct
],
}),
selectors: ({ selectors }) => ({
hasSchemaConflicts: [
() => [selectors.schemaFields],
(data: EngineViewValues['schemaFields']) => data.some((f) => f.type === 'conflict'),
],
isLoadingEngine: [
() => [selectors.fetchEngineApiStatus, selectors.engineData],
(status: EngineViewValues['fetchEngineApiStatus'], data: EngineViewValues['engineData']) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useParams } from 'react-router-dom';

import { useActions, useValues } from 'kea';

import { EuiButton, EuiIcon } from '@elastic/eui';
import { EuiButton, EuiIcon, EuiFlexGroup } from '@elastic/eui';
import { i18n } from '@kbn/i18n';

import { generateEncodedPath } from '../../../shared/encode_path_params';
Expand Down Expand Up @@ -65,7 +65,7 @@ const getTabBreadCrumb = (tabId: string) => {
const ContentTabs: string[] = Object.values(SearchApplicationContentTabs);

export const SearchApplicationContent = () => {
const { engineName, isLoadingEngine } = useValues(EngineViewLogic);
const { engineName, isLoadingEngine, hasSchemaConflicts } = useValues(EngineViewLogic);
const { addIndicesFlyoutOpen } = useValues(EngineIndicesLogic);
const { closeAddIndicesFlyout, openAddIndicesFlyout } = useActions(EngineIndicesLogic);
const { contentTabId = SearchApplicationContentTabs.INDICES } = useParams<{
Expand All @@ -85,6 +85,7 @@ export const SearchApplicationContent = () => {
rightSideItems: [],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
<EngineError notFound />
</EnterpriseSearchEnginesPageTemplate>
Expand Down Expand Up @@ -146,12 +147,18 @@ export const SearchApplicationContent = () => {
},
{
isSelected: contentTabId === SearchApplicationContentTabs.SCHEMA,
label: SCHEMA_TAB_TITLE,
label: (
<EuiFlexGroup gutterSize="s" alignItems="center">
{hasSchemaConflicts && <EuiIcon type="warning" color="danger" />}
{SCHEMA_TAB_TITLE}
</EuiFlexGroup>
),
onClick: onTabClick(SearchApplicationContentTabs.SCHEMA),
},
],
}}
engineName={engineName}
hasSchemaConflicts={hasSchemaConflicts}
>
{contentTabId === SearchApplicationContentTabs.INDICES && <EngineIndices />}
{contentTabId === SearchApplicationContentTabs.SCHEMA && <EngineSchema />}
Expand Down
Loading

0 comments on commit a520001

Please sign in to comment.