From 8875ce2a9ad6633553889f382e4afea3ca66107e Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 26 Aug 2021 12:42:45 -0400 Subject: [PATCH] [Lens] Inspect flyout should be available in editor mode. (#109656) (#110243) * [Lens] Inspect flyout should be available in editor mode. * fix typo * add test * add functional tests for inspector * toMatchInlineSnapshot -> toMatchSnapshot Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alexey Antonov --- ...s-public.createdefaultinspectoradapters.md | 11 +++ ...ibana-plugin-plugins-expressions-public.md | 1 + .../expressions/common/execution/execution.ts | 11 +-- .../util/create_default_inspector_adapters.ts | 19 +++++ src/plugins/expressions/common/util/index.ts | 1 + src/plugins/expressions/public/index.ts | 1 + src/plugins/expressions/public/public.api.md | 7 +- test/functional/services/inspector.ts | 4 +- .../__snapshots__/app.test.tsx.snap | 70 +++++++++++++++++++ .../lens/public/app_plugin/app.test.tsx | 42 ++++++++--- x-pack/plugins/lens/public/app_plugin/app.tsx | 14 +++- .../lens/public/app_plugin/lens_top_nav.tsx | 15 ++++ .../lens/public/app_plugin/mounter.tsx | 3 +- .../plugins/lens/public/app_plugin/types.ts | 6 +- .../editor_frame/editor_frame.test.tsx | 3 + .../editor_frame/editor_frame.tsx | 3 + .../workspace_panel/workspace_panel.test.tsx | 3 + .../workspace_panel/workspace_panel.tsx | 26 +++---- .../public/editor_frame_service/service.tsx | 3 +- .../public/embeddable/embeddable.test.tsx | 19 +++++ .../lens/public/embeddable/embeddable.tsx | 24 +++---- .../public/embeddable/embeddable_factory.ts | 4 ++ .../public/embeddable/expression_wrapper.tsx | 4 ++ .../lens/public/lens_inspector_service.ts | 23 ++++++ x-pack/plugins/lens/public/mocks.tsx | 2 + x-pack/plugins/lens/public/plugin.ts | 1 + x-pack/plugins/lens/public/types.ts | 39 ++++++----- x-pack/test/functional/apps/lens/index.ts | 1 + x-pack/test/functional/apps/lens/inspector.ts | 59 ++++++++++++++++ 29 files changed, 351 insertions(+), 68 deletions(-) create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md create mode 100644 src/plugins/expressions/common/util/create_default_inspector_adapters.ts create mode 100644 x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap create mode 100644 x-pack/plugins/lens/public/lens_inspector_service.ts create mode 100644 x-pack/test/functional/apps/lens/inspector.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md new file mode 100644 index 000000000000..b4f7fa197f82 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [createDefaultInspectorAdapters](./kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md) + +## createDefaultInspectorAdapters variable + +Signature: + +```typescript +createDefaultInspectorAdapters: () => DefaultInspectorAdapters +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md index e3eb7a34175e..42990930d0bd 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.md @@ -84,6 +84,7 @@ | Variable | Description | | --- | --- | +| [createDefaultInspectorAdapters](./kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md) | | | [ReactExpressionRenderer](./kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md) | | ## Type Aliases diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index 68efc15b2ed5..75b6c9e60613 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -25,7 +25,7 @@ import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; import { abortSignalToPromise, now } from '../../../kibana_utils/common'; -import { RequestAdapter, Adapters } from '../../../inspector/common'; +import { Adapters } from '../../../inspector/common'; import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error'; import { ExpressionAstArgument, @@ -42,8 +42,7 @@ import { ExpressionFunction } from '../expression_functions'; import { getByAlias } from '../util/get_by_alias'; import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; -import { TablesAdapter } from '../util/tables_adapter'; -import { ExpressionsInspectorAdapter } from '../util/expressions_inspector_adapter'; +import { createDefaultInspectorAdapters } from '../util/create_default_inspector_adapters'; /** * The result returned after an expression function execution. @@ -90,12 +89,6 @@ export interface ExecutionParams { params: ExpressionExecutionParams; } -const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({ - requests: new RequestAdapter(), - tables: new TablesAdapter(), - expression: new ExpressionsInspectorAdapter(), -}); - export class Execution< Input = unknown, Output = unknown, diff --git a/src/plugins/expressions/common/util/create_default_inspector_adapters.ts b/src/plugins/expressions/common/util/create_default_inspector_adapters.ts new file mode 100644 index 000000000000..693a9d6e8cf7 --- /dev/null +++ b/src/plugins/expressions/common/util/create_default_inspector_adapters.ts @@ -0,0 +1,19 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RequestAdapter } from '../../../inspector/common'; +import { TablesAdapter } from './tables_adapter'; +import { ExpressionsInspectorAdapter } from './expressions_inspector_adapter'; + +import type { DefaultInspectorAdapters } from '../execution'; + +export const createDefaultInspectorAdapters = (): DefaultInspectorAdapters => ({ + requests: new RequestAdapter(), + tables: new TablesAdapter(), + expression: new ExpressionsInspectorAdapter(), +}); diff --git a/src/plugins/expressions/common/util/index.ts b/src/plugins/expressions/common/util/index.ts index 470dfc3c2d43..110dcaec282f 100644 --- a/src/plugins/expressions/common/util/index.ts +++ b/src/plugins/expressions/common/util/index.ts @@ -11,3 +11,4 @@ export * from './get_by_alias'; export * from './tables_adapter'; export * from './expressions_inspector_adapter'; export * from './test_utils'; +export * from './create_default_inspector_adapters'; diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index b73406e51676..79319f1f6f4c 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -108,4 +108,5 @@ export { ExpressionsServiceStart, TablesAdapter, ExpressionsInspectorAdapter, + createDefaultInspectorAdapters, } from '../common'; diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 3126af02286c..1a5846f71acb 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -55,6 +55,12 @@ initialArgs: { [K in keyof FunctionArgs]: FunctionArgs[K] | ExpressionAstExpressionBuilder | ExpressionAstExpressionBuilder[] | ExpressionAstExpression | ExpressionAstExpression[]; }): ExpressionAstFunctionBuilder; +// Warning: (ae-forgotten-export) The symbol "DefaultInspectorAdapters" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "createDefaultInspectorAdapters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const createDefaultInspectorAdapters: () => DefaultInspectorAdapters; + // Warning: (ae-missing-release-tag) "Datatable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -95,7 +101,6 @@ export type DatatableRow = Record; // Warning: (ae-forgotten-export) The symbol "Adapters" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ExpressionExecutionParams" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "DefaultInspectorAdapters" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Execution" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/test/functional/services/inspector.ts b/test/functional/services/inspector.ts index dc46db458501..5364dbebe904 100644 --- a/test/functional/services/inspector.ts +++ b/test/functional/services/inspector.ts @@ -45,12 +45,12 @@ export class InspectorService extends FtrService { /** * Opens inspector panel */ - public async open(): Promise { + public async open(openButton: string = 'openInspectorButton'): Promise { this.log.debug('Inspector.open'); const isOpen = await this.testSubjects.exists('inspectorPanel'); if (!isOpen) { await this.retry.try(async () => { - await this.testSubjects.click('openInspectorButton'); + await this.testSubjects.click(openButton); await this.testSubjects.exists('inspectorPanel'); }); } diff --git a/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap b/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap new file mode 100644 index 000000000000..51adf7737fd4 --- /dev/null +++ b/x-pack/plugins/lens/public/app_plugin/__snapshots__/app.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Lens App renders the editor frame 1`] = ` +Array [ + Array [ + Object { + "lensInspector": Object { + "adapters": Object { + "expression": ExpressionsInspectorAdapter { + "_ast": Object {}, + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + Symbol(kCapture): false, + }, + "requests": RequestAdapter { + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + "requests": Map {}, + Symbol(kCapture): false, + }, + "tables": TablesAdapter { + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + "_tables": Object {}, + Symbol(kCapture): false, + }, + }, + "inspect": [Function], + }, + "showNoDataPopover": [Function], + }, + Object {}, + ], + Array [ + Object { + "lensInspector": Object { + "adapters": Object { + "expression": ExpressionsInspectorAdapter { + "_ast": Object {}, + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + Symbol(kCapture): false, + }, + "requests": RequestAdapter { + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + "requests": Map {}, + Symbol(kCapture): false, + }, + "tables": TablesAdapter { + "_events": Object {}, + "_eventsCount": 0, + "_maxListeners": undefined, + "_tables": Object {}, + Symbol(kCapture): false, + }, + }, + "inspect": [Function], + }, + "showNoDataPopover": [Function], + }, + Object {}, + ], +] +`; diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index a10b0f436010..22da62c616c4 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -140,16 +140,7 @@ describe('Lens App', () => { it('renders the editor frame', async () => { const { frame } = await mountWith({}); - expect(frame.EditorFrameContainer.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "showNoDataPopover": [Function], - }, - Object {}, - ], - ] - `); + expect(frame.EditorFrameContainer.mock.calls).toMatchSnapshot(); }); it('updates global filters with store state', async () => { @@ -772,6 +763,37 @@ describe('Lens App', () => { }); }); + describe('inspector', () => { + function getButton(inst: ReactWrapper): TopNavMenuData { + return (inst + .find('[data-test-subj="lnsApp_topNav"]') + .prop('config') as TopNavMenuData[]).find( + (button) => button.testId === 'lnsApp_inspectButton' + )!; + } + + async function runInspect(inst: ReactWrapper) { + await getButton(inst).run(inst.getDOMNode()); + await inst.update(); + } + + it('inspector button should be available', async () => { + const { instance } = await mountWith({ preloadedState: { isSaveable: true } }); + const button = getButton(instance); + + expect(button.disableButton).toEqual(false); + }); + + it('should open inspect panel', async () => { + const services = makeDefaultServices(sessionIdSubject); + const { instance } = await mountWith({ services, preloadedState: { isSaveable: true } }); + + await runInspect(instance); + + expect(services.inspector.open).toHaveBeenCalledTimes(1); + }); + }); + describe('query bar state management', () => { it('uses the default time and query language settings', async () => { const { lensStore, services } = await mountWith({}); diff --git a/x-pack/plugins/lens/public/app_plugin/app.tsx b/x-pack/plugins/lens/public/app_plugin/app.tsx index 4b956768265e..63cb7d300254 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.tsx @@ -23,6 +23,7 @@ import { LensTopNavMenu } from './lens_top_nav'; import { LensByReferenceInput } from '../embeddable'; import { EditorFrameInstance } from '../types'; import { Document } from '../persistence/saved_object_store'; + import { setState, useLensSelector, @@ -36,6 +37,7 @@ import { getLastKnownDocWithoutPinnedFilters, runSaveLensVisualization, } from './save_modal_container'; +import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; export type SaveProps = Omit & { returnToOrigin: boolean; @@ -63,11 +65,11 @@ export function App({ data, chrome, uiSettings, + inspector, application, notifications, savedObjectsTagging, getOriginatingAppName, - // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag, } = lensAppServices; @@ -95,6 +97,8 @@ export function App({ const [isSaveModalVisible, setIsSaveModalVisible] = useState(false); const [lastKnownDoc, setLastKnownDoc] = useState(undefined); + const lensInspector = getLensInspectorService(inspector); + useEffect(() => { if (currentDoc) { setLastKnownDoc(currentDoc); @@ -267,11 +271,13 @@ export function App({ indicateNoData={indicateNoData} datasourceMap={datasourceMap} title={persistedDoc?.title} + lensInspector={lensInspector} /> {(!isLoading || persistedDoc) && ( )} @@ -308,10 +314,14 @@ export function App({ const MemoizedEditorFrameWrapper = React.memo(function EditorFrameWrapper({ editorFrame, showNoDataPopover, + lensInspector, }: { editorFrame: EditorFrameInstance; + lensInspector: LensInspector; showNoDataPopover: () => void; }) { const { EditorFrameContainer } = editorFrame; - return ; + return ( + + ); }); diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index c4c2a7523e58..332d404c6375 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -71,6 +71,18 @@ function getLensTopNavConfig(options: { defaultMessage: 'Save', }); + topNavMenu.push({ + label: i18n.translate('xpack.lens.app.inspect', { + defaultMessage: 'Inspect', + }), + run: actions.inspect, + testId: 'lnsApp_inspectButton', + description: i18n.translate('xpack.lens.app.inspectAriaLabel', { + defaultMessage: 'inspect', + }), + disableButton: false, + }); + topNavMenu.push({ label: i18n.translate('xpack.lens.app.downloadCSV', { defaultMessage: 'Download as CSV', @@ -131,6 +143,7 @@ export const LensTopNavMenu = ({ setHeaderActionMenu, initialInput, indicateNoData, + lensInspector, setIsSaveModalVisible, getIsByValueMode, runSave, @@ -242,6 +255,7 @@ export const LensTopNavMenu = ({ }, }, actions: { + inspect: lensInspector.inspect, exportToCSV: () => { if (!activeData) { return; @@ -321,6 +335,7 @@ export const LensTopNavMenu = ({ setIsSaveModalVisible, uiSettings, unsavedTitle, + lensInspector.inspect, ] ); diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index 6bbc1284a0f1..15f72bed582e 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -48,6 +48,7 @@ export async function getLensServices( ): Promise { const { data, + inspector, navigation, embeddable, savedObjectsTagging, @@ -62,6 +63,7 @@ export async function getLensServices( return { data, storage, + inspector, navigation, fieldFormats, stateTransfer, @@ -82,7 +84,6 @@ export async function getLensServices( ? stateTransfer?.getAppNameFromId(embeddableEditorIncomingState.originatingApp) : undefined; }, - // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag: startDependencies.dashboard.dashboardFeatureFlagConfig, }; diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts index b6530b90ac3f..4ccf441799b1 100644 --- a/x-pack/plugins/lens/public/app_plugin/types.ts +++ b/x-pack/plugins/lens/public/app_plugin/types.ts @@ -20,6 +20,7 @@ import type { import type { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import type { UsageCollectionStart } from '../../../../../src/plugins/usage_collection/public'; import type { DashboardStart } from '../../../../../src/plugins/dashboard/public'; +import type { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; import type { LensEmbeddableInput } from '../embeddable/embeddable'; import type { NavigationPublicPluginStart } from '../../../../../src/plugins/navigation/public'; import type { LensAttributeService } from '../lens_attribute_service'; @@ -37,6 +38,7 @@ import type { import type { DatasourceMap, EditorFrameInstance, VisualizationMap } from '../types'; import type { PresentationUtilPluginStart } from '../../../../../src/plugins/presentation_util/public'; import type { FieldFormatsStart } from '../../../../../src/plugins/field_formats/public'; +import type { LensInspector } from '../lens_inspector_service'; export interface RedirectToOriginProps { input?: LensEmbeddableInput; @@ -86,6 +88,7 @@ export interface LensTopNavMenuProps { runSave: RunSave; datasourceMap: DatasourceMap; title?: string; + lensInspector: LensInspector; } export interface HistoryLocationState { @@ -101,6 +104,7 @@ export interface LensAppServices { dashboard: DashboardStart; fieldFormats: FieldFormatsStart; data: DataPublicPluginStart; + inspector: InspectorStart; uiSettings: IUiSettingsClient; application: ApplicationStart; notifications: NotificationsStart; @@ -112,7 +116,6 @@ export interface LensAppServices { savedObjectsTagging?: SavedObjectTaggingPluginStart; getOriginatingAppName: () => string | undefined; presentationUtil: PresentationUtilPluginStart; - // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag: DashboardFeatureFlagConfig; } @@ -122,6 +125,7 @@ export interface LensTopNavTooltips { } export interface LensTopNavActions { + inspect: () => void; saveAndReturn: () => void; showSaveModal: () => void; cancel: () => void; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index 5d1652b8e1f7..fff9fe1372a2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -39,6 +39,7 @@ import { DatasourceMock, createExpressionRendererMock, } from '../../mocks'; +import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; import { DragDrop } from '../../drag_drop'; import { uiActionsPluginMock } from '../../../../../../src/plugins/ui_actions/public/mocks'; @@ -46,6 +47,7 @@ import { chartPluginMock } from '../../../../../../src/plugins/charts/public/moc import { expressionsPluginMock } from '../../../../../../src/plugins/expressions/public/mocks'; import { mockDataPlugin, mountWithProvider } from '../../mocks'; import { setState } from '../../state_management'; +import { getLensInspectorService } from '../../lens_inspector_service'; function generateSuggestion(state = {}): DatasourceSuggestion { return { @@ -79,6 +81,7 @@ function getDefaultProps() { charts: chartPluginMock.createStartContract(), }, palettes: chartPluginMock.createPaletteRegistry(), + lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()), showNoDataPopover: jest.fn(), }; return defaultProps; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index 0813d0deef02..7700bc708fc1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -27,6 +27,7 @@ import { selectDatasourceStates, selectVisualization, } from '../../state_management'; +import type { LensInspector } from '../../lens_inspector_service'; export interface EditorFrameProps { datasourceMap: DatasourceMap; @@ -35,6 +36,7 @@ export interface EditorFrameProps { core: CoreStart; plugins: EditorFrameStartPlugins; showNoDataPopover: () => void; + lensInspector: LensInspector; } export function EditorFrame(props: EditorFrameProps) { @@ -108,6 +110,7 @@ export function EditorFrame(props: EditorFrameProps) { core={props.core} plugins={props.plugins} ExpressionRenderer={props.ExpressionRenderer} + lensInspector={props.lensInspector} datasourceMap={datasourceMap} visualizationMap={visualizationMap} framePublicAPI={framePublicAPI} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index e687c3ea4442..3c88e92afd44 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -34,6 +34,8 @@ import { uiActionsPluginMock } from '../../../../../../../src/plugins/ui_actions import { TriggerContract } from '../../../../../../../src/plugins/ui_actions/public/triggers'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public/embeddable'; import { LensRootStore, setState } from '../../../state_management'; +import { getLensInspectorService } from '../../../lens_inspector_service'; +import { inspectorPluginMock } from '../../../../../../../src/plugins/inspector/public/mocks'; const defaultPermissions: Record>> = { navLinks: { management: true }, @@ -59,6 +61,7 @@ const defaultProps = { data: mockDataPlugin(), }, getSuggestionForField: () => undefined, + lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()), toggleFullscreen: jest.fn(), }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 8b49c72b3cff..31705d6b9293 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -21,14 +21,10 @@ import { EuiButton, EuiSpacer, } from '@elastic/eui'; -import { CoreStart, ApplicationStart } from 'kibana/public'; -import { - DataPublicPluginStart, - ExecutionContextSearch, - TimefilterContract, -} from 'src/plugins/data/public'; +import type { CoreStart, ApplicationStart } from 'kibana/public'; +import type { DataPublicPluginStart, ExecutionContextSearch } from 'src/plugins/data/public'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; -import { +import type { ExpressionRendererEvent, ExpressionRenderError, ReactExpressionRendererType, @@ -67,6 +63,7 @@ import { selectActiveDatasourceId, selectSearchSessionId, } from '../../../state_management'; +import type { LensInspector } from '../../../lens_inspector_service'; export interface WorkspacePanelProps { visualizationMap: VisualizationMap; @@ -76,6 +73,7 @@ export interface WorkspacePanelProps { core: CoreStart; plugins: { uiActions?: UiActionsStart; data: DataPublicPluginStart }; getSuggestionForField: (field: DragDropIdentifier) => Suggestion | undefined; + lensInspector: LensInspector; } interface WorkspaceState { @@ -124,6 +122,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ plugins, ExpressionRenderer: ExpressionRendererComponent, suggestionForDraggedField, + lensInspector, }: Omit & { suggestionForDraggedField: Suggestion | undefined; }) { @@ -335,7 +334,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ void; setLocalState: (dispatch: (prevState: WorkspaceState) => WorkspaceState) => void; localState: WorkspaceState & { @@ -443,9 +442,9 @@ export const VisualizationWrapper = ({ const dispatchLens = useLensDispatch(); const onData$ = useCallback( - (data: unknown, inspectorAdapters?: Partial) => { - if (inspectorAdapters && inspectorAdapters.tables) { - dispatchLens(onActiveDataChange({ ...inspectorAdapters.tables.tables })); + (data: unknown, adapters?: Partial) => { + if (adapters && adapters.tables) { + dispatchLens(onActiveDataChange({ ...adapters.tables.tables })); } }, [dispatchLens] @@ -634,6 +633,7 @@ export const VisualizationWrapper = ({ searchSessionId={searchSessionId} onEvent={onEvent} onData$={onData$} + inspectorAdapters={lensInspector.adapters} renderMode="edit" renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => { const errorsFromRequest = getOriginalRequestErrorMessages(error); diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index b3574f19b5ca..e1b1c637fa24 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -107,13 +107,14 @@ export class EditorFrameService { const { EditorFrame } = await import('../async_services'); return { - EditorFrameContainer: ({ showNoDataPopover }) => { + EditorFrameContainer: ({ showNoDataPopover, lensInspector }) => { return (
({ isAvailable: false, @@ -116,6 +117,7 @@ describe('embeddable', () => { canSaveDashboards: true, canSaveVisualizations: true, }, + inspector: inspectorPluginMock.createStartContract(), getTrigger, documentToExpression: () => Promise.resolve({ @@ -154,6 +156,7 @@ describe('embeddable', () => { expressionRenderer, basePath, indexPatternService: {} as IndexPatternsContract, + inspector: inspectorPluginMock.createStartContract(), capabilities: { canSaveDashboards: true, canSaveVisualizations: true }, getTrigger, documentToExpression: () => @@ -193,6 +196,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -234,6 +238,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: ({ get: (id: string) => Promise.resolve({ id }), } as unknown) as IndexPatternsContract, @@ -274,6 +279,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -318,6 +324,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, canSaveVisualizations: true }, getTrigger, @@ -363,6 +370,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -409,6 +417,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -462,6 +471,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -515,6 +525,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -567,6 +578,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: ({ get: jest.fn() } as unknown) as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -608,6 +620,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -649,6 +662,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -690,6 +704,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -746,6 +761,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -818,6 +834,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -865,6 +882,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, @@ -912,6 +930,7 @@ describe('embeddable', () => { attributeService, expressionRenderer, basePath, + inspector: inspectorPluginMock.createStartContract(), indexPatternService: {} as IndexPatternsContract, capabilities: { canSaveDashboards: true, diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index cae071a61c17..172274b1f90b 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -8,7 +8,7 @@ import { isEqual, uniqBy } from 'lodash'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { +import type { ExecutionContextSearch, Filter, Query, @@ -16,11 +16,12 @@ import { TimeRange, IndexPattern, } from 'src/plugins/data/public'; -import { PaletteOutput } from 'src/plugins/charts/public'; +import type { PaletteOutput } from 'src/plugins/charts/public'; +import type { Start as InspectorStart } from 'src/plugins/inspector/public'; import { Subscription } from 'rxjs'; import { toExpression, Ast } from '@kbn/interpreter/common'; -import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; +import { RenderMode } from 'src/plugins/expressions'; import { map, distinctUntilChanged, skip } from 'rxjs/operators'; import fastIsEqual from 'fast-deep-equal'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; @@ -40,7 +41,7 @@ import { ReferenceOrValueEmbeddable, } from '../../../../../src/plugins/embeddable/public'; import { Document, injectFilterReferences } from '../persistence'; -import { ExpressionWrapper } from './expression_wrapper'; +import { ExpressionWrapper, ExpressionWrapperProps } from './expression_wrapper'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { isLensBrushEvent, @@ -56,6 +57,7 @@ import { getEditPath, DOC_TYPE, PLUGIN_ID } from '../../common'; import { IBasePath } from '../../../../../src/core/public'; import { LensAttributeService } from '../lens_attribute_service'; import type { ErrorMessage } from '../editor_frame_service/types'; +import { getLensInspectorService, LensInspector } from '../lens_inspector_service'; export type LensSavedObjectAttributes = Omit; @@ -93,6 +95,7 @@ export interface LensEmbeddableDeps { expressionRenderer: ReactExpressionRendererType; timefilter: TimefilterContract; basePath: IBasePath; + inspector: InspectorStart; getTrigger?: UiActionsStart['getTrigger'] | undefined; getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; capabilities: { canSaveVisualizations: boolean; canSaveDashboards: boolean }; @@ -112,10 +115,10 @@ export class Embeddable private domNode: HTMLElement | Element | undefined; private subscription: Subscription; private isInitialized = false; - private activeData: Partial | undefined; private errors: ErrorMessage[] | undefined; private inputReloadSubscriptions: Subscription[]; private isDestroyed?: boolean; + private lensInspector: LensInspector; private logError(type: 'runtime' | 'validation') { this.deps.usageCollection?.reportUiCounter( @@ -144,7 +147,7 @@ export class Embeddable }, parent ); - + this.lensInspector = getLensInspectorService(deps.inspector); this.expressionRenderer = deps.expressionRenderer; this.initializeSavedVis(initialInput).then(() => this.onContainerStateChanged(initialInput)); this.subscription = this.getUpdated$().subscribe(() => @@ -246,7 +249,7 @@ export class Embeddable } public getInspectorAdapters() { - return this.activeData; + return this.lensInspector.adapters; } async initializeSavedVis(input: LensEmbeddableInput) { @@ -300,11 +303,7 @@ export class Embeddable return isDirty; } - private updateActiveData = ( - data: unknown, - inspectorAdapters?: Partial | undefined - ) => { - this.activeData = inspectorAdapters; + private updateActiveData: ExpressionWrapperProps['onData$'] = () => { if (this.input.onLoad) { // once onData$ is get's called from expression renderer, loading becomes false this.input.onLoad(false); @@ -341,6 +340,7 @@ export class Embeddable ExpressionRenderer={this.expressionRenderer} expression={this.expression || null} errors={this.errors} + lensInspector={this.lensInspector} searchContext={this.getMergedSearchContext()} variables={input.palette ? { theme: { palette: input.palette } } : {}} searchSessionId={this.externalSearchContext.searchSessionId} diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts index ca9d830816db..5620f053cebf 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/embeddable/embeddable_factory.ts @@ -18,6 +18,7 @@ import { } from '../../../../../src/plugins/embeddable/public'; import { LensByReferenceInput, LensEmbeddableInput } from './embeddable'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; import { Document } from '../persistence/saved_object_store'; import { LensAttributeService } from '../lens_attribute_service'; import { DOC_TYPE } from '../../common'; @@ -27,6 +28,7 @@ import { extract, inject } from '../../common/embeddable_factory'; export interface LensEmbeddableStartServices { timefilter: TimefilterContract; coreHttp: HttpSetup; + inspector: InspectorStart; attributeService: LensAttributeService; capabilities: RecursiveReadonly; expressionRenderer: ReactExpressionRendererType; @@ -87,6 +89,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { indexPatternService, capabilities, usageCollection, + inspector, } = await this.getStartServices(); const { Embeddable } = await import('../async_services'); @@ -96,6 +99,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { attributeService, indexPatternService, timefilter, + inspector, expressionRenderer, basePath: coreHttp.basePath, getTrigger: uiActions?.getTrigger, diff --git a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx index fc6fcee9428b..d57e1c450fea 100644 --- a/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/embeddable/expression_wrapper.tsx @@ -20,6 +20,7 @@ import { DefaultInspectorAdapters, RenderMode } from 'src/plugins/expressions'; import classNames from 'classnames'; import { getOriginalRequestErrorMessages } from '../editor_frame_service/error_helper'; import { ErrorMessage } from '../editor_frame_service/types'; +import { LensInspector } from '../lens_inspector_service'; export interface ExpressionWrapperProps { ExpressionRenderer: ReactExpressionRendererType; @@ -41,6 +42,7 @@ export interface ExpressionWrapperProps { canEdit: boolean; onRuntimeError: () => void; executionContext?: KibanaExecutionContext; + lensInspector: LensInspector; } interface VisualizationErrorProps { @@ -111,6 +113,7 @@ export function ExpressionWrapper({ canEdit, onRuntimeError, executionContext, + lensInspector, }: ExpressionWrapperProps) { return ( @@ -126,6 +129,7 @@ export function ExpressionWrapper({ searchContext={searchContext} searchSessionId={searchSessionId} onData$={onData$} + inspectorAdapters={lensInspector.adapters} renderMode={renderMode} syncColors={syncColors} executionContext={executionContext} diff --git a/x-pack/plugins/lens/public/lens_inspector_service.ts b/x-pack/plugins/lens/public/lens_inspector_service.ts new file mode 100644 index 000000000000..6266e7c21f79 --- /dev/null +++ b/x-pack/plugins/lens/public/lens_inspector_service.ts @@ -0,0 +1,23 @@ +/* + * 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 type { + Adapters, + Start as InspectorStartContract, +} from '../../../../src/plugins/inspector/public'; + +import { createDefaultInspectorAdapters } from '../../../../src/plugins/expressions/public'; + +export const getLensInspectorService = (inspector: InspectorStartContract) => { + const adapters: Adapters = createDefaultInspectorAdapters(); + return { + adapters, + inspect: () => inspector.open(adapters), + }; +}; + +export type LensInspector = ReturnType; diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx index d4c058c12463..a88831dda7ba 100644 --- a/x-pack/plugins/lens/public/mocks.tsx +++ b/x-pack/plugins/lens/public/mocks.tsx @@ -23,6 +23,7 @@ import { navigationPluginMock } from '../../../../src/plugins/navigation/public/ import { LensAppServices } from './app_plugin/types'; import { DOC_TYPE, layerTypes } from '../common'; import { DataPublicPluginStart, esFilters, UI_SETTINGS } from '../../../../src/plugins/data/public'; +import { inspectorPluginMock } from '../../../../src/plugins/inspector/public/mocks'; import { dashboardPluginMock } from '../../../../src/plugins/dashboard/public/mocks'; import type { LensByValueInput, @@ -378,6 +379,7 @@ export function makeDefaultServices( navigation: navigationStartMock, notifications: core.notifications, attributeService: makeAttributeService(), + inspector: inspectorPluginMock.createStartContract(), dashboard: dashboardPluginMock.createStartContract(), presentationUtil: presentationUtilPluginMock.createStartContract(core), savedObjectsClient: core.savedObjects.client, diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 6e8b7d35b0cb..95f2e13cbc46 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -206,6 +206,7 @@ export class LensPlugin { indexPatternService: deps.data.indexPatterns, uiActions: deps.uiActions, usageCollection, + inspector: deps.inspector, }; }; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 0a04e4fea932..399e226a711d 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { IconType } from '@elastic/eui/src/components/icon/icon'; -import { CoreSetup } from 'kibana/public'; -import { PaletteOutput } from 'src/plugins/charts/public'; -import { SavedObjectReference } from 'kibana/public'; -import { MutableRefObject } from 'react'; -import { RowClickContext } from '../../../../src/plugins/ui_actions/public'; -import { +import type { IconType } from '@elastic/eui/src/components/icon/icon'; +import type { CoreSetup, SavedObjectReference } from 'kibana/public'; +import type { PaletteOutput } from 'src/plugins/charts/public'; +import type { MutableRefObject } from 'react'; +import type { ExpressionAstExpression, ExpressionRendererEvent, IInterpreterRenderHandlers, @@ -19,20 +17,28 @@ import { } from '../../../../src/plugins/expressions/public'; import { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop'; import type { DateRange, LayerType } from '../common'; -import { Query, Filter } from '../../../../src/plugins/data/public'; -import { VisualizeFieldContext } from '../../../../src/plugins/ui_actions/public'; -import { RangeSelectContext, ValueClickContext } from '../../../../src/plugins/embeddable/public'; -import { - LENS_EDIT_SORT_ACTION, - LENS_EDIT_RESIZE_ACTION, - LENS_TOGGLE_ACTION, -} from './datatable_visualization/components/constants'; +import type { Query, Filter } from '../../../../src/plugins/data/public'; +import type { + RangeSelectContext, + ValueClickContext, +} from '../../../../src/plugins/embeddable/public'; import type { LensSortActionData, LensResizeActionData, LensToggleActionData, } from './datatable_visualization/components/types'; -import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import type { + UiActionsStart, + RowClickContext, + VisualizeFieldContext, +} from '../../../../src/plugins/ui_actions/public'; + +import { + LENS_EDIT_SORT_ACTION, + LENS_EDIT_RESIZE_ACTION, + LENS_TOGGLE_ACTION, +} from './datatable_visualization/components/constants'; +import type { LensInspector } from './lens_inspector_service'; export type ErrorCallback = (e: { message: string }) => void; @@ -43,6 +49,7 @@ export interface PublicAPIProps { export interface EditorFrameProps { showNoDataPopover: () => void; + lensInspector: LensInspector; } export type VisualizationMap = Record; diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 19ecc017c507..09bbda595d55 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -43,6 +43,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./lens_tagging')); loadTestFile(require.resolve('./formula')); loadTestFile(require.resolve('./heatmap')); + loadTestFile(require.resolve('./inspector')); // has to be last one in the suite because it overrides saved objects loadTestFile(require.resolve('./rollup')); diff --git a/x-pack/test/functional/apps/lens/inspector.ts b/x-pack/test/functional/apps/lens/inspector.ts new file mode 100644 index 000000000000..0783124079d4 --- /dev/null +++ b/x-pack/test/functional/apps/lens/inspector.ts @@ -0,0 +1,59 @@ +/* + * 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 expect from '@kbn/expect'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const elasticChart = getService('elasticChart'); + const inspector = getService('inspector'); + + describe('lens inspector', () => { + before(async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'clientip', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'max', + field: 'bytes', + }); + + await PageObjects.lens.waitForVisualization(); + + await inspector.open('lnsApp_inspectButton'); + }); + + after(async () => { + await inspector.close(); + }); + + it('should inspect table data', async () => { + await inspector.expectTableData([ + ['232.44.243.247', '19,986'], + ['252.59.37.77', '19,985'], + ['239.180.70.74', '19,984'], + ['206.22.226.5', '19,952'], + ['80.252.219.9', '19,950'], + ['Other', '19,941'], + ]); + }); + + it('should inspect request data', async () => { + await inspector.openInspectorRequestsView(); + expect(await inspector.getRequestNames()).to.be('Data,Other bucket'); + }); + }); +}