Skip to content

Commit

Permalink
basic integration of components with dynamicActionManager
Browse files Browse the repository at this point in the history
  • Loading branch information
Dosant committed Mar 11, 2020
1 parent 12b8a3f commit 0f3ff3e
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 14 deletions.
29 changes: 29 additions & 0 deletions src/plugins/ui_actions/public/actions/dynamic_action_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ export class DynamicActionManager {
this.reviveAction(event);
}

public async updateEvent(
eventId: string,
action: SerializedAction<unknown>,
triggerId = 'VALUE_CLICK_TRIGGER'
) {
const event: SerializedEvent = {
eventId,
triggerId,
action,
};

const oldEvent = await this.params.storage.read(eventId);
this.killAction(oldEvent);
await this.params.storage.update(event);
this.reviveAction(event);
}

public async deleteEvents(eventIds: string[]) {
const eventsToKill = (await this.params.storage.list()).filter(event =>
eventIds.includes(event.eventId)
);
await Promise.all(eventIds.map(eventId => this.params.storage.remove(eventId)));
eventsToKill.forEach(event => this.killAction(event));
}

public async list(): Promise<SerializedEvent[]> {
return await this.params.storage.list();
}

public async count(): Promise<number> {
return await this.params.storage.count();
}
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/ui_actions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export {
ActionStorage as UiActionsActionStorage,
SerializedEvent as UiActionsSerializedEvent,
SerializedAction as UiActionsSerializedAction,
AnyActionFactory,
DynamicActionManager,
} from './actions';
export { buildContextMenuForActions, contextMenuSeparatorAction } from './context_menu';
export {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType<typeof OPEN_FLY
onClose={() => handle.close()}
context={context}
viewMode={'create'}
dynamicActionsManager={context.embeddable.dynamicActions!}
/>
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class FlyoutEditDrilldownAction implements ActionByType<typeof OPEN_FLYOU
onClose={() => handle.close()}
context={context}
viewMode={'manage'}
dynamicActionsManager={context.embeddable.dynamicActions!}
/>
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data';
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage';
import { mockDynamicActionManager } from './test_data';

const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({
advancedUiActions: {
Expand All @@ -29,6 +30,6 @@ const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({

storiesOf('components/FlyoutManageDrilldowns', module).add('default', () => (
<EuiFlyout onClose={() => {}}>
<FlyoutManageDrilldowns context={{}} />
<FlyoutManageDrilldowns context={{}} dynamicActionsManager={mockDynamicActionManager} />
</EuiFlyout>
));
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '../../../../advanced_ui_actions/public/components/action_wizard/test_data';
import { StubBrowserStorage } from '../../../../../../src/test_utils/public/stub_browser_storage';
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
import { mockDynamicActionManager } from './test_data';

const storage = new Storage(new StubBrowserStorage());
const FlyoutManageDrilldowns = createFlyoutManageDrilldowns({
Expand All @@ -35,7 +36,9 @@ beforeEach(() => {
});

test('<FlyoutManageDrilldowns/> should render in manage view and should allow to create new drilldown', async () => {
const screen = render(<FlyoutManageDrilldowns context={{}} />);
const screen = render(
<FlyoutManageDrilldowns context={{}} dynamicActionsManager={mockDynamicActionManager} />
);

// wait for initial render. It is async because resolving compatible action factories is async
await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible());
Expand Down Expand Up @@ -71,7 +74,9 @@ test('<FlyoutManageDrilldowns/> should render in manage view and should allow to
});

test('Should show drilldown welcome message. Should be able to dismiss it', async () => {
let screen = render(<FlyoutManageDrilldowns context={{}} />);
let screen = render(
<FlyoutManageDrilldowns context={{}} dynamicActionsManager={mockDynamicActionManager} />
);

// wait for initial render. It is async because resolving compatible action factories is async
await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible());
Expand All @@ -81,7 +86,9 @@ test('Should show drilldown welcome message. Should be able to dismiss it', asyn
expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull();
cleanup();

screen = render(<FlyoutManageDrilldowns context={{}} />);
screen = render(
<FlyoutManageDrilldowns context={{}} dynamicActionsManager={mockDynamicActionManager} />
);
// wait for initial render. It is async because resolving compatible action factories is async
await wait(() => expect(screen.getByText(/Manage Drilldowns/i)).toBeVisible());
expect(screen.queryByTestId(welcomeMessageTestSubj)).toBeNull();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ import {
AdvancedUiActionsActionFactory as ActionFactory,
AdvancedUiActionsStart,
} from '../../../../advanced_ui_actions/public';
import { FlyoutDrilldownWizard } from '../flyout_drilldown_wizard';
import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard';
import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns';
import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public';

import {
AnyActionFactory,
DynamicActionManager,
UiActionsSerializedEvent,
UiActionsSerializedAction,
} from '../../../../../../src/plugins/ui_actions/public';

interface ConnectedFlyoutManageDrilldownsProps<Context extends object = object> {
context: Context;
dynamicActionsManager: DynamicActionManager;
viewMode?: 'create' | 'manage';
onClose?: () => void;
}
Expand All @@ -38,6 +46,10 @@ export function createFlyoutManageDrilldowns({
// This is ok to assume this is static,
// because all action factories should be registered in setup phase
const allActionFactories = advancedUiActions.actionFactory.getAll();
const allActionFactoriesById = allActionFactories.reduce((acc, next) => {
acc[next.id] = next;
return acc;
}, {} as Record<string, AnyActionFactory>);

return (props: ConnectedFlyoutManageDrilldownsProps) => {
const isCreateOnly = props.viewMode === 'create';
Expand All @@ -50,15 +62,36 @@ export function createFlyoutManageDrilldowns({
const [route, setRoute] = useState<Routes>(
() => (isCreateOnly ? Routes.Create : Routes.Manage) // initial state is different depending on `viewMode`
);
const [currentEditId, setCurrentEditId] = useState<string | null>(null);

const [shouldShowWelcomeMessage, onHideWelcomeMessage] = useWelcomeMessage(storage);

const {
drilldowns,
createDrilldown,
editDrilldown,
deleteDrilldown,
} = useDrilldownsStateManager(props.dynamicActionsManager);

/**
* isCompatible promise is not yet resolved.
* Skip rendering until it is resolved
*/
if (!actionFactories) return null;

function resolveInitialDrilldownWizardConfig(): DrilldownWizardConfig | undefined {
if (route !== Routes.Edit) return undefined;
if (!currentEditId) return undefined;
const drilldownToEdit = drilldowns.find(d => d.eventId === currentEditId);
if (!drilldownToEdit) return undefined;

return {
actionFactory: allActionFactoriesById[drilldownToEdit.action.factoryId],
actionConfig: drilldownToEdit.action.config as object, // TODO: types
name: drilldownToEdit.action.name,
};
}

switch (route) {
case Routes.Create:
case Routes.Edit:
Expand All @@ -68,21 +101,41 @@ export function createFlyoutManageDrilldowns({
onWelcomeHideClick={onHideWelcomeMessage}
drilldownActionFactories={actionFactories}
onClose={props.onClose}
mode={Routes.Create ? 'create' : 'edit'}
mode={route === Routes.Create ? 'create' : 'edit'}
onBack={isCreateOnly ? undefined : () => setRoute(Routes.Manage)}
onSubmit={() => {
onSubmit={({ actionConfig, actionFactory, name }) => {
if (route === Routes.Create) {
createDrilldown({
name,
config: actionConfig,
factoryId: actionFactory.id,
});
} else {
// edit
editDrilldown(currentEditId!, {
name,
config: actionConfig,
factoryId: actionFactory.id,
});
}

if (isCreateOnly) {
if (props.onClose) {
props.onClose();
}
} else {
setRoute(Routes.Manage);
}

setCurrentEditId(null);
}}
onDelete={() => {
deleteDrilldown(currentEditId!);
setRoute(Routes.Manage);
setCurrentEditId(null);
}}
actionFactoryContext={props.context}
initialDrilldownWizardConfig={resolveInitialDrilldownWizardConfig()}
/>
);

Expand All @@ -92,12 +145,23 @@ export function createFlyoutManageDrilldowns({
<FlyoutListManageDrilldowns
showWelcomeMessage={shouldShowWelcomeMessage}
onWelcomeHideClick={onHideWelcomeMessage}
drilldowns={[]}
onDelete={() => {}}
onEdit={() => {
drilldowns={drilldowns.map(drilldown => ({
id: drilldown.eventId,
name: drilldown.action.name,
actionTypeDisplayName:
allActionFactoriesById[drilldown.action.factoryId]?.getDisplayName(props.context) ??
drilldown.action.factoryId,
}))}
onDelete={ids => {
setCurrentEditId(null);
deleteDrilldown(ids);
}}
onEdit={id => {
setCurrentEditId(id);
setRoute(Routes.Edit);
}}
onCreate={() => {
setCurrentEditId(null);
setRoute(Routes.Create);
}}
onClose={props.onClose}
Expand Down Expand Up @@ -146,3 +210,53 @@ function useWelcomeMessage(storage: IStorageWrapper): [boolean, () => void] {
},
];
}

function useDrilldownsStateManager(actionManager: DynamicActionManager) {
const [isLoading, setIsLoading] = useState(false);
const [drilldowns, setDrilldowns] = useState<UiActionsSerializedEvent[]>([]);

function reload() {
setIsLoading(true);
actionManager.list().then(res => {
setDrilldowns(res);
setIsLoading(false);
});
}

useEffect(() => {
reload();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

function createDrilldown(action: UiActionsSerializedAction<any>, triggerId?: string) {
setIsLoading(true);
actionManager.createEvent(action, triggerId).then(() => {
setIsLoading(false);
reload();
});
}

function editDrilldown(
drilldownId: string,
action: UiActionsSerializedAction<any>,
triggerId?: string
) {
setIsLoading(true);
actionManager.updateEvent(drilldownId, action, triggerId).then(() => {
setIsLoading(false);
reload();
});
}

function deleteDrilldown(drilldownIds: string | string[]) {
setIsLoading(true);
actionManager
.deleteEvents(Array.isArray(drilldownIds) ? drilldownIds : [drilldownIds])
.then(() => {
setIsLoading(false);
reload();
});
}

return { drilldowns, isLoading, createDrilldown, editDrilldown, deleteDrilldown };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 {
DynamicActionManager,
UiActionsSerializedAction,
} from '../../../../../../src/plugins/ui_actions/public';

class MockDynamicActionManager implements PublicMethodsOf<DynamicActionManager> {
async count() {
return 0;
}
async createEvent(action: UiActionsSerializedAction<any>, triggerId?: string) {}
async deleteEvents(eventIds: string[]) {}
async list() {
return [];
}
async updateEvent(
eventId: string,
action: UiActionsSerializedAction<unknown>,
triggerId?: string
) {}

async start() {}
async stop() {}
}

export const mockDynamicActionManager = (new MockDynamicActionManager() as unknown) as DynamicActionManager;
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export interface FlyoutDrilldownWizardProps<
> {
drilldownActionFactories: AnyActionFactory[];

onSubmit?: (drilldownWizardConfig: DrilldownWizardConfig) => void;
onSubmit?: (drilldownWizardConfig: Required<DrilldownWizardConfig>) => void;
onDelete?: () => void;
onClose?: () => void;
onBack?: () => void;
Expand Down Expand Up @@ -70,7 +70,9 @@ export function FlyoutDrilldownWizard<
}
);

const isActionValid = (): boolean => {
const isActionValid = (
config: DrilldownWizardConfig
): config is Required<DrilldownWizardConfig> => {
if (!wizardConfig.name) return false;
if (!wizardConfig.actionFactory) return false;
if (!wizardConfig.actionConfig) return false;
Expand All @@ -81,12 +83,12 @@ export function FlyoutDrilldownWizard<
const footer = (
<EuiButton
onClick={() => {
if (isActionValid()) {
if (isActionValid(wizardConfig)) {
onSubmit(wizardConfig);
}
}}
fill
isDisabled={!isActionValid()}
isDisabled={!isActionValid(wizardConfig)}
>
{mode === 'edit' ? txtEditDrilldownButtonLabel : txtCreateDrilldownButtonLabel}
</EuiButton>
Expand Down

0 comments on commit 0f3ff3e

Please sign in to comment.