From affa1f970b0de8f6e0e9a07888131586fec1fdad Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 19 Dec 2024 18:23:19 +0100 Subject: [PATCH] :white_check_mark: Integrate last tests --- .../react_embeddable/data_loader.test.ts | 96 +++++++++++++++---- .../public/react_embeddable/data_loader.ts | 8 +- .../initializers/initialize_edit.test.ts | 70 ++++++++++++++ .../initializers/initialize_edit.tsx | 7 +- .../user_messages/api.test.ts | 4 +- 5 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.test.ts diff --git a/x-pack/plugins/lens/public/react_embeddable/data_loader.test.ts b/x-pack/plugins/lens/public/react_embeddable/data_loader.test.ts index 39108a4e3ac63..fe2b4416a4943 100644 --- a/x-pack/plugins/lens/public/react_embeddable/data_loader.test.ts +++ b/x-pack/plugins/lens/public/react_embeddable/data_loader.test.ts @@ -21,7 +21,9 @@ import { LensApi, LensEmbeddableStartServices, LensInternalApi, + LensOverrides, LensPublicCallbacks, + LensRuntimeState, } from './types'; import { HasParentApi, @@ -84,12 +86,14 @@ type ChangeFnType = ({ async function callDataLoader( changeFn: ChangeFnType, - attributes: LensDocument = getLensAttributesMock(), - parentApiOverrides?: Partial<{ - filters$: BehaviorSubject; - query$: BehaviorSubject; - timeRange$: BehaviorSubject; - }> + runtimeState: LensRuntimeState = { attributes: getLensAttributesMock() }, + parentApiOverrides?: Partial< + { + filters$: BehaviorSubject; + query$: BehaviorSubject; + timeRange$: BehaviorSubject; + } & LensOverrides + > ): Promise { const parentApi = { ...createUnifiedSearchApi(), @@ -111,9 +115,7 @@ async function callDataLoader( ...getLensApiMock(), parentApi, }; - const getState = jest.fn(() => ({ - attributes, - })); + const getState = jest.fn(() => runtimeState); const internalApi = getLensInternalApiMock(); const services = makeEmbeddableServices(new BehaviorSubject(''), undefined, { visOverrides: { id: 'lnsXY' }, @@ -126,7 +128,7 @@ async function callDataLoader( api, parentApi, internalApi, - services, + services ); // there's a debounce, so skip to the next tick jest.advanceTimersByTime(100); @@ -302,7 +304,7 @@ describe('Data Loader', () => { return false; }, - attributes, + { attributes }, createUnifiedSearchApi(parentApiQuery, parentApiFilters, parentApiTimeRange) ); }); @@ -342,13 +344,71 @@ describe('Data Loader', () => { return false; }, - getLensAttributesMock({ - references: [ - { type: 'index-pattern', id: '123', name: 'abc' }, - { type: 'index-pattern', id: '123', name: 'def' }, - { type: 'index-pattern', id: '456', name: 'ghi' }, - ], - }) + { + attributes: getLensAttributesMock({ + references: [ + { type: 'index-pattern', id: '123', name: 'abc' }, + { type: 'index-pattern', id: '123', name: 'def' }, + { type: 'index-pattern', id: '456', name: 'ghi' }, + ], + }), + } + ); + }); + + it('should override noPadding in the display options if noPadding is set in the embeddable input', async () => { + await callDataLoader(async ({ internalApi }) => { + await waitForValue( + internalApi.expressionParams$, + (v: unknown) => isObject(v) && 'expression' in v && typeof v.expression != null + ); + + const params = internalApi.expressionParams$.getValue()!; + expect(params.noPadding).toBeUndefined(); + return false; + }); + }); + + it('should reload only once when the attributes or savedObjectId and the search context change at the same time', async () => { + await callDataLoader(async ({ internalApi, api }) => { + // trigger a change by changing the title in the attributes + (internalApi.attributes$ as BehaviorSubject).next({ + ...internalApi.attributes$.getValue(), + title: faker.lorem.word(), + }); + (api.savedObjectId as BehaviorSubject).next('newSavedObjectId'); + }); + }); + + it('should pass over the overrides as variables', async () => { + await callDataLoader( + async ({ internalApi }) => { + await waitForValue( + internalApi.expressionParams$, + (v: unknown) => isObject(v) && 'variables' in v && typeof v.variables != null + ); + + const params = internalApi.expressionParams$.getValue()!; + expect(params.variables).toEqual( + expect.objectContaining({ + overrides: { + settings: { + onBrushEnd: 'ignore', + }, + }, + }) + ); + return false; + }, + // send a runtime state with the overrides + { + attributes: getLensAttributesMock(), + overrides: { + settings: { + onBrushEnd: 'ignore', + }, + }, + } ); }); }); diff --git a/x-pack/plugins/lens/public/react_embeddable/data_loader.ts b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts index fd712fb6e6d2f..4a9a5c03fb095 100644 --- a/x-pack/plugins/lens/public/react_embeddable/data_loader.ts +++ b/x-pack/plugins/lens/public/react_embeddable/data_loader.ts @@ -93,13 +93,7 @@ export function loadEmbeddableData( updateWarnings, resetMessages, updateMessages, - } = buildUserMessagesHelpers( - api, - internalApi, - services, - onBeforeBadgesRender, - metaInfo - ); + } = buildUserMessagesHelpers(api, internalApi, services, onBeforeBadgesRender, metaInfo); const dispatchBlockingErrorIfAny = () => { const blockingErrors = getUserMessages(blockingMessageDisplayLocations, { diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.test.ts b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.test.ts new file mode 100644 index 0000000000000..6f382b616395b --- /dev/null +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { faker } from '@faker-js/faker'; +import { initializeEditApi } from './initialize_edit'; +import { + getLensApiMock, + getLensInternalApiMock, + getLensRuntimeStateMock, + makeEmbeddableServices, +} from '../mocks'; +import { BehaviorSubject } from 'rxjs'; +import { ApplicationStart } from '@kbn/core/public'; +import { LensEmbeddableStartServices } from '../types'; + +function createEditApi(servicesOverrides: Partial = {}) { + const internalApi = getLensInternalApiMock(); + const runtimeState = getLensRuntimeStateMock(); + const api = getLensApiMock(); + const services = { + ...makeEmbeddableServices(new BehaviorSubject(''), undefined, { + visOverrides: { id: 'lnsXY' }, + dataOverrides: { id: 'formBased' }, + }), + ...servicesOverrides, + }; + return initializeEditApi( + faker.string.uuid(), + runtimeState, + () => runtimeState, + internalApi, + api, + api, + () => false, // DSL based + services, + { getAppContext: () => ({ currentAppId: 'lens' }), viewMode: new BehaviorSubject('edit') } + ); +} + +describe('edit features', () => { + it('should be editable if visualize library privileges allow it', () => { + const editApi = createEditApi(); + expect(editApi.api.isEditingEnabled()).toBe(true); + }); + + it('should not be editable if visualize library privileges do not allow it', () => { + const editApi = createEditApi({ + capabilities: { + visualize: { + // cannot save + save: false, + saveQuery: true, + // cannot see the visualization + show: true, + createShortUrl: true, + }, + dashboard: { + // cannot edit in dashboard + showWriteControls: false, + }, + } as unknown as ApplicationStart['capabilities'], + }); + + expect(editApi.api.isEditingEnabled()).toBe(false); + }); +}); diff --git a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx index 81372dad339f7..deb7c6c44764c 100644 --- a/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx +++ b/x-pack/plugins/lens/public/react_embeddable/initializers/initialize_edit.tsx @@ -218,7 +218,12 @@ export function initializeEditApi( * Check everything here: user/app permissions and the current inline editing state */ isEditingEnabled: () => { - return apiHasAppContext(parentApi) && canEdit() && panelManagementApi.isEditingEnabled(); + return Boolean( + parentApi && + apiHasAppContext(parentApi) && + canEdit() && + panelManagementApi.isEditingEnabled() + ); }, getEditHref: async () => { if (!parentApi || !apiHasAppContext(parentApi)) { diff --git a/x-pack/plugins/lens/public/react_embeddable/user_messages/api.test.ts b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.test.ts index c809a679241c6..ce3658dec0dfd 100644 --- a/x-pack/plugins/lens/public/react_embeddable/user_messages/api.test.ts +++ b/x-pack/plugins/lens/public/react_embeddable/user_messages/api.test.ts @@ -47,13 +47,13 @@ function buildUserMessagesApi(metaInfo?: SharingSavedObjectProps) { const internalApi = getLensInternalApiMock(); const services = makeEmbeddableServices(new BehaviorSubject(''), undefined, { visOverrides: { id: 'lnsXY' }, - dataOverrides: { id: 'form_based' }, + dataOverrides: { id: 'formBased' }, }); // fill the context with some data internalApi.updateVisualizationContext({ activeAttributes: getLensAttributesMock({ state: { - datasourceStates: { form_based: { something: {} } }, + datasourceStates: { formBased: { something: {} } }, visualization: { activeId: 'lnsXY', state: {} }, query: { query: '', language: 'kuery' }, filters: [],