[Drilldowns] misc improvements & fixes (#75276)

Added support for isCompatible. It is checked during execution.
Pass actionFactory context into createConfig, IsConfigValid
Fix bug that selectedTriggers wasn't reset when switching action factories
Check if license is active in action factories
This commit is contained in:
Anton Dosov 2020-08-18 17:50:02 +02:00 committed by GitHub
parent e30220f04c
commit 50f9a97a41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 83 additions and 17 deletions

View file

@ -26,12 +26,12 @@ export interface Configurable<Config extends object = object, Context = object>
/**
* Create default config for this item, used when item is created for the first time.
*/
readonly createConfig: () => Config;
readonly createConfig: (context: Context) => Config;
/**
* Is this config valid. Used to validate user's input before saving.
*/
readonly isConfigValid: (config: Config) => boolean;
readonly isConfigValid: (config: Config, context: Context) => boolean;
/**
* `UiComponent` to be rendered when collecting configuration for this item.

View file

@ -11,6 +11,7 @@ import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../p
import { RangeSelectContext } from '../../../../../src/plugins/embeddable/public';
import { CollectConfigProps } from '../../../../../src/plugins/kibana_utils/public';
import { SELECT_RANGE_TRIGGER } from '../../../../../src/plugins/ui_actions/public';
import { BaseActionFactoryContext } from '../../../../plugins/ui_actions_enhanced/public/dynamic_actions';
export interface Config {
name: string;
@ -52,10 +53,24 @@ export class DashboardHelloWorldOnlyRangeSelectDrilldown
name: '',
});
public readonly isConfigValid = (config: Config): config is Config => {
public readonly isConfigValid = (
config: Config,
context: BaseActionFactoryContext<typeof SELECT_RANGE_TRIGGER>
): config is Config => {
// eslint-disable-next-line no-console
console.log('Showcasing, that can access action factory context:', context);
return !!config.name;
};
/**
* Showcase isCompatible. Disabled drilldown action in case if range.length === 0
*/
isCompatible(config: Config, context: RangeSelectContext): Promise<boolean> {
if (context.data.range.length === 0) return Promise.resolve(false);
return Promise.resolve(true);
}
public readonly execute = async (config: Config, context: RangeSelectContext) => {
alert(`Hello, ${config.name}, your selected range: ${JSON.stringify(context.data.range)}`);
};

View file

@ -220,7 +220,9 @@ export function Demo({ actionFactories }: { actionFactories: Array<ActionFactory
setState({
currentActionFactory: newActionFactory,
config: newActionFactory.createConfig(),
config: newActionFactory.createConfig({
triggers: state.selectedTriggers ?? [],
}),
});
}
@ -255,7 +257,11 @@ export function Demo({ actionFactories }: { actionFactories: Array<ActionFactory
<div>Action Factory Config: {JSON.stringify(state.config)}</div>
<div>
Is config valid:{' '}
{JSON.stringify(state.currentActionFactory?.isConfigValid(state.config!) ?? false)}
{JSON.stringify(
state.currentActionFactory?.isConfigValid(state.config!, {
triggers: state.selectedTriggers ?? [],
}) ?? false
)}
</div>
<div>Picked trigger: {state.selectedTriggers?.[0]}</div>
</>

View file

@ -57,6 +57,7 @@ export interface FlyoutDrilldownWizardProps<
}
function useWizardConfigState(
actionFactoryContext: BaseActionFactoryContext,
initialDrilldownWizardConfig?: DrilldownWizardConfig
): [
DrilldownWizardConfig,
@ -102,7 +103,10 @@ function useWizardConfigState(
setWizardConfig({
...wizardConfig,
actionFactory,
actionConfig: actionConfigCache[actionFactory.id] ?? actionFactory.createConfig(),
actionConfig:
actionConfigCache[actionFactory.id] ??
actionFactory.createConfig(actionFactoryContext),
selectedTriggers: [],
});
} else {
if (wizardConfig.actionFactory?.id) {
@ -147,7 +151,18 @@ export function FlyoutDrilldownWizard<CurrentActionConfig extends object = objec
const [
wizardConfig,
{ setActionFactory, setActionConfig, setName, setSelectedTriggers },
] = useWizardConfigState(initialDrilldownWizardConfig);
] = useWizardConfigState(
{ ...extraActionFactoryContext, triggers: supportedTriggers },
initialDrilldownWizardConfig
);
const actionFactoryContext: BaseActionFactoryContext = useMemo(
() => ({
...extraActionFactoryContext,
triggers: wizardConfig.selectedTriggers ?? [],
}),
[extraActionFactoryContext, wizardConfig.selectedTriggers]
);
const isActionValid = (
config: DrilldownWizardConfig
@ -157,17 +172,12 @@ export function FlyoutDrilldownWizard<CurrentActionConfig extends object = objec
if (!wizardConfig.actionConfig) return false;
if (!wizardConfig.selectedTriggers || wizardConfig.selectedTriggers.length === 0) return false;
return wizardConfig.actionFactory.isConfigValid(wizardConfig.actionConfig);
return wizardConfig.actionFactory.isConfigValid(
wizardConfig.actionConfig,
actionFactoryContext
);
};
const actionFactoryContext: BaseActionFactoryContext = useMemo(
() => ({
...extraActionFactoryContext,
triggers: wizardConfig.selectedTriggers ?? [],
}),
[extraActionFactoryContext, wizardConfig.selectedTriggers]
);
const footer = (
<EuiButton
onClick={() => {

View file

@ -106,6 +106,15 @@ export interface DrilldownDefinition<
*/
getDisplayName: () => string;
/**
* isCompatible during execution
* Could be used to prevent drilldown from execution
*/
isCompatible?(
config: Config,
context: ExecutionContext | ActionExecutionContext<ExecutionContext>
): Promise<boolean>;
/**
* Implements the "navigation" action of the drilldown. This happens when
* user clicks something in the UI that executes a trigger to which this

View file

@ -37,6 +37,14 @@ describe('License & ActionFactory', () => {
expect(factory.isCompatibleLicence()).toBe(false);
});
test('licence has expired', async () => {
const factory = new ActionFactory({ ...def, minimalLicense: 'gold' }, () =>
licensingMock.createLicense({ license: { type: 'gold', status: 'expired' } })
);
expect(await factory.isCompatible({ triggers: [] })).toBe(true);
expect(factory.isCompatibleLicence()).toBe(false);
});
test('enough license level', async () => {
const factory = new ActionFactory({ ...def, minimalLicense: 'gold' }, () =>
licensingMock.createLicense({ license: { type: 'gold' } })

View file

@ -70,7 +70,8 @@ export class ActionFactory<
*/
public isCompatibleLicence() {
if (!this.minimalLicense) return true;
return this.getLicence().hasAtLeast(this.minimalLicense);
const licence = this.getLicence();
return licence.isAvailable && licence.isActive && licence.hasAtLeast(this.minimalLicense);
}
public create(

View file

@ -75,5 +75,18 @@ describe('UiActionsService', () => {
'Action factory [actionFactoryId = UNKNOWN_ID] does not exist.'
);
});
test('isCompatible from definition is used on registered factory', async () => {
const service = new UiActionsServiceEnhancements({ getLicenseInfo });
service.registerActionFactory({
...factoryDefinition1,
isCompatible: () => Promise.resolve(false),
});
await expect(
service.getActionFactory(factoryDefinition1.id).isCompatible({ triggers: [] })
).resolves.toBe(false);
});
});
});

View file

@ -95,6 +95,7 @@ export class UiActionsServiceEnhancements {
getHref,
minimalLicense,
supportedTriggers,
isCompatible,
}: DrilldownDefinition<Config, SupportedTriggers, FactoryContext, ExecutionContext>): void => {
const actionFactory: ActionFactoryDefinition<
Config,
@ -119,6 +120,9 @@ export class UiActionsServiceEnhancements {
getDisplayName: () => serializedAction.name,
execute: async (context) => await execute(serializedAction.config, context),
getHref: getHref ? async (context) => getHref(serializedAction.config, context) : undefined,
isCompatible: isCompatible
? async (context) => isCompatible(serializedAction.config, context)
: undefined,
}),
} as ActionFactoryDefinition<Config, SupportedTriggers, FactoryContext, ExecutionContext>;