[Lens][Embeddable] Restore show missing dataView error message in case of missing datasource (#208363)

## Summary

Fixes #207428 

This PR restores the `Could not find data view xxxx` message when a
dataView referenced by the visualization is missing.
<img width="764" alt="Screenshot 2025-01-27 at 14 18 19"
src="https://github.com/user-attachments/assets/14ed86fc-f6db-4056-8517-2a14fe491541"
/>

### Checklist

Check the PR satisfies following conditions. 

- [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
This commit is contained in:
Marco Liberati 2025-02-04 15:34:13 +01:00 committed by GitHub
parent 771a080ffa
commit f332644698
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 90 additions and 20 deletions

View file

@ -141,7 +141,7 @@ function getMissingIndexPatternsErrors(
// Check for access to both Management app && specific indexPattern section
const { management: isManagementEnabled } = core.application.capabilities.navLinks;
const isIndexPatternManagementEnabled =
core.application.capabilities.management.kibana.indexPatterns;
core.application.capabilities.management?.kibana?.indexPatterns;
const canFix = isManagementEnabled && isIndexPatternManagementEnabled;
return [
{

View file

@ -34,7 +34,7 @@ import {
} from '@kbn/presentation-publishing';
import { PublishesSearchSession } from '@kbn/presentation-publishing/interfaces/fetch/publishes_search_session';
import { isObject } from 'lodash';
import { defaultDoc } from '../mocks';
import { createMockDatasource, defaultDoc } from '../mocks';
jest.mock('@kbn/interpreter', () => ({
toExpression: jest.fn().mockReturnValue('expression'),
@ -87,13 +87,21 @@ type ChangeFnType = ({
async function expectRerenderOnDataLoder(
changeFn: ChangeFnType,
runtimeState: LensRuntimeState = { attributes: getLensAttributesMock() },
parentApiOverrides?: Partial<
{
filters$: BehaviorSubject<Filter[] | undefined>;
query$: BehaviorSubject<Query | AggregateQuery | undefined>;
timeRange$: BehaviorSubject<TimeRange | undefined>;
} & LensOverrides
>
{
parentApiOverrides,
servicesOverrides,
internalApiOverrides,
}: {
parentApiOverrides?: Partial<
{
filters$: BehaviorSubject<Filter[] | undefined>;
query$: BehaviorSubject<Query | AggregateQuery | undefined>;
timeRange$: BehaviorSubject<TimeRange | undefined>;
} & LensOverrides
>;
internalApiOverrides?: Partial<LensInternalApi>;
servicesOverrides?: Partial<LensEmbeddableStartServices>;
} = {}
): Promise<void> {
const parentApi = {
...createUnifiedSearchApi(),
@ -116,12 +124,15 @@ async function expectRerenderOnDataLoder(
parentApi,
};
const getState = jest.fn(() => runtimeState);
const internalApi = getLensInternalApiMock();
const services = makeEmbeddableServices(new BehaviorSubject<string>(''), undefined, {
visOverrides: { id: 'lnsXY' },
dataOverrides: { id: 'form_based' },
});
services.documentToExpression = jest.fn().mockResolvedValue({ ast: 'expression_string' });
const internalApi = getLensInternalApiMock(internalApiOverrides);
const services = {
...makeEmbeddableServices(new BehaviorSubject<string>(''), undefined, {
visOverrides: { id: 'lnsXY' },
dataOverrides: { id: 'form_based' },
}),
documentToExpression: jest.fn().mockResolvedValue({ ast: 'expression_string' }),
...servicesOverrides,
};
const { cleanup } = loadEmbeddableData(
faker.string.uuid(),
getState,
@ -242,7 +253,7 @@ describe('Data Loader', () => {
return false;
},
undefined, // use default attributes
createUnifiedSearchApi(query, filters) // customize parentApi
{ parentApiOverrides: createUnifiedSearchApi(query, filters) } // customize parentApi
);
});
@ -305,7 +316,13 @@ describe('Data Loader', () => {
return false;
},
{ attributes },
createUnifiedSearchApi(parentApiQuery, parentApiFilters, parentApiTimeRange)
{
parentApiOverrides: createUnifiedSearchApi(
parentApiQuery,
parentApiFilters,
parentApiTimeRange
),
}
);
});
@ -411,4 +428,54 @@ describe('Data Loader', () => {
}
);
});
it('should catch missing dataView errors correctly', async () => {
await expectRerenderOnDataLoder(
async ({ internalApi }) => {
// wait for the error to appear
await waitForValue(internalApi.blockingError$);
const error = internalApi.blockingError$.getValue()!;
expect(error.message).toEqual(
'Could not find the data view: 90943e30-9a47-11e8-b64d-95841ca0b247'
);
return false;
},
undefined,
// Unfortuantely some mocks are required here to make the test work
{
// Mock the testing datasource to return an error when asked for checkIntegrity
servicesOverrides: {
datasourceMap: {
form_based: {
...createMockDatasource('form_based'),
checkIntegrity: jest.fn().mockReturnValue(['90943e30-9a47-11e8-b64d-95841ca0b247']),
},
},
},
// Mock the visualization context to fully load the datasource state
internalApiOverrides: {
getVisualizationContext: jest.fn().mockReturnValue({
activeAttributes: {
...defaultDoc,
visualizationType: 'lnsXY',
state: { ...defaultDoc.state, datasourceStates: { form_based: {} } },
},
mergedSearchContext: {
now: Date.now(),
timeRange: { from: 'now-15m', to: 'now' },
query: [defaultDoc.state.query],
filters: [],
disableWarningToasts: true,
},
indexPatterns: [],
indexPatternRefs: {},
activeVisualizationState: { activeId: 'lnsXY' },
activeDatasourceState: {},
activeData: {},
}),
},
}
);
});
});

View file

@ -202,7 +202,7 @@ export function loadEmbeddableData(
);
// Go concurrently: build the expression and fetch the dataViews
const [{ params, abortController, ...rest }, dataViews] = await Promise.all([
const [{ params, abortController, ...rest }, dataViewIds] = await Promise.all([
getExpressionRendererParams(currentState, {
searchContext,
api,
@ -242,9 +242,12 @@ export function loadEmbeddableData(
});
// Publish the used dataViews on the Lens API
internalApi.updateDataViews(dataViews);
internalApi.updateDataViews(dataViewIds);
if (params?.expression != null && !dispatchBlockingErrorIfAny()) {
// This will catch also failed loaded dataViews
const hasBlockingErrors = dispatchBlockingErrorIfAny();
if (params?.expression != null && !hasBlockingErrors) {
internalApi.updateExpressionParams(params);
}