diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts index 6f9f40745322b..947b062b3a551 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.test.ts @@ -7,7 +7,10 @@ */ import { + ContactCardEmbeddable, + ContactCardEmbeddableFactory, ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, CONTACT_CARD_EMBEDDABLE, } from '@kbn/embeddable-plugin/public/lib/test_samples'; import { @@ -253,3 +256,63 @@ test('creates a control group from the control group factory and waits for it to ); expect(mockControlGroupContainer.untilInitialized).toHaveBeenCalled(); }); + +/* + * dashboard.getInput$() subscriptions are used to update: + * 1) dashboard instance searchSessionId state + * 2) child input on parent input changes + * + * Rxjs subscriptions are executed in the order that they are created. + * This test ensures that searchSessionId update subscription is created before child input subscription + * to ensure child input subscription includes updated searchSessionId. + */ +test('searchSessionId is updated prior to child embeddable parent subscription execution', async () => { + const embeddableFactory = { + create: new ContactCardEmbeddableFactory((() => null) as any, {} as any), + getDefaultInput: jest.fn().mockResolvedValue({ + timeRange: { + to: 'now', + from: 'now-15m', + }, + }), + }; + pluginServices.getServices().embeddable.getEmbeddableFactory = jest + .fn() + .mockReturnValue(embeddableFactory); + let sessionCount = 0; + pluginServices.getServices().data.search.session.start = () => { + sessionCount++; + return `searchSessionId${sessionCount}`; + }; + const dashboard = await createDashboard(embeddableId, { + searchSessionSettings: { + getSearchSessionIdFromURL: () => undefined, + removeSessionIdFromUrl: () => {}, + createSessionRestorationDataProvider: () => {}, + } as unknown as DashboardCreationOptions['searchSessionSettings'], + }); + const embeddable = await dashboard.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Bob', + }); + + expect(embeddable.getInput().searchSessionId).toBe('searchSessionId1'); + + dashboard.updateInput({ + timeRange: { + to: 'now', + from: 'now-7d', + }, + }); + + expect(sessionCount).toBeGreaterThan(1); + const embeddableInput = embeddable.getInput(); + expect((embeddableInput as any).timeRange).toEqual({ + to: 'now', + from: 'now-7d', + }); + expect(embeddableInput.searchSessionId).toBe(`searchSessionId${sessionCount}`); +}); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts index 7609a4f3eb95f..09f98f42b4d99 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/create_dashboard.ts @@ -211,6 +211,7 @@ export const createDashboard = async ( // -------------------------------------------------------------------------------------- // Set up search sessions integration. // -------------------------------------------------------------------------------------- + let initialSearchSessionId; if (searchSessionSettings) { const { sessionIdToRestore } = searchSessionSettings; @@ -223,7 +224,7 @@ export const createDashboard = async ( } const existingSession = session.getSessionId(); - const initialSearchSessionId = + initialSearchSessionId = sessionIdToRestore ?? (existingSession && incomingEmbeddable ? existingSession : session.start()); @@ -232,7 +233,6 @@ export const createDashboard = async ( creationOptions?.searchSessionSettings ); }); - initialInput.searchSessionId = initialSearchSessionId; } // -------------------------------------------------------------------------------------- @@ -278,6 +278,7 @@ export const createDashboard = async ( const dashboardContainer = new DashboardContainer( initialInput, reduxEmbeddablePackage, + initialSearchSessionId, savedObjectResult?.dashboardInput, dashboardCreationStartTime, undefined, diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts index 506083ab25386..7f59b56c228b6 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/create/search_sessions/start_dashboard_search_session_integration.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import { debounceTime, pairwise, skip } from 'rxjs/operators'; +import { pairwise, skip } from 'rxjs/operators'; import { noSearchSessionStorageCapabilityMessage } from '@kbn/data-plugin/public'; import { DashboardContainer } from '../../dashboard_container'; import { DashboardContainerInput } from '../../../../../common'; import { pluginServices } from '../../../../services/plugin_services'; -import { CHANGE_CHECK_DEBOUNCE } from '../../../../dashboard_constants'; import { DashboardCreationOptions } from '../../dashboard_container_factory'; import { getShouldRefresh } from '../../../state/diffing/dashboard_diffing_integration'; @@ -57,10 +56,10 @@ export function startDashboardSearchSessionIntegration( // listen to and compare states to determine when to launch a new session. this.getInput$() - .pipe(pairwise(), debounceTime(CHANGE_CHECK_DEBOUNCE)) - .subscribe(async (states) => { + .pipe(pairwise()) + .subscribe((states) => { const [previous, current] = states as DashboardContainerInput[]; - const shouldRefetch = await getShouldRefresh.bind(this)(previous, current); + const shouldRefetch = getShouldRefresh.bind(this)(previous, current); if (!shouldRefetch) return; const currentSearchSessionId = this.getState().explicitInput.searchSessionId; @@ -83,7 +82,7 @@ export function startDashboardSearchSessionIntegration( })(); if (updatedSearchSessionId && updatedSearchSessionId !== currentSearchSessionId) { - this.dispatch.setSearchSessionId(updatedSearchSessionId); + this.searchSessionId = updatedSearchSessionId; } }); diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx index bdce2754ba0dd..5a360446f03a8 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; +import { mockedReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public/mocks'; import { findTestSubject, nextTick } from '@kbn/test-jest-helpers'; import { I18nProvider } from '@kbn/i18n-react'; import { @@ -29,9 +30,10 @@ import { applicationServiceMock, coreMock } from '@kbn/core/public/mocks'; import { uiActionsPluginMock } from '@kbn/ui-actions-plugin/public/mocks'; import { createEditModeActionDefinition } from '@kbn/embeddable-plugin/public/lib/test_samples'; -import { buildMockDashboard, getSampleDashboardPanel } from '../../mocks'; +import { buildMockDashboard, getSampleDashboardInput, getSampleDashboardPanel } from '../../mocks'; import { pluginServices } from '../../services/plugin_services'; import { ApplicationStart } from '@kbn/core-application-browser'; +import { DashboardContainer } from './dashboard_container'; const theme = coreMock.createStart().theme; let application: ApplicationStart | undefined; @@ -171,7 +173,11 @@ test('Container view mode change propagates to new children', async () => { test('searchSessionId propagates to children', async () => { const searchSessionId1 = 'searchSessionId1'; - const container = buildMockDashboard({ searchSessionId: searchSessionId1 }); + const container = new DashboardContainer( + getSampleDashboardInput(), + mockedReduxEmbeddablePackage, + searchSessionId1 + ); const embeddable = await container.addNewEmbeddable< ContactCardEmbeddableInput, ContactCardEmbeddableOutput, @@ -181,11 +187,6 @@ test('searchSessionId propagates to children', async () => { }); expect(embeddable.getInput().searchSessionId).toBe(searchSessionId1); - - const searchSessionId2 = 'searchSessionId2'; - container.updateInput({ searchSessionId: searchSessionId2 }); - - expect(embeddable.getInput().searchSessionId).toBe(searchSessionId2); }); test('DashboardContainer in edit mode shows edit mode actions', async () => { diff --git a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx index 5b7a589afa950..6ee260dab90e8 100644 --- a/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/dashboard_container/embeddable/dashboard_container.tsx @@ -95,6 +95,8 @@ export class DashboardContainer extends Container void; private cleanupStateTools: () => void; @@ -117,6 +119,7 @@ export class DashboardContainer extends Container - ) => { - state.explicitInput.searchSessionId = action.payload; - }, - // ------------------------------------------------------------------------------ // Unsaved Changes Reducers // ------------------------------------------------------------------------------ diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts index 7f2a55044b527..fe8e18528e2c0 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_functions.ts @@ -37,7 +37,7 @@ export type DashboardDiffFunctions = { ) => boolean | Promise; }; -export const isKeyEqual = async ( +export const isKeyEqualAsync = async ( key: keyof DashboardContainerInput, diffFunctionProps: DiffFunctionProps, diffingFunctions: DashboardDiffFunctions @@ -52,6 +52,25 @@ export const isKeyEqual = async ( return fastIsEqual(diffFunctionProps.currentValue, diffFunctionProps.lastValue); }; +export const isKeyEqual = ( + key: keyof Omit, // only Panels is async + diffFunctionProps: DiffFunctionProps, + diffingFunctions: DashboardDiffFunctions +) => { + const propsAsNever = diffFunctionProps as never; // todo figure out why props has conflicting types in some constituents. + const diffingFunction = diffingFunctions[key]; + if (!diffingFunction) { + return fastIsEqual(diffFunctionProps.currentValue, diffFunctionProps.lastValue); + } + + if (diffingFunction?.prototype?.name === 'AsyncFunction') { + throw new Error( + `The function for key "${key}" is async, must use isKeyEqualAsync for asynchronous functions` + ); + } + return diffingFunction(propsAsNever); +}; + /** * A collection of functions which diff individual keys of dashboard state. If a key is missing from this list it is * diffed by the default diffing function, fastIsEqual. diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts index c0953f8bbc98a..b79eb27af3d79 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.test.ts @@ -29,14 +29,14 @@ describe('getShouldRefresh', () => { ); describe('filter changes', () => { - test('should return false when filters do not change', async () => { + test('should return false when filters do not change', () => { const lastInput = { filters: [existsFilter], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); }); - test('should return true when pinned filters change', async () => { + test('should return true when pinned filters change', () => { const pinnedFilter = pinFilter(existsFilter); const lastInput = { filters: [pinnedFilter], @@ -44,10 +44,10 @@ describe('getShouldRefresh', () => { const input = { filters: [toggleFilterNegated(pinnedFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); - test('should return false when disabled filters change', async () => { + test('should return false when disabled filters change', () => { const disabledFilter = disableFilter(existsFilter); const lastInput = { filters: [disabledFilter], @@ -55,29 +55,29 @@ describe('getShouldRefresh', () => { const input = { filters: [toggleFilterNegated(disabledFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); }); - test('should return false when pinned filter changes to unpinned', async () => { + test('should return false when pinned filter changes to unpinned', () => { const lastInput = { filters: [existsFilter], } as unknown as DashboardContainerInput; const input = { filters: [pinFilter(existsFilter)], } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(false); }); }); describe('timeRange changes', () => { - test('should return false when timeRange does not change', async () => { + test('should return false when timeRange does not change', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); }); - test('should return true when timeRange changes (timeRestore is true)', async () => { + test('should return true when timeRange changes (timeRestore is true)', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, timeRestore: true, @@ -86,10 +86,10 @@ describe('getShouldRefresh', () => { timeRange: { from: 'now-30m', to: 'now' }, timeRestore: true, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); - test('should return true when timeRange changes (timeRestore is false)', async () => { + test('should return true when timeRange changes (timeRestore is false)', () => { const lastInput = { timeRange: { from: 'now-15m', to: 'now' }, timeRestore: false, @@ -98,7 +98,26 @@ describe('getShouldRefresh', () => { timeRange: { from: 'now-30m', to: 'now' }, timeRestore: false, } as unknown as DashboardContainerInput; - expect(await getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); + }); + }); + + describe('key without custom diffing function (syncColors)', () => { + test('should return false when syncColors do not change', () => { + const lastInput = { + syncColors: false, + } as unknown as DashboardContainerInput; + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, lastInput)).toBe(false); + }); + + test('should return true when syncColors change', () => { + const lastInput = { + syncColors: false, + } as unknown as DashboardContainerInput; + const input = { + syncColors: true, + } as unknown as DashboardContainerInput; + expect(getShouldRefresh.bind(dashboardContainerMock)(lastInput, input)).toBe(true); }); }); }); diff --git a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts index 02399543750cd..b1ee66fd02c75 100644 --- a/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts +++ b/src/plugins/dashboard/public/dashboard_container/state/diffing/dashboard_diffing_integration.ts @@ -9,13 +9,13 @@ import { omit } from 'lodash'; import { AnyAction, Middleware } from 'redux'; import { debounceTime, Observable, Subject, switchMap } from 'rxjs'; -import { DashboardContainerInput } from '../../../../common'; -import type { DashboardDiffFunctions } from './dashboard_diffing_functions'; import { isKeyEqual, + isKeyEqualAsync, shouldRefreshDiffingFunctions, unsavedChangesDiffingFunctions, } from './dashboard_diffing_functions'; +import { DashboardContainerInput } from '../../../../common'; import { pluginServices } from '../../../services/plugin_services'; import { DashboardContainer, DashboardCreationOptions } from '../..'; import { CHANGE_CHECK_DEBOUNCE } from '../../../dashboard_constants'; @@ -29,7 +29,6 @@ import { dashboardContainerReducers } from '../dashboard_container_reducers'; export const reducersToIgnore: Array = [ 'setTimeslice', 'setFullScreenMode', - 'setSearchSessionId', 'setExpandedPanelId', 'setHasUnsavedChanges', ]; @@ -40,7 +39,6 @@ export const reducersToIgnore: Array = const keysToOmitFromSessionStorage: Array = [ 'lastReloadRequestTime', 'executionContext', - 'searchSessionId', 'timeslice', 'id', @@ -55,7 +53,6 @@ const keysToOmitFromSessionStorage: Array = [ export const keysNotConsideredUnsavedChanges: Array = [ 'lastReloadRequestTime', 'executionContext', - 'searchSessionId', 'timeslice', 'viewMode', 'id', @@ -64,7 +61,7 @@ export const keysNotConsideredUnsavedChanges: Array = [ +const sessionChangeKeys: Array> = [ 'query', 'filters', 'timeRange', @@ -138,42 +135,17 @@ export async function getUnsavedChanges( const allKeys = [...new Set([...Object.keys(lastInput), ...Object.keys(input)])] as Array< keyof DashboardContainerInput >; - return await getInputChanges(this, lastInput, input, allKeys, unsavedChangesDiffingFunctions); -} - -export async function getShouldRefresh( - this: DashboardContainer, - lastInput: DashboardContainerInput, - input: DashboardContainerInput -): Promise { - const inputChanges = await getInputChanges( - this, - lastInput, - input, - refetchKeys, - shouldRefreshDiffingFunctions - ); - return Object.keys(inputChanges).length > 0; -} - -async function getInputChanges( - container: DashboardContainer, - lastInput: DashboardContainerInput, - input: DashboardContainerInput, - keys: Array, - diffingFunctions: DashboardDiffFunctions -): Promise> { - const keyComparePromises = keys.map( + const keyComparePromises = allKeys.map( (key) => new Promise<{ key: keyof DashboardContainerInput; isEqual: boolean }>((resolve) => { if (input[key] === undefined && lastInput[key] === undefined) { resolve({ key, isEqual: true }); } - isKeyEqual( + isKeyEqualAsync( key, { - container, + container: this, currentValue: input[key], currentInput: input, @@ -181,7 +153,7 @@ async function getInputChanges( lastValue: lastInput[key], lastInput, }, - diffingFunctions + unsavedChangesDiffingFunctions ).then((isEqual) => resolve({ key, isEqual })); }) ); @@ -195,6 +167,34 @@ async function getInputChanges( return inputChanges; } +export function getShouldRefresh( + this: DashboardContainer, + lastInput: DashboardContainerInput, + input: DashboardContainerInput +): boolean { + for (const key of sessionChangeKeys) { + if (input[key] === undefined && lastInput[key] === undefined) { + continue; + } + if ( + !isKeyEqual( + key, + { + container: this, + currentValue: input[key], + currentInput: input, + lastValue: lastInput[key], + lastInput, + }, + shouldRefreshDiffingFunctions + ) + ) { + return true; + } + } + return false; +} + function updateUnsavedChangesState( this: DashboardContainer, unsavedChanges: Partial diff --git a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx index b8b9fb1f4795f..68d9df23bb612 100644 --- a/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx +++ b/src/plugins/embeddable/public/lib/filterable_embeddable/should_fetch.tsx @@ -27,19 +27,24 @@ export function shouldFetch$< return updated$.pipe(map(() => getInput())).pipe( // wrapping distinctUntilChanged with startWith and skip to prime distinctUntilChanged with an initial input value. startWith(getInput()), - distinctUntilChanged((a: TFilterableEmbeddableInput, b: TFilterableEmbeddableInput) => { - // Only need to diff searchSessionId when container uses search sessions because - // searchSessionId changes with any filter, query, or time changes - if (a.searchSessionId !== undefined || b.searchSessionId !== undefined) { - return a.searchSessionId === b.searchSessionId; - } + distinctUntilChanged( + (previous: TFilterableEmbeddableInput, current: TFilterableEmbeddableInput) => { + if ( + !fastIsEqual( + [previous.searchSessionId, previous.query, previous.timeRange, previous.timeslice], + [current.searchSessionId, current.query, current.timeRange, current.timeslice] + ) + ) { + return false; + } - if (!fastIsEqual([a.query, a.timeRange, a.timeslice], [b.query, b.timeRange, b.timeslice])) { - return false; + return onlyDisabledFiltersChanged( + previous.filters, + current.filters, + shouldRefreshFilterCompareOptions + ); } - - return onlyDisabledFiltersChanged(a.filters, b.filters, shouldRefreshFilterCompareOptions); - }), + ), skip(1) ); } diff --git a/test/plugin_functional/test_suites/data_plugin/session.ts b/test/plugin_functional/test_suites/data_plugin/session.ts index 6c485a76db32e..469e6f992e79f 100644 --- a/test/plugin_functional/test_suites/data_plugin/session.ts +++ b/test/plugin_functional/test_suites/data_plugin/session.ts @@ -106,7 +106,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('starts a session on filter change', async () => { - await filterBar.removeAllFilters(); + await filterBar.removeFilter('animal'); const sessionIds = await getSessionIds(); expect(sessionIds.length).to.be(1); }); diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index c127e9f1130d3..5e48e1f46dd63 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -441,11 +441,6 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(1); - embeddable.updateInput({ - filters: [{ meta: { alias: 'test', negate: false, disabled: false } }], - }); - await new Promise((resolve) => setTimeout(resolve, 0)); - embeddable.updateInput({ searchSessionId: 'nextSession', }); diff --git a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts index ee07446783603..2295c90d60c65 100644 --- a/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts +++ b/x-pack/test/functional/apps/dashboard/group2/panel_time_range.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')); + expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); await PageObjects.dashboard.clickQuickSave(); }); @@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')); + expect(await testSubjects.exists('xyVisChart')).to.be(true); }); }); @@ -74,7 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')); + expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); await PageObjects.dashboard.clickQuickSave(); }); @@ -84,7 +84,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardCustomizePanel.clickSaveButton(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')); + expect(await testSubjects.exists('xyVisChart')).to.be(true); }); });