mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Drilldowns] Config to disable URL Drilldown (#77887)
This pr makes sure there is way to disable URL drilldown feature. I decided to extract Url drilldown definition into a separate plugin to benefit from regular disabling a plugin feature. Having it as a separate plugin also makes sense because we will start adding registries specific to URL drilldown implementation Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
0f8043ca8d
commit
4b6d77fa5d
22 changed files with 151 additions and 36 deletions
|
@ -504,6 +504,10 @@ in their infrastructure.
|
|||
|Contains HTTP endpoints and UiSettings that are slated for removal.
|
||||
|
||||
|
||||
|{kib-repo}blob/{branch}/x-pack/plugins/drilldowns/url_drilldown/README.md[urlDrilldown]
|
||||
|NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin.
|
||||
|
||||
|
||||
|===
|
||||
|
||||
include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1]
|
||||
|
|
|
@ -238,3 +238,14 @@ Tip: Consider using <<helpers, date>> helper for date formatting.
|
|||
| Aggregation field behind the selected range, if available.
|
||||
|
||||
|===
|
||||
|
||||
[float]
|
||||
[[disable]]
|
||||
==== Disable URL drilldown
|
||||
|
||||
You can disable URL drilldown feature on your {kib} instance by disabling the plugin:
|
||||
|
||||
["source","yml"]
|
||||
-----------
|
||||
url_drilldown.enabled: false
|
||||
-----------
|
||||
|
|
|
@ -48,6 +48,7 @@ const createStartContract = (): Start => {
|
|||
executeTriggerActions: jest.fn(),
|
||||
fork: jest.fn(),
|
||||
getAction: jest.fn(),
|
||||
hasAction: jest.fn(),
|
||||
getTrigger: jest.fn(),
|
||||
getTriggerActions: jest.fn((id: TriggerId) => []),
|
||||
getTriggerCompatibleActions: jest.fn(),
|
||||
|
|
|
@ -99,6 +99,10 @@ export class UiActionsService {
|
|||
this.actions.delete(actionId);
|
||||
};
|
||||
|
||||
public readonly hasAction = (actionId: string): boolean => {
|
||||
return this.actions.has(actionId);
|
||||
};
|
||||
|
||||
public readonly attachAction = <T extends TriggerId>(triggerId: T, actionId: string): void => {
|
||||
const trigger = this.triggers.get(triggerId);
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
|
||||
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
|
||||
"xpack.uptime": ["plugins/uptime"],
|
||||
"xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
|
||||
"xpack.watcher": "plugins/watcher",
|
||||
"xpack.observability": "plugins/observability"
|
||||
},
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
# Basic url drilldown implementation
|
||||
## URL drilldown
|
||||
|
||||
> NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to `ui_actions_enhanced` plugin.
|
||||
|
||||
Url drilldown allows navigating to external URL or to internal kibana URL.
|
||||
By using variables in url template result url can be dynamic and depend on user's interaction.
|
||||
|
||||
URL drilldown has 3 sources for variables:
|
||||
|
||||
- Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used.
|
||||
- Context variables are dynamic and different depending on where drilldown is created and used.
|
||||
- Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed.
|
||||
1. Global static variables like, for example, `kibanaUrl`. Such variables won’t change depending on a place where url drilldown is used.
|
||||
2. Context variables are dynamic and different depending on where drilldown is created and used.
|
||||
3. Event variables depend on a trigger context. These variables are dynamically extracted from the action context when drilldown is executed.
|
||||
|
||||
Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel),
|
||||
but `event` variables mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL.
|
||||
|
||||
In current implementation url drilldown has to be used inside the embeddable and with `ValueClickTrigger` or `RangeSelectTrigger`.
|
||||
|
||||
- `context` variables extracted from `embeddable`
|
||||
- `event` variables extracted from `trigger` context
|
||||
* `context` variables extracted from `embeddable`
|
||||
* `event` variables extracted from `trigger` context
|
||||
|
||||
In future this basic url drilldown implementation would allow injecting more variables into `context` (e.g. `dashboard` app specific variables) and would allow providing support for new trigger types from outside.
|
||||
This extensibility improvements are tracked here: https://github.com/elastic/kibana/issues/55324
|
||||
|
||||
In case a solution app has a use case for url drilldown that has to be different from current basic implementation and
|
||||
just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`.
|
||||
just extending variables list is not enough, then recommendation is to create own custom url drilldown and reuse building blocks from `ui_actions_enhanced`.
|
8
x-pack/plugins/drilldowns/url_drilldown/kibana.json
Normal file
8
x-pack/plugins/drilldowns/url_drilldown/kibana.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "urlDrilldown",
|
||||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["embeddable", "uiActions", "uiActionsEnhanced"],
|
||||
"requiredBundles": ["kibanaUtils", "kibanaReact"]
|
||||
}
|
|
@ -4,4 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './url_drilldown';
|
||||
import { PluginInitializerContext } from 'src/core/public';
|
||||
import { UrlDrilldownPlugin } from './plugin';
|
||||
|
||||
export function plugin(context: PluginInitializerContext) {
|
||||
return new UrlDrilldownPlugin(context);
|
||||
}
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const txtUrlDrilldownDisplayName = i18n.translate(
|
||||
'xpack.embeddableEnhanced.drilldowns.urlDrilldownDisplayName',
|
||||
{
|
||||
defaultMessage: 'Go to URL',
|
||||
}
|
||||
);
|
||||
export const txtUrlDrilldownDisplayName = i18n.translate('xpack.urlDrilldown.DisplayName', {
|
||||
defaultMessage: 'Go to URL',
|
||||
});
|
59
x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts
Normal file
59
x-pack/plugins/drilldowns/url_drilldown/public/plugin.ts
Normal 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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public';
|
||||
import { EmbeddableSetup, EmbeddableStart } from '../../../../../src/plugins/embeddable/public';
|
||||
import {
|
||||
AdvancedUiActionsSetup,
|
||||
AdvancedUiActionsStart,
|
||||
urlDrilldownGlobalScopeProvider,
|
||||
} from '../../../ui_actions_enhanced/public';
|
||||
import { UrlDrilldown } from './lib';
|
||||
import { createStartServicesGetter } from '../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export interface SetupDependencies {
|
||||
embeddable: EmbeddableSetup;
|
||||
uiActionsEnhanced: AdvancedUiActionsSetup;
|
||||
}
|
||||
|
||||
export interface StartDependencies {
|
||||
embeddable: EmbeddableStart;
|
||||
uiActionsEnhanced: AdvancedUiActionsStart;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export interface SetupContract {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
export interface StartContract {}
|
||||
|
||||
export class UrlDrilldownPlugin
|
||||
implements Plugin<SetupContract, StartContract, SetupDependencies, StartDependencies> {
|
||||
constructor(protected readonly context: PluginInitializerContext) {}
|
||||
|
||||
public setup(core: CoreSetup<StartDependencies>, plugins: SetupDependencies): SetupContract {
|
||||
const startServices = createStartServicesGetter(core.getStartServices);
|
||||
plugins.uiActionsEnhanced.registerDrilldown(
|
||||
new UrlDrilldown({
|
||||
getGlobalScope: urlDrilldownGlobalScopeProvider({ core }),
|
||||
navigateToUrl: (url: string) =>
|
||||
core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)),
|
||||
getSyntaxHelpDocsLink: () =>
|
||||
startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax,
|
||||
getVariablesHelpDocsLink: () =>
|
||||
startServices().core.docLinks.links.dashboard.urlDrilldownVariables,
|
||||
})
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public start(core: CoreStart, plugins: StartDependencies): StartContract {
|
||||
return {};
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
}
|
|
@ -3,6 +3,5 @@
|
|||
"version": "kibana",
|
||||
"server": false,
|
||||
"ui": true,
|
||||
"requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"],
|
||||
"requiredBundles": ["kibanaUtils"]
|
||||
"requiredPlugins": ["embeddable", "kibanaReact", "uiActions", "uiActionsEnhanced"]
|
||||
}
|
||||
|
|
|
@ -28,11 +28,8 @@ import {
|
|||
UiActionsEnhancedDynamicActionManager as DynamicActionManager,
|
||||
AdvancedUiActionsSetup,
|
||||
AdvancedUiActionsStart,
|
||||
urlDrilldownGlobalScopeProvider,
|
||||
} from '../../ui_actions_enhanced/public';
|
||||
import { PanelNotificationsAction, ACTION_PANEL_NOTIFICATIONS } from './actions';
|
||||
import { UrlDrilldown } from './drilldowns';
|
||||
import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
declare module '../../../../src/plugins/ui_actions/public' {
|
||||
export interface ActionContextMapping {
|
||||
|
@ -64,23 +61,10 @@ export class EmbeddableEnhancedPlugin
|
|||
|
||||
public setup(core: CoreSetup<StartDependencies>, plugins: SetupDependencies): SetupContract {
|
||||
this.setCustomEmbeddableFactoryProvider(plugins);
|
||||
const startServices = createStartServicesGetter(core.getStartServices);
|
||||
const panelNotificationAction = new PanelNotificationsAction();
|
||||
plugins.uiActionsEnhanced.registerAction(panelNotificationAction);
|
||||
plugins.uiActionsEnhanced.attachAction(PANEL_NOTIFICATION_TRIGGER, panelNotificationAction.id);
|
||||
|
||||
plugins.uiActionsEnhanced.registerDrilldown(
|
||||
new UrlDrilldown({
|
||||
getGlobalScope: urlDrilldownGlobalScopeProvider({ core }),
|
||||
navigateToUrl: (url: string) =>
|
||||
core.getStartServices().then(([{ application }]) => application.navigateToUrl(url)),
|
||||
getSyntaxHelpDocsLink: () =>
|
||||
startServices().core.docLinks.links.dashboard.urlDrilldownTemplateSyntax,
|
||||
getVariablesHelpDocsLink: () =>
|
||||
startServices().core.docLinks.links.dashboard.urlDrilldownVariables,
|
||||
})
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -437,8 +437,7 @@ describe('DynamicActionManager', () => {
|
|||
name: 'foo',
|
||||
config: {},
|
||||
};
|
||||
|
||||
await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects;
|
||||
await expect(manager.createEvent(action, ['SELECT_RANGE_TRIGGER'])).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -704,4 +703,18 @@ describe('DynamicActionManager', () => {
|
|||
|
||||
expect(basicAndGoldActions).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("failing to revive/kill an action doesn't fail action manager", async () => {
|
||||
const { manager, uiActions, storage } = setup([event1, event3, event2]);
|
||||
|
||||
uiActions.registerActionFactory(actionFactoryDefinition1);
|
||||
|
||||
await manager.start();
|
||||
|
||||
expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(2);
|
||||
expect(await storage.list()).toEqual([event1, event3, event2]);
|
||||
|
||||
await manager.stop();
|
||||
expect(uiActions.getTriggerActions('VALUE_CLICK_TRIGGER')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,13 @@ export interface DynamicActionManagerParams {
|
|||
storage: ActionStorage;
|
||||
uiActions: Pick<
|
||||
StartContract,
|
||||
'registerAction' | 'attachAction' | 'unregisterAction' | 'detachAction' | 'getActionFactory'
|
||||
| 'registerAction'
|
||||
| 'attachAction'
|
||||
| 'unregisterAction'
|
||||
| 'detachAction'
|
||||
| 'hasAction'
|
||||
| 'getActionFactory'
|
||||
| 'hasActionFactory'
|
||||
>;
|
||||
isCompatible: <C = unknown>(context: C) => Promise<boolean>;
|
||||
}
|
||||
|
@ -73,8 +79,17 @@ export class DynamicActionManager {
|
|||
|
||||
const actionId = this.generateActionId(eventId);
|
||||
|
||||
if (!uiActions.hasActionFactory(action.factoryId)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
`Action factory for action [action.factoryId = ${action.factoryId}] doesn't exist. Skipping action [action.name = ${action.name}] revive.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const factory = uiActions.getActionFactory(event.action.factoryId);
|
||||
const actionDefinition: ActionDefinition = factory.create(action as SerializedAction);
|
||||
|
||||
uiActions.registerAction({
|
||||
...actionDefinition,
|
||||
id: actionId,
|
||||
|
@ -100,6 +115,7 @@ export class DynamicActionManager {
|
|||
protected killAction({ eventId, triggers }: SerializedEvent) {
|
||||
const { uiActions } = this.params;
|
||||
const actionId = this.generateActionId(eventId);
|
||||
if (!uiActions.hasAction(actionId)) return;
|
||||
|
||||
for (const trigger of triggers) uiActions.detachAction(trigger as any, actionId);
|
||||
uiActions.unregisterAction(actionId);
|
||||
|
@ -157,6 +173,7 @@ export class DynamicActionManager {
|
|||
try {
|
||||
const events = await this.params.storage.list();
|
||||
for (const event of events) this.reviveAction(event);
|
||||
|
||||
this.ui.transitions.finishFetching(events);
|
||||
} catch (error) {
|
||||
this.ui.transitions.failFetching(error instanceof Error ? error : { message: String(error) });
|
||||
|
|
|
@ -29,6 +29,7 @@ const createStartContract = (): Start => {
|
|||
...uiActionsPluginMock.createStartContract(),
|
||||
getActionFactories: jest.fn(),
|
||||
getActionFactory: jest.fn(),
|
||||
hasActionFactory: jest.fn(),
|
||||
FlyoutManageDrilldowns: jest.fn(),
|
||||
telemetry: jest.fn(),
|
||||
extract: jest.fn(),
|
||||
|
|
|
@ -61,7 +61,12 @@ export interface StartContract
|
|||
extends UiActionsStart,
|
||||
Pick<
|
||||
UiActionsServiceEnhancements,
|
||||
'getActionFactory' | 'getActionFactories' | 'telemetry' | 'extract' | 'inject'
|
||||
| 'getActionFactory'
|
||||
| 'hasActionFactory'
|
||||
| 'getActionFactories'
|
||||
| 'telemetry'
|
||||
| 'extract'
|
||||
| 'inject'
|
||||
> {
|
||||
FlyoutManageDrilldowns: ReturnType<typeof createFlyoutManageDrilldowns>;
|
||||
}
|
||||
|
|
|
@ -79,6 +79,10 @@ export class UiActionsServiceEnhancements
|
|||
return actionFactory;
|
||||
};
|
||||
|
||||
public readonly hasActionFactory = (actionFactoryId: string): boolean => {
|
||||
return this.actionFactories.has(actionFactoryId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an array of all action factories.
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue