mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Discover locator (#102712)
* Add Discover locator * Add Discover locator tests * Expose locator for Discover app and deprecate URL generator * Use Discover locator in Explore Underlying Data * Fix explore data unit tests after refactor * fix: 🐛 update Discover plugin mock * style: 💄 remove any * test: 💍 fix test mock * fix: 🐛 adjust property name after refactor * test: 💍 fix tests after refactor Co-authored-by: Vadim Kibana <vadimkibana@gmail.com>
This commit is contained in:
parent
868ae59c93
commit
1386c330fc
11 changed files with 589 additions and 91 deletions
|
@ -17,4 +17,6 @@ export function plugin(initializerContext: PluginInitializerContext) {
|
|||
export { SavedSearch, SavedSearchLoader, createSavedSearchesLoader } from './saved_searches';
|
||||
export { ISearchEmbeddable, SEARCH_EMBEDDABLE_TYPE, SearchInput } from './application/embeddable';
|
||||
export { loadSharingDataHelpers } from './shared';
|
||||
|
||||
export { DISCOVER_APP_URL_GENERATOR, DiscoverUrlGeneratorState } from './url_generator';
|
||||
export { DiscoverAppLocator, DiscoverAppLocatorParams } from './locator';
|
||||
|
|
270
src/plugins/discover/public/locator.test.ts
Normal file
270
src/plugins/discover/public/locator.test.ts
Normal file
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* 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 { hashedItemStore, getStatesFromKbnUrl } from '../../kibana_utils/public';
|
||||
import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock';
|
||||
import { FilterStateStore } from '../../data/common';
|
||||
import { DiscoverAppLocatorDefinition } from './locator';
|
||||
import { SerializableState } from 'src/plugins/kibana_utils/common';
|
||||
|
||||
const indexPatternId: string = 'c367b774-a4c2-11ea-bb37-0242ac130002';
|
||||
const savedSearchId: string = '571aaf70-4c88-11e8-b3d7-01146121b73d';
|
||||
|
||||
interface SetupParams {
|
||||
useHash?: boolean;
|
||||
}
|
||||
|
||||
const setup = async ({ useHash = false }: SetupParams = {}) => {
|
||||
const locator = new DiscoverAppLocatorDefinition({
|
||||
useHash,
|
||||
});
|
||||
|
||||
return {
|
||||
locator,
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// @ts-expect-error
|
||||
hashedItemStore.storage = mockStorage;
|
||||
});
|
||||
|
||||
describe('Discover url generator', () => {
|
||||
test('can create a link to Discover with no state and no saved search', async () => {
|
||||
const { locator } = await setup();
|
||||
const { app, path } = await locator.getLocation({});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(app).toBe('discover');
|
||||
expect(_a).toEqual({});
|
||||
expect(_g).toEqual({});
|
||||
});
|
||||
|
||||
test('can create a link to a saved search in Discover', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({ savedSearchId });
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(path.startsWith(`#/view/${savedSearchId}`)).toBe(true);
|
||||
expect(_a).toEqual({});
|
||||
expect(_g).toEqual({});
|
||||
});
|
||||
|
||||
test('can specify specific index pattern', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
indexPatternId,
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({
|
||||
index: indexPatternId,
|
||||
});
|
||||
expect(_g).toEqual({});
|
||||
});
|
||||
|
||||
test('can specify specific time range', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
timeRange: { to: 'now', from: 'now-15m', mode: 'relative' },
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({});
|
||||
expect(_g).toEqual({
|
||||
time: {
|
||||
from: 'now-15m',
|
||||
mode: 'relative',
|
||||
to: 'now',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('can specify query', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: 'foo',
|
||||
},
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({
|
||||
query: {
|
||||
language: 'kuery',
|
||||
query: 'foo',
|
||||
},
|
||||
});
|
||||
expect(_g).toEqual({});
|
||||
});
|
||||
|
||||
test('can specify local and global filters', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
alias: 'foo',
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
alias: 'bar',
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.GLOBAL_STATE,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
meta: {
|
||||
alias: 'foo',
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(_g).toEqual({
|
||||
filters: [
|
||||
{
|
||||
$state: {
|
||||
store: 'globalState',
|
||||
},
|
||||
meta: {
|
||||
alias: 'bar',
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('can set refresh interval', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 666,
|
||||
},
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({});
|
||||
expect(_g).toEqual({
|
||||
refreshInterval: {
|
||||
pause: false,
|
||||
value: 666,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('can set time range', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
timeRange: {
|
||||
from: 'now-3h',
|
||||
to: 'now',
|
||||
},
|
||||
});
|
||||
const { _a, _g } = getStatesFromKbnUrl(path, ['_a', '_g']);
|
||||
|
||||
expect(_a).toEqual({});
|
||||
expect(_g).toEqual({
|
||||
time: {
|
||||
from: 'now-3h',
|
||||
to: 'now',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('can specify a search session id', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
searchSessionId: '__test__',
|
||||
});
|
||||
|
||||
expect(path).toMatchInlineSnapshot(`"#/?_g=()&_a=()&searchSessionId=__test__"`);
|
||||
expect(path).toContain('__test__');
|
||||
});
|
||||
|
||||
test('can specify columns, interval, sort and savedQuery', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
columns: ['_source'],
|
||||
interval: 'auto',
|
||||
sort: [['timestamp, asc']] as string[][] & SerializableState,
|
||||
savedQuery: '__savedQueryId__',
|
||||
});
|
||||
|
||||
expect(path).toMatchInlineSnapshot(
|
||||
`"#/?_g=()&_a=(columns:!(_source),interval:auto,savedQuery:__savedQueryId__,sort:!(!('timestamp,%20asc')))"`
|
||||
);
|
||||
});
|
||||
|
||||
describe('useHash property', () => {
|
||||
describe('when default useHash is set to false', () => {
|
||||
test('when using default, sets index pattern ID in the generated URL', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
indexPatternId,
|
||||
});
|
||||
|
||||
expect(path.indexOf(indexPatternId) > -1).toBe(true);
|
||||
});
|
||||
|
||||
test('when enabling useHash, does not set index pattern ID in the generated URL', async () => {
|
||||
const { locator } = await setup();
|
||||
const { path } = await locator.getLocation({
|
||||
useHash: true,
|
||||
indexPatternId,
|
||||
});
|
||||
|
||||
expect(path.indexOf(indexPatternId) > -1).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when default useHash is set to true', () => {
|
||||
test('when using default, does not set index pattern ID in the generated URL', async () => {
|
||||
const { locator } = await setup({ useHash: true });
|
||||
const { path } = await locator.getLocation({
|
||||
indexPatternId,
|
||||
});
|
||||
|
||||
expect(path.indexOf(indexPatternId) > -1).toBe(false);
|
||||
});
|
||||
|
||||
test('when disabling useHash, sets index pattern ID in the generated URL', async () => {
|
||||
const { locator } = await setup({ useHash: true });
|
||||
const { path } = await locator.getLocation({
|
||||
useHash: false,
|
||||
indexPatternId,
|
||||
});
|
||||
|
||||
expect(path.indexOf(indexPatternId) > -1).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
146
src/plugins/discover/public/locator.ts
Normal file
146
src/plugins/discover/public/locator.ts
Normal file
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 type { SerializableState } from 'src/plugins/kibana_utils/common';
|
||||
import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public';
|
||||
import type { LocatorDefinition, LocatorPublic } from '../../share/public';
|
||||
import { esFilters } from '../../data/public';
|
||||
import { setStateToKbnUrl } from '../../kibana_utils/public';
|
||||
|
||||
export const DISCOVER_APP_LOCATOR = 'DISCOVER_APP_LOCATOR';
|
||||
|
||||
export interface DiscoverAppLocatorParams extends SerializableState {
|
||||
/**
|
||||
* Optionally set saved search ID.
|
||||
*/
|
||||
savedSearchId?: string;
|
||||
|
||||
/**
|
||||
* Optionally set index pattern ID.
|
||||
*/
|
||||
indexPatternId?: string;
|
||||
|
||||
/**
|
||||
* Optionally set the time range in the time picker.
|
||||
*/
|
||||
timeRange?: TimeRange;
|
||||
|
||||
/**
|
||||
* Optionally set the refresh interval.
|
||||
*/
|
||||
refreshInterval?: RefreshInterval & SerializableState;
|
||||
|
||||
/**
|
||||
* Optionally apply filters.
|
||||
*/
|
||||
filters?: Filter[];
|
||||
|
||||
/**
|
||||
* Optionally set a query.
|
||||
*/
|
||||
query?: Query;
|
||||
|
||||
/**
|
||||
* If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines
|
||||
* whether to hash the data in the url to avoid url length issues.
|
||||
*/
|
||||
useHash?: boolean;
|
||||
|
||||
/**
|
||||
* Background search session id
|
||||
*/
|
||||
searchSessionId?: string;
|
||||
|
||||
/**
|
||||
* Columns displayed in the table
|
||||
*/
|
||||
columns?: string[];
|
||||
|
||||
/**
|
||||
* Used interval of the histogram
|
||||
*/
|
||||
interval?: string;
|
||||
|
||||
/**
|
||||
* Array of the used sorting [[field,direction],...]
|
||||
*/
|
||||
sort?: string[][] & SerializableState;
|
||||
|
||||
/**
|
||||
* id of the used saved query
|
||||
*/
|
||||
savedQuery?: string;
|
||||
}
|
||||
|
||||
export type DiscoverAppLocator = LocatorPublic<DiscoverAppLocatorParams>;
|
||||
|
||||
export interface DiscoverAppLocatorDependencies {
|
||||
useHash: boolean;
|
||||
}
|
||||
|
||||
export class DiscoverAppLocatorDefinition implements LocatorDefinition<DiscoverAppLocatorParams> {
|
||||
public readonly id = DISCOVER_APP_LOCATOR;
|
||||
|
||||
constructor(protected readonly deps: DiscoverAppLocatorDependencies) {}
|
||||
|
||||
public readonly getLocation = async (params: DiscoverAppLocatorParams) => {
|
||||
const {
|
||||
useHash = this.deps.useHash,
|
||||
filters,
|
||||
indexPatternId,
|
||||
query,
|
||||
refreshInterval,
|
||||
savedSearchId,
|
||||
timeRange,
|
||||
searchSessionId,
|
||||
columns,
|
||||
savedQuery,
|
||||
sort,
|
||||
interval,
|
||||
} = params;
|
||||
const savedSearchPath = savedSearchId ? `view/${encodeURIComponent(savedSearchId)}` : '';
|
||||
const appState: {
|
||||
query?: Query;
|
||||
filters?: Filter[];
|
||||
index?: string;
|
||||
columns?: string[];
|
||||
interval?: string;
|
||||
sort?: string[][];
|
||||
savedQuery?: string;
|
||||
} = {};
|
||||
const queryState: QueryState = {};
|
||||
|
||||
if (query) appState.query = query;
|
||||
if (filters && filters.length)
|
||||
appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f));
|
||||
if (indexPatternId) appState.index = indexPatternId;
|
||||
if (columns) appState.columns = columns;
|
||||
if (savedQuery) appState.savedQuery = savedQuery;
|
||||
if (sort) appState.sort = sort;
|
||||
if (interval) appState.interval = interval;
|
||||
|
||||
if (timeRange) queryState.time = timeRange;
|
||||
if (filters && filters.length)
|
||||
queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f));
|
||||
if (refreshInterval) queryState.refreshInterval = refreshInterval;
|
||||
|
||||
let path = `#/${savedSearchPath}`;
|
||||
path = setStateToKbnUrl<QueryState>('_g', queryState, { useHash }, path);
|
||||
path = setStateToKbnUrl('_a', appState, { useHash }, path);
|
||||
|
||||
if (searchSessionId) {
|
||||
path = `${path}&searchSessionId=${searchSessionId}`;
|
||||
}
|
||||
|
||||
return {
|
||||
app: 'discover',
|
||||
path,
|
||||
state: {},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -16,6 +16,12 @@ const createSetupContract = (): Setup => {
|
|||
docViews: {
|
||||
addDocView: jest.fn(),
|
||||
},
|
||||
locator: {
|
||||
getLocation: jest.fn(),
|
||||
getUrl: jest.fn(),
|
||||
useUrl: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
@ -26,6 +32,12 @@ const createStartContract = (): Start => {
|
|||
urlGenerator: ({
|
||||
createUrl: jest.fn(),
|
||||
} as unknown) as DiscoverStart['urlGenerator'],
|
||||
locator: {
|
||||
getLocation: jest.fn(),
|
||||
getUrl: jest.fn(),
|
||||
useUrl: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
};
|
||||
return startContract;
|
||||
};
|
||||
|
|
|
@ -59,6 +59,7 @@ import {
|
|||
DiscoverUrlGenerator,
|
||||
SEARCH_SESSION_ID_QUERY_PARAM,
|
||||
} from './url_generator';
|
||||
import { DiscoverAppLocatorDefinition, DiscoverAppLocator } from './locator';
|
||||
import { SearchEmbeddableFactory } from './application/embeddable';
|
||||
import { UsageCollectionSetup } from '../../usage_collection/public';
|
||||
import { replaceUrlHashQuery } from '../../kibana_utils/public/';
|
||||
|
@ -83,17 +84,27 @@ export interface DiscoverSetup {
|
|||
*/
|
||||
addDocView(docViewRaw: DocViewInput | DocViewInputFn): void;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DiscoverStart {
|
||||
savedSearchLoader: SavedObjectLoader;
|
||||
|
||||
/**
|
||||
* `share` plugin URL generator for Discover app. Use it to generate links into
|
||||
* Discover application, example:
|
||||
* `share` plugin URL locator for Discover app. Use it to generate links into
|
||||
* Discover application, for example, navigate:
|
||||
*
|
||||
* ```ts
|
||||
* const url = await plugins.discover.urlGenerator.createUrl({
|
||||
* await plugins.discover.locator.navigate({
|
||||
* savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',
|
||||
* indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',
|
||||
* timeRange: {
|
||||
* to: 'now',
|
||||
* from: 'now-15m',
|
||||
* mode: 'relative',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Generate a location:
|
||||
*
|
||||
* ```ts
|
||||
* const location = await plugins.discover.locator.getLocation({
|
||||
* savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',
|
||||
* indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',
|
||||
* timeRange: {
|
||||
|
@ -104,7 +115,48 @@ export interface DiscoverStart {
|
|||
* });
|
||||
* ```
|
||||
*/
|
||||
readonly locator: undefined | DiscoverAppLocator;
|
||||
}
|
||||
|
||||
export interface DiscoverStart {
|
||||
savedSearchLoader: SavedObjectLoader;
|
||||
|
||||
/**
|
||||
* @deprecated Use URL locator instead. URL generaotr will be removed.
|
||||
*/
|
||||
readonly urlGenerator: undefined | UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;
|
||||
|
||||
/**
|
||||
* `share` plugin URL locator for Discover app. Use it to generate links into
|
||||
* Discover application, for example, navigate:
|
||||
*
|
||||
* ```ts
|
||||
* await plugins.discover.locator.navigate({
|
||||
* savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',
|
||||
* indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',
|
||||
* timeRange: {
|
||||
* to: 'now',
|
||||
* from: 'now-15m',
|
||||
* mode: 'relative',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* Generate a location:
|
||||
*
|
||||
* ```ts
|
||||
* const location = await plugins.discover.locator.getLocation({
|
||||
* savedSearchId: '571aaf70-4c88-11e8-b3d7-01146121b73d',
|
||||
* indexPatternId: 'c367b774-a4c2-11ea-bb37-0242ac130002',
|
||||
* timeRange: {
|
||||
* to: 'now',
|
||||
* from: 'now-15m',
|
||||
* mode: 'relative',
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
readonly locator: undefined | DiscoverAppLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,7 +208,12 @@ export class DiscoverPlugin
|
|||
private stopUrlTracking: (() => void) | undefined = undefined;
|
||||
private servicesInitialized: boolean = false;
|
||||
private innerAngularInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
private urlGenerator?: DiscoverStart['urlGenerator'];
|
||||
private locator?: DiscoverAppLocator;
|
||||
|
||||
/**
|
||||
* why are those functions public? they are needed for some mocha tests
|
||||
|
@ -179,6 +236,15 @@ export class DiscoverPlugin
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (plugins.share) {
|
||||
this.locator = plugins.share.url.locators.create(
|
||||
new DiscoverAppLocatorDefinition({
|
||||
useHash: core.uiSettings.get('state:storeInSessionStorage'),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this.docViewsRegistry = new DocViewsRegistry();
|
||||
setDocViewsRegistry(this.docViewsRegistry);
|
||||
this.docViewsRegistry.addDocView({
|
||||
|
@ -323,6 +389,7 @@ export class DiscoverPlugin
|
|||
docViews: {
|
||||
addDocView: this.docViewsRegistry.addDocView.bind(this.docViewsRegistry),
|
||||
},
|
||||
locator: this.locator,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -367,6 +434,7 @@ export class DiscoverPlugin
|
|||
|
||||
return {
|
||||
urlGenerator: this.urlGenerator,
|
||||
locator: this.locator,
|
||||
savedSearchLoader: createSavedSearchesLoader({
|
||||
savedObjectsClient: core.savedObjects.client,
|
||||
savedObjects: plugins.savedObjects,
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"requiredPlugins": ["uiActions", "embeddable", "discover"],
|
||||
"optionalPlugins": ["share", "kibanaLegacy", "usageCollection"],
|
||||
"configPath": ["xpack", "discoverEnhanced"],
|
||||
"requiredBundles": ["kibanaUtils", "data", "share"]
|
||||
"requiredBundles": ["kibanaUtils", "data"]
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@ import { ViewMode, IEmbeddable } from '../../../../../../src/plugins/embeddable/
|
|||
import { StartServicesGetter } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
import { KibanaLegacyStart } from '../../../../../../src/plugins/kibana_legacy/public';
|
||||
import { CoreStart } from '../../../../../../src/core/public';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import { KibanaLocation } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
|
||||
export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA';
|
||||
|
||||
export interface PluginDeps {
|
||||
discover: Pick<DiscoverStart, 'urlGenerator'>;
|
||||
discover: Pick<DiscoverStart, 'locator'>;
|
||||
kibanaLegacy?: {
|
||||
dashboardConfig: {
|
||||
getHideWriteControls: KibanaLegacyStart['dashboardConfig']['getHideWriteControls'];
|
||||
|
@ -26,7 +26,7 @@ export interface PluginDeps {
|
|||
}
|
||||
|
||||
export interface CoreDeps {
|
||||
application: Pick<CoreStart['application'], 'navigateToApp'>;
|
||||
application: Pick<CoreStart['application'], 'navigateToApp' | 'getUrlForApp'>;
|
||||
}
|
||||
|
||||
export interface Params {
|
||||
|
@ -43,7 +43,7 @@ export abstract class AbstractExploreDataAction<Context extends { embeddable?: I
|
|||
|
||||
constructor(protected readonly params: Params) {}
|
||||
|
||||
protected abstract getUrl(context: Context): Promise<KibanaURL>;
|
||||
protected abstract getLocation(context: Context): Promise<KibanaLocation>;
|
||||
|
||||
public async isCompatible({ embeddable }: Context): Promise<boolean> {
|
||||
if (!embeddable) return false;
|
||||
|
@ -52,7 +52,7 @@ export abstract class AbstractExploreDataAction<Context extends { embeddable?: I
|
|||
const { capabilities } = core.application;
|
||||
|
||||
if (capabilities.discover && !capabilities.discover.show) return false;
|
||||
if (!plugins.discover.urlGenerator) return false;
|
||||
if (!plugins.discover.locator) return false;
|
||||
const isDashboardOnlyMode = !!this.params
|
||||
.start()
|
||||
.plugins.kibanaLegacy?.dashboardConfig.getHideWriteControls();
|
||||
|
@ -68,10 +68,10 @@ export abstract class AbstractExploreDataAction<Context extends { embeddable?: I
|
|||
if (!shared.hasExactlyOneIndexPattern(context.embeddable)) return;
|
||||
|
||||
const { core } = this.params.start();
|
||||
const { appName, appPath } = await this.getUrl(context);
|
||||
const { app, path } = await this.getLocation(context);
|
||||
|
||||
await core.application.navigateToApp(appName, {
|
||||
path: appPath,
|
||||
await core.application.navigateToApp(app, {
|
||||
path,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -82,8 +82,10 @@ export abstract class AbstractExploreDataAction<Context extends { embeddable?: I
|
|||
throw new Error(`Embeddable not supported for "${this.getDisplayName(context)}" action.`);
|
||||
}
|
||||
|
||||
const { path } = await this.getUrl(context);
|
||||
const { core } = this.params.start();
|
||||
const { app, path } = await this.getLocation(context);
|
||||
const url = await core.application.getUrlForApp(app, { path, absolute: false });
|
||||
|
||||
return path;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import { ExploreDataChartAction } from './explore_data_chart_action';
|
||||
import { Params, PluginDeps } from './abstract_explore_data_action';
|
||||
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||
import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public';
|
||||
import { ExploreDataChartActionContext } from './explore_data_chart_action';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
} from '../../../../../../src/plugins/visualizations/public';
|
||||
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { Filter, RangeFilter } from '../../../../../../src/plugins/data/public';
|
||||
import { DiscoverAppLocator } from '../../../../../../src/plugins/discover/public';
|
||||
|
||||
const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance;
|
||||
|
||||
|
@ -43,17 +43,23 @@ const setup = (
|
|||
dashboardOnlyMode?: boolean;
|
||||
} = { filters: [] }
|
||||
) => {
|
||||
type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;
|
||||
|
||||
const core = coreMock.createStart();
|
||||
|
||||
const urlGenerator: UrlGenerator = ({
|
||||
createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')),
|
||||
} as unknown) as UrlGenerator;
|
||||
const locator: DiscoverAppLocator = {
|
||||
getLocation: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
app: 'discover',
|
||||
path: '/foo#bar',
|
||||
state: {},
|
||||
})
|
||||
),
|
||||
navigate: jest.fn(async () => {}),
|
||||
getUrl: jest.fn(),
|
||||
useUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const plugins: PluginDeps = {
|
||||
discover: {
|
||||
urlGenerator,
|
||||
locator,
|
||||
},
|
||||
kibanaLegacy: {
|
||||
dashboardConfig: {
|
||||
|
@ -95,7 +101,7 @@ const setup = (
|
|||
embeddable,
|
||||
} as ExploreDataChartActionContext;
|
||||
|
||||
return { core, plugins, urlGenerator, params, action, input, output, embeddable, context };
|
||||
return { core, plugins, locator, params, action, input, output, embeddable, context };
|
||||
};
|
||||
|
||||
describe('"Explore underlying data" panel action', () => {
|
||||
|
@ -132,7 +138,7 @@ describe('"Explore underlying data" panel action', () => {
|
|||
|
||||
test('returns false when URL generator is not present', async () => {
|
||||
const { action, plugins, context } = setup();
|
||||
(plugins.discover as any).urlGenerator = undefined;
|
||||
(plugins.discover as any).locator = undefined;
|
||||
|
||||
const isCompatible = await action.isCompatible(context);
|
||||
|
||||
|
@ -205,23 +211,15 @@ describe('"Explore underlying data" panel action', () => {
|
|||
});
|
||||
|
||||
describe('getHref()', () => {
|
||||
test('returns URL path generated by URL generator', async () => {
|
||||
const { action, context } = setup();
|
||||
|
||||
const href = await action.getHref(context);
|
||||
|
||||
expect(href).toBe('/xyz/app/discover/foo#bar');
|
||||
});
|
||||
|
||||
test('calls URL generator with right arguments', async () => {
|
||||
const { action, urlGenerator, context } = setup();
|
||||
const { action, locator, context } = setup();
|
||||
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0);
|
||||
expect(locator.getLocation).toHaveBeenCalledTimes(0);
|
||||
|
||||
await action.getHref(context);
|
||||
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1);
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledWith({
|
||||
expect(locator.getLocation).toHaveBeenCalledTimes(1);
|
||||
expect(locator.getLocation).toHaveBeenCalledWith({
|
||||
filters: [],
|
||||
indexPatternId: 'index-ptr-foo',
|
||||
timeRange: undefined,
|
||||
|
@ -260,11 +258,11 @@ describe('"Explore underlying data" panel action', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const { action, context, urlGenerator } = setup({ filters, timeFieldName });
|
||||
const { action, context, locator } = setup({ filters, timeFieldName });
|
||||
|
||||
await action.getHref(context);
|
||||
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledWith({
|
||||
expect(locator.getLocation).toHaveBeenCalledWith({
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { Action } from '../../../../../../src/plugins/ui_actions/public';
|
||||
import {
|
||||
DiscoverUrlGeneratorState,
|
||||
DiscoverAppLocatorParams,
|
||||
SearchInput,
|
||||
} from '../../../../../../src/plugins/discover/public';
|
||||
import {
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
esFilters,
|
||||
} from '../../../../../../src/plugins/data/public';
|
||||
import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import { KibanaLocation } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
import { AbstractExploreDataAction } from './abstract_explore_data_action';
|
||||
|
||||
|
@ -43,14 +43,14 @@ export class ExploreDataChartAction
|
|||
return super.isCompatible(context);
|
||||
}
|
||||
|
||||
protected readonly getUrl = async (
|
||||
protected readonly getLocation = async (
|
||||
context: ExploreDataChartActionContext
|
||||
): Promise<KibanaURL> => {
|
||||
): Promise<KibanaLocation> => {
|
||||
const { plugins } = this.params.start();
|
||||
const { urlGenerator } = plugins.discover;
|
||||
const { locator } = plugins.discover;
|
||||
|
||||
if (!urlGenerator) {
|
||||
throw new Error('Discover URL generator not available.');
|
||||
if (!locator) {
|
||||
throw new Error('Discover URL locator not available.');
|
||||
}
|
||||
|
||||
const { embeddable } = context;
|
||||
|
@ -59,23 +59,23 @@ export class ExploreDataChartAction
|
|||
context.timeFieldName
|
||||
);
|
||||
|
||||
const state: DiscoverUrlGeneratorState = {
|
||||
const params: DiscoverAppLocatorParams = {
|
||||
filters,
|
||||
timeRange,
|
||||
};
|
||||
|
||||
if (embeddable) {
|
||||
state.indexPatternId = shared.getIndexPatterns(embeddable)[0] || undefined;
|
||||
params.indexPatternId = shared.getIndexPatterns(embeddable)[0] || undefined;
|
||||
|
||||
const input = embeddable.getInput() as Readonly<SearchInput>;
|
||||
|
||||
if (input.timeRange && !state.timeRange) state.timeRange = input.timeRange;
|
||||
if (input.query) state.query = input.query;
|
||||
if (input.filters) state.filters = [...input.filters, ...(state.filters || [])];
|
||||
if (input.timeRange && !params.timeRange) params.timeRange = input.timeRange;
|
||||
if (input.query) params.query = input.query;
|
||||
if (input.filters) params.filters = [...input.filters, ...(params.filters || [])];
|
||||
}
|
||||
|
||||
const path = await urlGenerator.createUrl(state);
|
||||
const location = await locator.getLocation(params);
|
||||
|
||||
return new KibanaURL(path);
|
||||
return location;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
import { ExploreDataContextMenuAction } from './explore_data_context_menu_action';
|
||||
import { Params, PluginDeps } from './abstract_explore_data_action';
|
||||
import { coreMock } from '../../../../../../src/core/public/mocks';
|
||||
import { UrlGeneratorContract } from '../../../../../../src/plugins/share/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
VisualizeEmbeddableContract,
|
||||
VISUALIZE_EMBEDDABLE_TYPE,
|
||||
} from '../../../../../../src/plugins/visualizations/public';
|
||||
import { ViewMode } from '../../../../../../src/plugins/embeddable/public';
|
||||
import { DiscoverAppLocator } from '../../../../../../src/plugins/discover/public';
|
||||
|
||||
const i18nTranslateSpy = (i18n.translate as unknown) as jest.SpyInstance;
|
||||
|
||||
|
@ -29,17 +29,23 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } = {}) => {
|
||||
type UrlGenerator = UrlGeneratorContract<'DISCOVER_APP_URL_GENERATOR'>;
|
||||
|
||||
const core = coreMock.createStart();
|
||||
|
||||
const urlGenerator: UrlGenerator = ({
|
||||
createUrl: jest.fn(() => Promise.resolve('/xyz/app/discover/foo#bar')),
|
||||
} as unknown) as UrlGenerator;
|
||||
const locator: DiscoverAppLocator = {
|
||||
getLocation: jest.fn(() =>
|
||||
Promise.resolve({
|
||||
app: 'discover',
|
||||
path: '/foo#bar',
|
||||
state: {},
|
||||
})
|
||||
),
|
||||
navigate: jest.fn(async () => {}),
|
||||
getUrl: jest.fn(),
|
||||
useUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const plugins: PluginDeps = {
|
||||
discover: {
|
||||
urlGenerator,
|
||||
locator,
|
||||
},
|
||||
kibanaLegacy: {
|
||||
dashboardConfig: {
|
||||
|
@ -79,7 +85,7 @@ const setup = ({ dashboardOnlyMode = false }: { dashboardOnlyMode?: boolean } =
|
|||
embeddable,
|
||||
};
|
||||
|
||||
return { core, plugins, urlGenerator, params, action, input, output, embeddable, context };
|
||||
return { core, plugins, locator, params, action, input, output, embeddable, context };
|
||||
};
|
||||
|
||||
describe('"Explore underlying data" panel action', () => {
|
||||
|
@ -116,7 +122,7 @@ describe('"Explore underlying data" panel action', () => {
|
|||
|
||||
test('returns false when URL generator is not present', async () => {
|
||||
const { action, plugins, context } = setup();
|
||||
(plugins.discover as any).urlGenerator = undefined;
|
||||
(plugins.discover as any).locator = undefined;
|
||||
|
||||
const isCompatible = await action.isCompatible(context);
|
||||
|
||||
|
@ -189,23 +195,15 @@ describe('"Explore underlying data" panel action', () => {
|
|||
});
|
||||
|
||||
describe('getHref()', () => {
|
||||
test('returns URL path generated by URL generator', async () => {
|
||||
const { action, context } = setup();
|
||||
|
||||
const href = await action.getHref(context);
|
||||
|
||||
expect(href).toBe('/xyz/app/discover/foo#bar');
|
||||
});
|
||||
|
||||
test('calls URL generator with right arguments', async () => {
|
||||
const { action, urlGenerator, context } = setup();
|
||||
const { action, locator, context } = setup();
|
||||
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(0);
|
||||
expect(locator.getLocation).toHaveBeenCalledTimes(0);
|
||||
|
||||
await action.getHref(context);
|
||||
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledTimes(1);
|
||||
expect(urlGenerator.createUrl).toHaveBeenCalledWith({
|
||||
expect(locator.getLocation).toHaveBeenCalledTimes(1);
|
||||
expect(locator.getLocation).toHaveBeenCalledWith({
|
||||
indexPatternId: 'index-ptr-foo',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,8 +12,8 @@ import {
|
|||
IEmbeddable,
|
||||
} from '../../../../../../src/plugins/embeddable/public';
|
||||
import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/public';
|
||||
import { DiscoverUrlGeneratorState } from '../../../../../../src/plugins/discover/public';
|
||||
import { KibanaURL } from '../../../../../../src/plugins/share/public';
|
||||
import { DiscoverAppLocatorParams } from '../../../../../../src/plugins/discover/public';
|
||||
import { KibanaLocation } from '../../../../../../src/plugins/share/public';
|
||||
import * as shared from './shared';
|
||||
import { AbstractExploreDataAction } from './abstract_explore_data_action';
|
||||
|
||||
|
@ -40,29 +40,31 @@ export class ExploreDataContextMenuAction
|
|||
|
||||
public readonly order = 200;
|
||||
|
||||
protected readonly getUrl = async (context: EmbeddableQueryContext): Promise<KibanaURL> => {
|
||||
protected readonly getLocation = async (
|
||||
context: EmbeddableQueryContext
|
||||
): Promise<KibanaLocation> => {
|
||||
const { plugins } = this.params.start();
|
||||
const { urlGenerator } = plugins.discover;
|
||||
const { locator } = plugins.discover;
|
||||
|
||||
if (!urlGenerator) {
|
||||
throw new Error('Discover URL generator not available.');
|
||||
if (!locator) {
|
||||
throw new Error('Discover URL locator not available.');
|
||||
}
|
||||
|
||||
const { embeddable } = context;
|
||||
const state: DiscoverUrlGeneratorState = {};
|
||||
const params: DiscoverAppLocatorParams = {};
|
||||
|
||||
if (embeddable) {
|
||||
state.indexPatternId = shared.getIndexPatterns(embeddable)[0] || undefined;
|
||||
params.indexPatternId = shared.getIndexPatterns(embeddable)[0] || undefined;
|
||||
|
||||
const input = embeddable.getInput();
|
||||
|
||||
if (input.timeRange && !state.timeRange) state.timeRange = input.timeRange;
|
||||
if (input.query) state.query = input.query;
|
||||
if (input.filters) state.filters = [...input.filters, ...(state.filters || [])];
|
||||
if (input.timeRange && !params.timeRange) params.timeRange = input.timeRange;
|
||||
if (input.query) params.query = input.query;
|
||||
if (input.filters) params.filters = [...input.filters, ...(params.filters || [])];
|
||||
}
|
||||
|
||||
const path = await urlGenerator.createUrl(state);
|
||||
const location = await locator.getLocation(params);
|
||||
|
||||
return new KibanaURL(path);
|
||||
return location;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue