[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 <alexwizp@gmail.com>
This commit is contained in:
Kibana Machine 2021-08-26 12:42:45 -04:00 committed by GitHub
parent 1ceb2e833c
commit 8875ce2a9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 351 additions and 68 deletions

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) &gt; [createDefaultInspectorAdapters](./kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md)
## createDefaultInspectorAdapters variable
<b>Signature:</b>
```typescript
createDefaultInspectorAdapters: () => DefaultInspectorAdapters
```

View file

@ -84,6 +84,7 @@
| Variable | Description |
| --- | --- |
| [createDefaultInspectorAdapters](./kibana-plugin-plugins-expressions-public.createdefaultinspectoradapters.md) | |
| [ReactExpressionRenderer](./kibana-plugin-plugins-expressions-public.reactexpressionrenderer.md) | |
## Type Aliases

View file

@ -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,

View file

@ -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(),
});

View file

@ -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';

View file

@ -108,4 +108,5 @@ export {
ExpressionsServiceStart,
TablesAdapter,
ExpressionsInspectorAdapter,
createDefaultInspectorAdapters,
} from '../common';

View file

@ -55,6 +55,12 @@ initialArgs: {
[K in keyof FunctionArgs<FnDef>]: FunctionArgs<FnDef>[K] | ExpressionAstExpressionBuilder | ExpressionAstExpressionBuilder[] | ExpressionAstExpression | ExpressionAstExpression[];
}): ExpressionAstFunctionBuilder<FnDef>;
// 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<string, any>;
// 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)

View file

@ -45,12 +45,12 @@ export class InspectorService extends FtrService {
/**
* Opens inspector panel
*/
public async open(): Promise<void> {
public async open(openButton: string = 'openInspectorButton'): Promise<void> {
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');
});
}

View file

@ -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 {},
],
]
`;

View file

@ -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({});

View file

@ -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<OnSaveProps, 'onTitleDuplicate' | 'newDescription'> & {
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<Document | undefined>(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) && (
<MemoizedEditorFrameWrapper
editorFrame={editorFrame}
showNoDataPopover={showNoDataPopover}
lensInspector={lensInspector}
/>
)}
</div>
@ -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 <EditorFrameContainer showNoDataPopover={showNoDataPopover} />;
return (
<EditorFrameContainer showNoDataPopover={showNoDataPopover} lensInspector={lensInspector} />
);
});

View file

@ -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,
]
);

View file

@ -48,6 +48,7 @@ export async function getLensServices(
): Promise<LensAppServices> {
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,
};

View file

@ -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;

View file

@ -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;

View file

@ -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}

View file

@ -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<string, Record<string, boolean | Record<string, boolean>>> = {
navLinks: { management: true },
@ -59,6 +61,7 @@ const defaultProps = {
data: mockDataPlugin(),
},
getSuggestionForField: () => undefined,
lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()),
toggleFullscreen: jest.fn(),
};

View file

@ -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<WorkspacePanelProps, 'getSuggestionForField'> & {
suggestionForDraggedField: Suggestion | undefined;
}) {
@ -335,7 +334,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
<VisualizationWrapper
expression={expression}
framePublicAPI={framePublicAPI}
timefilter={plugins.data.query.timefilter.timefilter}
lensInspector={lensInspector}
onEvent={onEvent}
setLocalState={setLocalState}
localState={{ ...localState, configurationValidationError, missingRefsErrors }}
@ -401,7 +400,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({
export const VisualizationWrapper = ({
expression,
framePublicAPI,
timefilter,
lensInspector,
onEvent,
setLocalState,
localState,
@ -411,7 +410,7 @@ export const VisualizationWrapper = ({
}: {
expression: string | null | undefined;
framePublicAPI: FramePublicAPI;
timefilter: TimefilterContract;
lensInspector: LensInspector;
onEvent: (event: ExpressionRendererEvent) => 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<DefaultInspectorAdapters>) => {
if (inspectorAdapters && inspectorAdapters.tables) {
dispatchLens(onActiveDataChange({ ...inspectorAdapters.tables.tables }));
(data: unknown, adapters?: Partial<DefaultInspectorAdapters>) => {
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);

View file

@ -107,13 +107,14 @@ export class EditorFrameService {
const { EditorFrame } = await import('../async_services');
return {
EditorFrameContainer: ({ showNoDataPopover }) => {
EditorFrameContainer: ({ showNoDataPopover, lensInspector }) => {
return (
<div className="lnsApp__frame">
<EditorFrame
data-test-subj="lnsEditorFrame"
core={core}
plugins={plugins}
lensInspector={lensInspector}
showNoDataPopover={showNoDataPopover}
datasourceMap={resolvedDatasources}
visualizationMap={resolvedVisualizations}

View file

@ -23,6 +23,7 @@ import { AttributeService, ViewMode } from '../../../../../src/plugins/embeddabl
import { LensAttributeService } from '../lens_attribute_service';
import { OnSaveProps } from '../../../../../src/plugins/saved_objects/public/save_modal';
import { act } from 'react-dom/test-utils';
import { inspectorPluginMock } from '../../../../../src/plugins/inspector/public/mocks';
jest.mock('../../../../../src/plugins/inspector/public/', () => ({
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,

View file

@ -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<Document, 'savedObjectId' | 'type'>;
@ -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<DefaultInspectorAdapters> | 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<DefaultInspectorAdapters> | 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}

View file

@ -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<Capabilities>;
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,

View file

@ -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 (
<I18nProvider>
@ -126,6 +129,7 @@ export function ExpressionWrapper({
searchContext={searchContext}
searchSessionId={searchSessionId}
onData$={onData$}
inspectorAdapters={lensInspector.adapters}
renderMode={renderMode}
syncColors={syncColors}
executionContext={executionContext}

View file

@ -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<typeof getLensInspectorService>;

View file

@ -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,

View file

@ -206,6 +206,7 @@ export class LensPlugin {
indexPatternService: deps.data.indexPatterns,
uiActions: deps.uiActions,
usageCollection,
inspector: deps.inspector,
};
};

View file

@ -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<T> {
export interface EditorFrameProps {
showNoDataPopover: () => void;
lensInspector: LensInspector;
}
export type VisualizationMap = Record<string, Visualization>;

View file

@ -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'));

View file

@ -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');
});
});
}