mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Lens] Shard failure notices make it impossible to use Lens (#142985)
* [Lens] Shard failure notices make it impossible to use Lens * rename getTSDBRollupWarningMessages -> getShardFailuresWarningMessages * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * update UI * push some logic * fix CI * push some changes * delete outdated test * fix CI * push some logic * add KibanaThemeProvider * apply UI changes for shard_failure_open_modal_button * cleanup Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
1d06b5ffc1
commit
d9adcfee0e
16 changed files with 267 additions and 146 deletions
|
@ -169,6 +169,7 @@ export type {
|
|||
IEsError,
|
||||
Reason,
|
||||
WaitUntilNextSessionCompletesOptions,
|
||||
SearchResponseWarning,
|
||||
} from './search';
|
||||
|
||||
export {
|
||||
|
@ -270,6 +271,9 @@ export type {
|
|||
GlobalQueryStateFromUrl,
|
||||
} from './query';
|
||||
|
||||
export type { ShardFailureRequest } from './shard_failure_modal';
|
||||
export { ShardFailureOpenModalButton } from './shard_failure_modal';
|
||||
|
||||
export type { AggsStart } from './search/aggs';
|
||||
|
||||
export { getTime } from '../common';
|
||||
|
|
|
@ -13,6 +13,7 @@ import { setNotifications } from '../../services';
|
|||
import { SearchResponseWarning } from '../types';
|
||||
import { filterWarnings, handleWarnings } from './handle_warnings';
|
||||
import * as extract from './extract_warnings';
|
||||
import { SearchRequest } from '../../../common';
|
||||
|
||||
jest.mock('@kbn/i18n', () => {
|
||||
return {
|
||||
|
@ -152,6 +153,8 @@ describe('Filtering and showing warnings', () => {
|
|||
|
||||
describe('filterWarnings', () => {
|
||||
const callback = jest.fn();
|
||||
const request = {} as SearchRequest;
|
||||
const response = {} as estypes.SearchResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
callback.mockImplementation(() => {
|
||||
|
@ -161,19 +164,19 @@ describe('Filtering and showing warnings', () => {
|
|||
|
||||
it('filters out all', () => {
|
||||
callback.mockImplementation(() => true);
|
||||
expect(filterWarnings(warnings, callback)).toEqual([]);
|
||||
expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual([]);
|
||||
});
|
||||
|
||||
it('filters out some', () => {
|
||||
callback.mockImplementation(
|
||||
(warning: SearchResponseWarning) => warning.reason?.type !== 'generic_shard_failure'
|
||||
);
|
||||
expect(filterWarnings(warnings, callback)).toEqual([warnings[2]]);
|
||||
expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual([warnings[2]]);
|
||||
});
|
||||
|
||||
it('filters out none', () => {
|
||||
callback.mockImplementation(() => false);
|
||||
expect(filterWarnings(warnings, callback)).toEqual(warnings);
|
||||
expect(filterWarnings(warnings, callback, request, response, 'id')).toEqual(warnings);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { debounce } from 'lodash';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { EuiSpacer, EuiTextAlign } from '@elastic/eui';
|
||||
import { ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
|
@ -57,19 +57,23 @@ export function handleWarnings({
|
|||
theme,
|
||||
callback,
|
||||
sessionId = '',
|
||||
requestId,
|
||||
}: {
|
||||
request: SearchRequest;
|
||||
response: estypes.SearchResponse;
|
||||
theme: ThemeServiceStart;
|
||||
callback?: WarningHandlerCallback;
|
||||
sessionId?: string;
|
||||
requestId?: string;
|
||||
}) {
|
||||
const warnings = extractWarnings(response);
|
||||
if (warnings.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const internal = callback ? filterWarnings(warnings, callback) : warnings;
|
||||
const internal = callback
|
||||
? filterWarnings(warnings, callback, request, response, requestId)
|
||||
: warnings;
|
||||
if (internal.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -95,12 +99,16 @@ export function handleWarnings({
|
|||
<>
|
||||
{warning.text}
|
||||
<EuiSpacer size="s" />
|
||||
<ShardFailureOpenModalButton
|
||||
request={request as ShardFailureRequest}
|
||||
response={response}
|
||||
theme={theme}
|
||||
title={title}
|
||||
/>
|
||||
<EuiTextAlign textAlign="right">
|
||||
<ShardFailureOpenModalButton
|
||||
theme={theme}
|
||||
title={title}
|
||||
getRequestMeta={() => ({
|
||||
request: request as ShardFailureRequest,
|
||||
response,
|
||||
})}
|
||||
/>
|
||||
</EuiTextAlign>
|
||||
</>,
|
||||
{ theme$: theme.theme$ }
|
||||
);
|
||||
|
@ -116,12 +124,22 @@ export function handleWarnings({
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function filterWarnings(warnings: SearchResponseWarning[], cb: WarningHandlerCallback) {
|
||||
export function filterWarnings(
|
||||
warnings: SearchResponseWarning[],
|
||||
cb: WarningHandlerCallback,
|
||||
request: SearchRequest,
|
||||
response: estypes.SearchResponse,
|
||||
requestId: string | undefined
|
||||
) {
|
||||
const unfiltered: SearchResponseWarning[] = [];
|
||||
|
||||
// use the consumer's callback as a filter to receive warnings to handle on our side
|
||||
warnings.forEach((warning) => {
|
||||
const consumerHandled = cb?.(warning);
|
||||
const consumerHandled = cb?.(warning, {
|
||||
requestId,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
if (!consumerHandled) {
|
||||
unfiltered.push(warning);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export * from './expressions';
|
||||
|
||||
export type {
|
||||
SearchResponseWarning,
|
||||
ISearchSetup,
|
||||
ISearchStart,
|
||||
ISearchStartSearchSource,
|
||||
|
|
|
@ -241,11 +241,13 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
onResponse: (request, response, options) => {
|
||||
if (!options.disableShardFailureWarning) {
|
||||
const { rawResponse } = response;
|
||||
|
||||
handleWarnings({
|
||||
request: request.body,
|
||||
response: rawResponse,
|
||||
theme,
|
||||
sessionId: options.sessionId,
|
||||
requestId: request.id,
|
||||
});
|
||||
}
|
||||
return response;
|
||||
|
@ -286,12 +288,12 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
|
|||
if (!rawResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleWarnings({
|
||||
request: request.json as SearchRequest,
|
||||
response: rawResponse,
|
||||
theme,
|
||||
callback,
|
||||
requestId: request.id,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { PackageInfo } from '@kbn/core/server';
|
|||
import { DataViewsContract } from '@kbn/data-views-plugin/common';
|
||||
import { RequestAdapter } from '@kbn/inspector-plugin/public';
|
||||
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { ISearchGeneric, ISearchStartSearchSource } from '../../common/search';
|
||||
import { ISearchGeneric, ISearchStartSearchSource, SearchRequest } from '../../common/search';
|
||||
import { AggsSetup, AggsSetupDependencies, AggsStart, AggsStartDependencies } from './aggs';
|
||||
import { SearchUsageCollector } from './collectors';
|
||||
import { ISessionsClient, ISessionService } from './session';
|
||||
|
@ -159,4 +159,11 @@ export type SearchResponseWarning =
|
|||
* function to prevent the search service from showing warning notifications by default.
|
||||
* @public
|
||||
*/
|
||||
export type WarningHandlerCallback = (warnings: SearchResponseWarning) => boolean | undefined;
|
||||
export type WarningHandlerCallback = (
|
||||
warnings: SearchResponseWarning,
|
||||
meta: {
|
||||
request: SearchRequest;
|
||||
response: estypes.SearchResponse;
|
||||
requestId: string | undefined;
|
||||
}
|
||||
) => boolean | undefined;
|
||||
|
|
|
@ -21,8 +21,10 @@ describe('ShardFailureOpenModalButton', () => {
|
|||
it('triggers the openModal function when "Show details" button is clicked', () => {
|
||||
const component = mountWithIntl(
|
||||
<ShardFailureOpenModalButton
|
||||
request={shardFailureRequest}
|
||||
response={shardFailureResponse}
|
||||
getRequestMeta={() => ({
|
||||
request: shardFailureRequest,
|
||||
response: shardFailureResponse,
|
||||
})}
|
||||
theme={theme}
|
||||
title="test"
|
||||
/>
|
||||
|
|
|
@ -6,33 +6,41 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiButton, EuiTextAlign } from '@elastic/eui';
|
||||
import { EuiLink, EuiButton, EuiButtonProps } from '@elastic/eui';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { ThemeServiceStart } from '@kbn/core/public';
|
||||
import type { ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { getOverlays } from '../services';
|
||||
import { ShardFailureModal } from './shard_failure_modal';
|
||||
import { ShardFailureRequest } from './shard_failure_types';
|
||||
import type { ShardFailureRequest } from './shard_failure_types';
|
||||
|
||||
// @internal
|
||||
export interface ShardFailureOpenModalButtonProps {
|
||||
request: ShardFailureRequest;
|
||||
response: estypes.SearchResponse<any>;
|
||||
theme: ThemeServiceStart;
|
||||
title: string;
|
||||
size?: EuiButtonProps['size'];
|
||||
color?: EuiButtonProps['color'];
|
||||
getRequestMeta: () => {
|
||||
request: ShardFailureRequest;
|
||||
response: estypes.SearchResponse<any>;
|
||||
};
|
||||
isButtonEmpty?: boolean;
|
||||
}
|
||||
|
||||
// Needed for React.lazy
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ShardFailureOpenModalButton({
|
||||
request,
|
||||
response,
|
||||
getRequestMeta,
|
||||
theme,
|
||||
title,
|
||||
size = 's',
|
||||
color = 'warning',
|
||||
isButtonEmpty = false,
|
||||
}: ShardFailureOpenModalButtonProps) {
|
||||
function onClick() {
|
||||
const onClick = useCallback(() => {
|
||||
const { request, response } = getRequestMeta();
|
||||
const modal = getOverlays().openModal(
|
||||
toMountPoint(
|
||||
<ShardFailureModal
|
||||
|
@ -47,21 +55,22 @@ export default function ShardFailureOpenModalButton({
|
|||
className: 'shardFailureModal',
|
||||
}
|
||||
);
|
||||
}
|
||||
}, [getRequestMeta, theme.theme$, title]);
|
||||
|
||||
const Component = isButtonEmpty ? EuiLink : EuiButton;
|
||||
|
||||
return (
|
||||
<EuiTextAlign textAlign="right">
|
||||
<EuiButton
|
||||
color="warning"
|
||||
size="s"
|
||||
onClick={onClick}
|
||||
data-test-subj="openShardFailureModalBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.showDetails"
|
||||
defaultMessage="Show details"
|
||||
description="Open the modal to show details"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiTextAlign>
|
||||
<Component
|
||||
color={color}
|
||||
size={size}
|
||||
onClick={onClick}
|
||||
data-test-subj="openShardFailureModalBtn"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="data.search.searchSource.fetch.shardsFailedModal.showDetails"
|
||||
defaultMessage="Show details"
|
||||
description="Open the modal to show details"
|
||||
/>
|
||||
</Component>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ import {
|
|||
|
||||
import {
|
||||
getFiltersInLayer,
|
||||
getTSDBRollupWarningMessages,
|
||||
getShardFailuresWarningMessages,
|
||||
getVisualDefaultsForLayer,
|
||||
isColumnInvalid,
|
||||
cloneLayer,
|
||||
|
@ -89,10 +89,10 @@ import {
|
|||
} from './operations/layer_helpers';
|
||||
import { FormBasedPrivateState, FormBasedPersistedState, DataViewDragDropOperation } from './types';
|
||||
import { mergeLayer, mergeLayers } from './state_helpers';
|
||||
import { Datasource, VisualizeEditorContext } from '../../types';
|
||||
import type { Datasource, VisualizeEditorContext } from '../../types';
|
||||
import { deleteColumn, isReferenced } from './operations';
|
||||
import { GeoFieldWorkspacePanel } from '../../editor_frame_service/editor_frame/workspace_panel/geo_field_workspace_panel';
|
||||
import { DraggingIdentifier } from '../../drag_drop';
|
||||
import type { DraggingIdentifier } from '../../drag_drop';
|
||||
import { getStateTimeShiftWarningMessages } from './time_shift_utils';
|
||||
import { getPrecisionErrorWarningMessages } from './utils';
|
||||
import { DOCUMENT_FIELD_NAME } from '../../../common/constants';
|
||||
|
@ -897,8 +897,8 @@ export function getFormBasedDatasource({
|
|||
),
|
||||
];
|
||||
},
|
||||
getSearchWarningMessages: (state, warning) => {
|
||||
return [...getTSDBRollupWarningMessages(state, warning)];
|
||||
getSearchWarningMessages: (state, warning, request, response) => {
|
||||
return [...getShardFailuresWarningMessages(state, warning, request, response, core.theme)];
|
||||
},
|
||||
getDeprecationMessages: () => {
|
||||
const deprecatedMessages: React.ReactNode[] = [];
|
||||
|
|
|
@ -8,15 +8,23 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { DocLinksStart } from '@kbn/core/public';
|
||||
import type { DocLinksStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { EuiLink, EuiTextColor, EuiButton, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiLink, EuiTextColor, EuiButton, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||
import { groupBy, escape, uniq } from 'lodash';
|
||||
import type { Query } from '@kbn/data-plugin/common';
|
||||
import { SearchResponseWarning } from '@kbn/data-plugin/public/search/types';
|
||||
import { SearchRequest } from '@kbn/data-plugin/common';
|
||||
|
||||
import {
|
||||
SearchResponseWarning,
|
||||
ShardFailureOpenModalButton,
|
||||
ShardFailureRequest,
|
||||
} from '@kbn/data-plugin/public';
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import type { FramePublicAPI, IndexPattern, StateSetter } from '../../types';
|
||||
import { renewIDs } from '../../utils';
|
||||
import type { FormBasedLayer, FormBasedPersistedState, FormBasedPrivateState } from './types';
|
||||
|
@ -162,43 +170,67 @@ const accuracyModeEnabledWarning = (columnName: string, docLink: string) => (
|
|||
/>
|
||||
);
|
||||
|
||||
export function getTSDBRollupWarningMessages(
|
||||
export function getShardFailuresWarningMessages(
|
||||
state: FormBasedPersistedState,
|
||||
warning: SearchResponseWarning
|
||||
) {
|
||||
warning: SearchResponseWarning,
|
||||
request: SearchRequest,
|
||||
response: estypes.SearchResponse,
|
||||
theme: ThemeServiceStart
|
||||
): Array<string | React.ReactNode> {
|
||||
if (state) {
|
||||
const hasTSDBRollupWarnings =
|
||||
warning.type === 'shard_failure' &&
|
||||
warning.reason.type === 'unsupported_aggregation_on_downsampled_index';
|
||||
if (!hasTSDBRollupWarnings) {
|
||||
return [];
|
||||
if (warning.type === 'shard_failure') {
|
||||
switch (warning.reason.type) {
|
||||
case 'unsupported_aggregation_on_downsampled_index':
|
||||
return Object.values(state.layers).flatMap((layer) =>
|
||||
uniq(
|
||||
Object.values(layer.columns)
|
||||
.filter((col) =>
|
||||
[
|
||||
'median',
|
||||
'percentile',
|
||||
'percentile_rank',
|
||||
'last_value',
|
||||
'unique_count',
|
||||
'standard_deviation',
|
||||
].includes(col.operationType)
|
||||
)
|
||||
.map((col) => col.label)
|
||||
).map((label) =>
|
||||
i18n.translate('xpack.lens.indexPattern.tsdbRollupWarning', {
|
||||
defaultMessage:
|
||||
'{label} uses a function that is unsupported by rolled up data. Select a different function or change the time range.',
|
||||
values: {
|
||||
label,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
default:
|
||||
return [
|
||||
<>
|
||||
<EuiText size="s">
|
||||
<strong>{warning.message}</strong>
|
||||
<p>{warning.text}</p>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
{warning.text ? (
|
||||
<ShardFailureOpenModalButton
|
||||
theme={theme}
|
||||
title={warning.message}
|
||||
size="m"
|
||||
getRequestMeta={() => ({
|
||||
request: request as ShardFailureRequest,
|
||||
response,
|
||||
})}
|
||||
color="primary"
|
||||
isButtonEmpty={true}
|
||||
/>
|
||||
) : null}
|
||||
</>,
|
||||
];
|
||||
}
|
||||
}
|
||||
return Object.values(state.layers).flatMap((layer) =>
|
||||
uniq(
|
||||
Object.values(layer.columns)
|
||||
.filter((col) =>
|
||||
[
|
||||
'median',
|
||||
'percentile',
|
||||
'percentile_rank',
|
||||
'last_value',
|
||||
'unique_count',
|
||||
'standard_deviation',
|
||||
].includes(col.operationType)
|
||||
)
|
||||
.map((col) => col.label)
|
||||
).map((label) =>
|
||||
i18n.translate('xpack.lens.indexPattern.tsdbRollupWarning', {
|
||||
defaultMessage:
|
||||
'{label} uses a function that is unsupported by rolled up data. Select a different function or change the time range.',
|
||||
values: {
|
||||
label,
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import type { DefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
|||
import type { Datatable } from '@kbn/expressions-plugin/public';
|
||||
import { DropIllustration } from '@kbn/chart-icons';
|
||||
import { trackUiCounterEvents } from '../../../lens_ui_telemetry';
|
||||
import { getSearchWarningMessages } from '../../../utils';
|
||||
import {
|
||||
FramePublicAPI,
|
||||
isLensBrushEvent,
|
||||
|
@ -231,32 +232,37 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
|
|||
(data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => {
|
||||
if (renderDeps.current) {
|
||||
const [defaultLayerId] = Object.keys(renderDeps.current.datasourceLayers);
|
||||
const datasource = Object.values(renderDeps.current.datasourceMap)[0];
|
||||
const datasourceState = Object.values(renderDeps.current.datasourceStates)[0].state;
|
||||
|
||||
let requestWarnings: Array<React.ReactNode | string> = [];
|
||||
|
||||
const requestWarnings: string[] = [];
|
||||
const datasource = Object.values(renderDeps.current?.datasourceMap)[0];
|
||||
const datasourceState = Object.values(renderDeps.current?.datasourceStates)[0].state;
|
||||
if (adapters?.requests) {
|
||||
plugins.data.search.showWarnings(adapters.requests, (warning) => {
|
||||
const warningMessage = datasource.getSearchWarningMessages?.(datasourceState, warning);
|
||||
|
||||
requestWarnings.push(...(warningMessage || []));
|
||||
if (warningMessage && warningMessage.length) return true;
|
||||
});
|
||||
}
|
||||
if (adapters && adapters.tables) {
|
||||
dispatchLens(
|
||||
onActiveDataChange({
|
||||
activeData: Object.entries(adapters.tables?.tables).reduce<Record<string, Datatable>>(
|
||||
(acc, [key, value], index, tables) => ({
|
||||
...acc,
|
||||
[tables.length === 1 ? defaultLayerId : key]: value,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
requestWarnings,
|
||||
})
|
||||
requestWarnings = getSearchWarningMessages(
|
||||
adapters.requests,
|
||||
datasource,
|
||||
datasourceState,
|
||||
{
|
||||
searchService: plugins.data.search,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
dispatchLens(
|
||||
onActiveDataChange({
|
||||
activeData:
|
||||
adapters && adapters.tables
|
||||
? Object.entries(adapters.tables?.tables).reduce<Record<string, Datatable>>(
|
||||
(acc, [key, value], index, tables) => ({
|
||||
...acc,
|
||||
[tables.length === 1 ? defaultLayerId : key]: value,
|
||||
}),
|
||||
{}
|
||||
)
|
||||
: undefined,
|
||||
requestWarnings,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
[dispatchLens, plugins.data.search]
|
||||
|
|
|
@ -87,7 +87,12 @@ import { LensAttributeService } from '../lens_attribute_service';
|
|||
import type { ErrorMessage, TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { getLensInspectorService, LensInspector } from '../lens_inspector_service';
|
||||
import { SharingSavedObjectProps, VisualizationDisplayOptions } from '../types';
|
||||
import { getActiveDatasourceIdFromDoc, getIndexPatternsObjects, inferTimeField } from '../utils';
|
||||
import {
|
||||
getActiveDatasourceIdFromDoc,
|
||||
getIndexPatternsObjects,
|
||||
getSearchWarningMessages,
|
||||
inferTimeField,
|
||||
} from '../utils';
|
||||
import { getLayerMetaInfo, combineQueryAndFilters } from '../app_plugin/show_underlying_data';
|
||||
import { convertDataViewIntoLensIndexPattern } from '../data_views_service/loader';
|
||||
|
||||
|
@ -531,21 +536,30 @@ export class Embeddable
|
|||
|
||||
private handleWarnings(adapters?: Partial<DefaultInspectorAdapters>) {
|
||||
const activeDatasourceId = getActiveDatasourceIdFromDoc(this.savedVis);
|
||||
if (!activeDatasourceId || !adapters?.requests) return;
|
||||
|
||||
if (!activeDatasourceId || !adapters?.requests) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDatasource = this.deps.datasourceMap[activeDatasourceId];
|
||||
const docDatasourceState = this.savedVis?.state.datasourceStates[activeDatasourceId];
|
||||
const warnings: React.ReactNode[] = [];
|
||||
this.deps.data.search.showWarnings(adapters.requests, (warning) => {
|
||||
const warningMessage = activeDatasource.getSearchWarningMessages?.(
|
||||
docDatasourceState,
|
||||
warning
|
||||
);
|
||||
|
||||
warnings.push(...(warningMessage || []));
|
||||
if (warningMessage && warningMessage.length) return true;
|
||||
});
|
||||
if (warnings && this.warningDomNode) {
|
||||
render(<Warnings warnings={warnings} />, this.warningDomNode);
|
||||
const requestWarnings = getSearchWarningMessages(
|
||||
adapters.requests,
|
||||
activeDatasource,
|
||||
docDatasourceState,
|
||||
{
|
||||
searchService: this.deps.data.search,
|
||||
}
|
||||
);
|
||||
|
||||
if (requestWarnings.length && this.warningDomNode) {
|
||||
render(
|
||||
<KibanaThemeProvider theme$={this.deps.theme.theme$}>
|
||||
<Warnings warnings={requestWarnings} />
|
||||
</KibanaThemeProvider>,
|
||||
this.warningDomNode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { mapValues, uniq } from 'lodash';
|
||||
|
@ -98,8 +99,8 @@ export const getPreloadedState = ({
|
|||
|
||||
export const setState = createAction<Partial<LensAppState>>('lens/setState');
|
||||
export const onActiveDataChange = createAction<{
|
||||
activeData: TableInspectorAdapter;
|
||||
requestWarnings?: string[];
|
||||
activeData?: TableInspectorAdapter;
|
||||
requestWarnings?: Array<ReactNode | string>;
|
||||
}>('lens/onActiveDataChange');
|
||||
export const setSaveable = createAction<boolean>('lens/setSaveable');
|
||||
export const enableAutoApply = createAction<void>('lens/enableAutoApply');
|
||||
|
@ -265,8 +266,8 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => {
|
|||
) => {
|
||||
return {
|
||||
...state,
|
||||
activeData,
|
||||
requestWarnings,
|
||||
...(activeData ? { activeData } : {}),
|
||||
...(requestWarnings ? { requestWarnings } : {}),
|
||||
};
|
||||
},
|
||||
[setSaveable.type]: (state, { payload }: PayloadAction<boolean>) => {
|
||||
|
|
|
@ -31,6 +31,9 @@ import type { FieldSpec, DataViewSpec } from '@kbn/data-views-plugin/common';
|
|||
import type { FieldFormatParams } from '@kbn/field-formats-plugin/common';
|
||||
import { SearchResponseWarning } from '@kbn/data-plugin/public/search/types';
|
||||
import type { EuiButtonIconColor } from '@elastic/eui';
|
||||
import { SearchRequest } from '@kbn/data-plugin/public';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import React from 'react';
|
||||
import type { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop';
|
||||
import type { DateRange, LayerType, SortingHint } from '../common';
|
||||
import type {
|
||||
|
@ -428,7 +431,13 @@ export interface Datasource<T = unknown, P = unknown> {
|
|||
/**
|
||||
* The embeddable calls this function to display warnings about visualization on the dashboard
|
||||
*/
|
||||
getSearchWarningMessages?: (state: P, warning: SearchResponseWarning) => string[] | undefined;
|
||||
getSearchWarningMessages?: (
|
||||
state: P,
|
||||
warning: SearchResponseWarning,
|
||||
request: SearchRequest,
|
||||
response: estypes.SearchResponse
|
||||
) => Array<string | React.ReactNode> | undefined;
|
||||
|
||||
/**
|
||||
* Checks if the visualization created is time based, for example date histogram
|
||||
*/
|
||||
|
|
|
@ -14,6 +14,9 @@ import type { IUiSettingsClient, SavedObjectReference } from '@kbn/core/public';
|
|||
import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import type { DatatableUtilitiesService } from '@kbn/data-plugin/common';
|
||||
import { BrushTriggerEvent, ClickTriggerEvent } from '@kbn/charts-plugin/public';
|
||||
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||
import { ISearchStart } from '@kbn/data-plugin/public';
|
||||
import React from 'react';
|
||||
import type { Document } from './persistence/saved_object_store';
|
||||
import {
|
||||
Datasource,
|
||||
|
@ -285,3 +288,36 @@ export const isOperationFromTheSameGroup = (op1?: DraggingIdentifier, op2?: Drag
|
|||
op1.layerId === op2.layerId
|
||||
);
|
||||
};
|
||||
|
||||
export const getSearchWarningMessages = (
|
||||
adapter: RequestAdapter,
|
||||
datasource: Datasource,
|
||||
state: unknown,
|
||||
deps: {
|
||||
searchService: ISearchStart;
|
||||
}
|
||||
) => {
|
||||
const warningsMap: Map<string, Array<string | React.ReactNode>> = new Map();
|
||||
|
||||
deps.searchService.showWarnings(adapter, (warning, meta) => {
|
||||
const { request, response, requestId } = meta;
|
||||
|
||||
const warningMessages = datasource.getSearchWarningMessages?.(
|
||||
state,
|
||||
warning,
|
||||
request,
|
||||
response
|
||||
);
|
||||
|
||||
if (warningMessages?.length) {
|
||||
const key = (requestId ?? '') + warning.type + warning.reason?.type ?? '';
|
||||
if (!warningsMap.has(key)) {
|
||||
warningsMap.set(key, warningMessages);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return [...warningsMap.values()].flat();
|
||||
};
|
||||
|
|
|
@ -9,10 +9,8 @@ import expect from '@kbn/expect';
|
|||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const queryBar = getService('queryBar');
|
||||
const PageObjects = getPageObjects(['common', 'timePicker', 'lens', 'dashboard']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const retry = getService('retry');
|
||||
const es = getService('es');
|
||||
const log = getService('log');
|
||||
const indexPatterns = getService('indexPatterns');
|
||||
|
@ -130,27 +128,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
'Median of kubernetes.container.memory.available.bytes uses a function that is unsupported by rolled up data. Select a different function or change the time range.'
|
||||
);
|
||||
});
|
||||
it('still shows other warnings as toast', async () => {
|
||||
await es.indices.delete({ index: [testRollupIndex] });
|
||||
// index a document which will produce a shard failure because a string field doesn't support median
|
||||
await es.create({
|
||||
id: '1',
|
||||
index: testRollupIndex,
|
||||
document: {
|
||||
'kubernetes.container.memory.available.bytes': 'fsdfdsf',
|
||||
'@timestamp': '2022-06-20',
|
||||
},
|
||||
wait_for_active_shards: 1,
|
||||
});
|
||||
await retry.try(async () => {
|
||||
await queryBar.clickQuerySubmitButton();
|
||||
expect(
|
||||
await (await testSubjects.find('euiToastHeader__title', 1000)).getVisibleText()
|
||||
).to.equal('1 of 3 shards failed');
|
||||
});
|
||||
// as the rollup index is gone, there is no inline warning left
|
||||
await PageObjects.lens.assertNoInlineWarning();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue