mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[ML] Transforms: Wizard displays warning callout for source preview when used with CCS against clusters below 7.10. (#96297)
The transforms UI source preview uses fields to search and retrieve document attributes. The feature was introduced in 7.10. For cross cluster search, as of now, when a search using fields is used using cross cluster search against a cluster earlier than 7.10, the API won't return an error or other information but just silently drop the fields attribute and return empty hits without field attributes. In Kibana, index patterns can be set up to use cross cluster search using the pattern <cluster-names>:<pattern>. If we identify such a pattern and the search hits don't include fields attributes, we display a warning callout from now on.
This commit is contained in:
parent
7f4ec48ce6
commit
f945f3a425
7 changed files with 98 additions and 10 deletions
|
@ -80,6 +80,7 @@ export const DataGrid: FC<Props> = memo(
|
|||
baseline,
|
||||
chartsVisible,
|
||||
chartsButtonVisible,
|
||||
ccsWarning,
|
||||
columnsWithCharts,
|
||||
dataTestSubj,
|
||||
errorMessage,
|
||||
|
@ -291,6 +292,24 @@ export const DataGrid: FC<Props> = memo(
|
|||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
)}
|
||||
{ccsWarning && (
|
||||
<div data-test-subj={`${dataTestSubj} ccsWarning`}>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.ml.dataGrid.CcsWarningCalloutTitle', {
|
||||
defaultMessage: 'Cross-cluster search returned no fields data.',
|
||||
})}
|
||||
color="warning"
|
||||
>
|
||||
<p>
|
||||
{i18n.translate('xpack.ml.dataGrid.CcsWarningCalloutBody', {
|
||||
defaultMessage:
|
||||
'There was an issue retrieving data for the index pattern. Source preview in combination with cross-cluster search is only supported for versions 7.10 and above. You may still configure and create the transform.',
|
||||
})}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
<EuiSpacer size="m" />
|
||||
</div>
|
||||
)}
|
||||
<div className="mlDataGrid">
|
||||
<EuiDataGrid
|
||||
aria-label={isWithHeader(props) ? props.title : ''}
|
||||
|
|
|
@ -59,6 +59,7 @@ export interface UseIndexDataReturnType
|
|||
UseDataGridReturnType,
|
||||
| 'chartsVisible'
|
||||
| 'chartsButtonVisible'
|
||||
| 'ccsWarning'
|
||||
| 'columnsWithCharts'
|
||||
| 'errorMessage'
|
||||
| 'invalidSortingColumnns'
|
||||
|
@ -84,6 +85,7 @@ export interface UseIndexDataReturnType
|
|||
}
|
||||
|
||||
export interface UseDataGridReturnType {
|
||||
ccsWarning: boolean;
|
||||
chartsVisible: ChartsVisible;
|
||||
chartsButtonVisible: boolean;
|
||||
columnsWithCharts: EuiDataGridColumn[];
|
||||
|
@ -97,6 +99,7 @@ export interface UseDataGridReturnType {
|
|||
resetPagination: () => void;
|
||||
rowCount: number;
|
||||
rowCountRelation: RowCountRelation;
|
||||
setCcsWarning: Dispatch<SetStateAction<boolean>>;
|
||||
setColumnCharts: Dispatch<SetStateAction<ChartData[]>>;
|
||||
setErrorMessage: Dispatch<SetStateAction<string>>;
|
||||
setNoDataMessage: Dispatch<SetStateAction<string>>;
|
||||
|
|
|
@ -36,6 +36,7 @@ export const useDataGrid = (
|
|||
): UseDataGridReturnType => {
|
||||
const defaultPagination: IndexPagination = { pageIndex: 0, pageSize: defaultPageSize };
|
||||
|
||||
const [ccsWarning, setCcsWarning] = useState(false);
|
||||
const [noDataMessage, setNoDataMessage] = useState('');
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const [status, setStatus] = useState(INDEX_STATUS.UNUSED);
|
||||
|
@ -152,6 +153,7 @@ export const useDataGrid = (
|
|||
}, [chartsVisible, rowCount, rowCountRelation]);
|
||||
|
||||
return {
|
||||
ccsWarning,
|
||||
chartsVisible,
|
||||
chartsButtonVisible: true,
|
||||
columnsWithCharts,
|
||||
|
@ -166,6 +168,7 @@ export const useDataGrid = (
|
|||
rowCount,
|
||||
rowCountRelation,
|
||||
setColumnCharts,
|
||||
setCcsWarning,
|
||||
setErrorMessage,
|
||||
setNoDataMessage,
|
||||
setPagination,
|
||||
|
|
|
@ -136,9 +136,20 @@ const apiFactory = () => ({
|
|||
return Promise.resolve([]);
|
||||
},
|
||||
async esSearch(payload: any): Promise<estypes.SearchResponse | HttpFetchError> {
|
||||
const hits = [];
|
||||
|
||||
// simulate a cross cluster search result
|
||||
// against a cluster that doesn't support fields
|
||||
if (payload.index.includes(':')) {
|
||||
hits.push({
|
||||
_id: 'the-doc',
|
||||
_index: 'the-index',
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
hits: {
|
||||
hits: [],
|
||||
hits,
|
||||
total: {
|
||||
value: 0,
|
||||
relation: 'eq',
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
import React, { FC } from 'react';
|
||||
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { CoreSetup } from 'src/core/public';
|
||||
|
@ -49,6 +50,7 @@ describe('Transform: useIndexData()', () => {
|
|||
const wrapper: FC = ({ children }) => (
|
||||
<MlSharedContext.Provider value={mlShared}>{children}</MlSharedContext.Provider>
|
||||
);
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() =>
|
||||
useIndexData(
|
||||
|
@ -62,6 +64,7 @@ describe('Transform: useIndexData()', () => {
|
|||
),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
const IndexObj: UseIndexDataReturnType = result.current;
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
@ -73,7 +76,7 @@ describe('Transform: useIndexData()', () => {
|
|||
});
|
||||
|
||||
describe('Transform: <DataGrid /> with useIndexData()', () => {
|
||||
test('Minimal initialization', async () => {
|
||||
test('Minimal initialization, no cross cluster search warning.', async () => {
|
||||
// Arrange
|
||||
const indexPattern = {
|
||||
title: 'the-index-pattern-title',
|
||||
|
@ -97,7 +100,8 @@ describe('Transform: <DataGrid /> with useIndexData()', () => {
|
|||
|
||||
return <DataGrid {...props} />;
|
||||
};
|
||||
const { getByText } = render(
|
||||
|
||||
const { queryByText } = render(
|
||||
<MlSharedContext.Provider value={mlSharedImports}>
|
||||
<Wrapper />
|
||||
</MlSharedContext.Provider>
|
||||
|
@ -105,6 +109,48 @@ describe('Transform: <DataGrid /> with useIndexData()', () => {
|
|||
|
||||
// Act
|
||||
// Assert
|
||||
expect(getByText('the-index-preview-title')).toBeInTheDocument();
|
||||
await waitFor(() => {
|
||||
expect(queryByText('the-index-preview-title')).toBeInTheDocument();
|
||||
expect(queryByText('Cross-cluster search returned no fields data.')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('Cross-cluster search warning', async () => {
|
||||
// Arrange
|
||||
const indexPattern = {
|
||||
title: 'remote:the-index-pattern-title',
|
||||
fields: [] as any[],
|
||||
} as SearchItems['indexPattern'];
|
||||
|
||||
const mlSharedImports = await getMlSharedImports();
|
||||
|
||||
const Wrapper = () => {
|
||||
const {
|
||||
ml: { DataGrid },
|
||||
} = useAppDependencies();
|
||||
const props = {
|
||||
...useIndexData(indexPattern, { match_all: {} }, runtimeMappings),
|
||||
copyToClipboard: 'the-copy-to-clipboard-code',
|
||||
copyToClipboardDescription: 'the-copy-to-clipboard-description',
|
||||
dataTestSubj: 'the-data-test-subj',
|
||||
title: 'the-index-preview-title',
|
||||
toastNotifications: {} as CoreSetup['notifications']['toasts'],
|
||||
};
|
||||
|
||||
return <DataGrid {...props} />;
|
||||
};
|
||||
|
||||
const { queryByText } = render(
|
||||
<MlSharedContext.Provider value={mlSharedImports}>
|
||||
<Wrapper />
|
||||
</MlSharedContext.Provider>
|
||||
);
|
||||
|
||||
// Act
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(queryByText('the-index-preview-title')).toBeInTheDocument();
|
||||
expect(queryByText('Cross-cluster search returned no fields data.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -87,6 +87,7 @@ export const useIndexData = (
|
|||
pagination,
|
||||
resetPagination,
|
||||
setColumnCharts,
|
||||
setCcsWarning,
|
||||
setErrorMessage,
|
||||
setRowCount,
|
||||
setRowCountRelation,
|
||||
|
@ -134,8 +135,12 @@ export const useIndexData = (
|
|||
return;
|
||||
}
|
||||
|
||||
const isCrossClusterSearch = indexPattern.title.includes(':');
|
||||
const isMissingFields = resp.hits.hits.every((d) => typeof d.fields === 'undefined');
|
||||
|
||||
const docs = resp.hits.hits.map((d) => getProcessedFields(d.fields ?? {}));
|
||||
|
||||
setCcsWarning(isCrossClusterSearch && isMissingFields);
|
||||
setRowCount(typeof resp.hits.total === 'number' ? resp.hits.total : resp.hits.total.value);
|
||||
setRowCountRelation(
|
||||
typeof resp.hits.total === 'number'
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, wait } from '@testing-library/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
import { PIVOT_SUPPORTED_AGGS } from '../../../../../../common/types/pivot_aggs';
|
||||
|
||||
|
@ -77,7 +77,7 @@ describe('Transform: <DefinePivotSummary />', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const { getByText } = render(
|
||||
const { queryByText } = render(
|
||||
<MlSharedContext.Provider value={mlSharedImports}>
|
||||
<StepDefineSummary formState={formState} searchItems={searchItems as SearchItems} />
|
||||
</MlSharedContext.Provider>
|
||||
|
@ -85,8 +85,9 @@ describe('Transform: <DefinePivotSummary />', () => {
|
|||
|
||||
// Act
|
||||
// Assert
|
||||
expect(getByText('Group by')).toBeInTheDocument();
|
||||
expect(getByText('Aggregations')).toBeInTheDocument();
|
||||
await wait();
|
||||
await waitFor(() => {
|
||||
expect(queryByText('Group by')).toBeInTheDocument();
|
||||
expect(queryByText('Aggregations')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue