mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
771a080ffa
commit
f332644698
3 changed files with 90 additions and 20 deletions
|
@ -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 [
|
||||
{
|
||||
|
|
|
@ -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: {},
|
||||
}),
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue