mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Delete CellActions from UI actions plugin (#149522)
## Summary This PR only deletes the component from the UI action plugin. @semd has already added the component to a new package here https://github.com/elastic/kibana/pull/149057 Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c4a0ed2c19
commit
482d1ced82
26 changed files with 0 additions and 1596 deletions
|
@ -42,7 +42,6 @@ const STORYBOOKS = [
|
||||||
'security_solution',
|
'security_solution',
|
||||||
'shared_ux',
|
'shared_ux',
|
||||||
'triggers_actions_ui',
|
'triggers_actions_ui',
|
||||||
'ui_actions',
|
|
||||||
'ui_actions_enhanced',
|
'ui_actions_enhanced',
|
||||||
'language_documentation_popover',
|
'language_documentation_popover',
|
||||||
'unified_search',
|
'unified_search',
|
||||||
|
|
|
@ -47,6 +47,5 @@ export const storybookAliases = {
|
||||||
threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook',
|
threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook',
|
||||||
triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook',
|
triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook',
|
||||||
ui_actions_enhanced: 'src/plugins/ui_actions_enhanced/.storybook',
|
ui_actions_enhanced: 'src/plugins/ui_actions_enhanced/.storybook',
|
||||||
ui_actions: 'src/plugins/ui_actions/.storybook',
|
|
||||||
unified_search: 'src/plugins/unified_search/.storybook',
|
unified_search: 'src/plugins/unified_search/.storybook',
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const defaultConfig = require('@kbn/storybook').defaultConfig;
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
...defaultConfig,
|
|
||||||
stories: ['../**/*.stories.tsx'],
|
|
||||||
reactOptions: {
|
|
||||||
strictMode: true,
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,17 +0,0 @@
|
||||||
This package provides a uniform interface for displaying UI actions for a cell.
|
|
||||||
For the `CellActions` component to work, it must be wrapped by `CellActionsContextProvider`. Ideally, the wrapper should stay on the top of the rendering tree.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```JSX
|
|
||||||
<CellActionsContextProvider
|
|
||||||
// call uiActions.getTriggerCompatibleActions(triggerId, data)
|
|
||||||
getCompatibleActions={getCompatibleActions}>
|
|
||||||
...
|
|
||||||
<CellActions mode={CellActionsMode.HOVER_POPOVER} triggerId={MY_TRIGGER_ID} config={{ field: 'fieldName', value: 'fieldValue', fieldType: 'text' }}>
|
|
||||||
Hover me
|
|
||||||
</CellActions>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
`CellActions` component will display all compatible actions registered for the trigger id.
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { ActionItem } from './cell_action_item';
|
|
||||||
|
|
||||||
describe('ActionItem', () => {
|
|
||||||
it('renders', () => {
|
|
||||||
const action = makeAction('test-action');
|
|
||||||
const actionContext = {} as CellActionExecutionContext;
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<ActionItem action={action} actionContext={actionContext} showTooltip={false} />
|
|
||||||
);
|
|
||||||
expect(queryByTestId('actionItem-test-action')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders tooltip when showTooltip=true is received', () => {
|
|
||||||
const action = makeAction('test-action');
|
|
||||||
const actionContext = {} as CellActionExecutionContext;
|
|
||||||
const { container } = render(
|
|
||||||
<ActionItem action={action} actionContext={actionContext} showTooltip />
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container.querySelector('.euiToolTipAnchor')).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 React, { useMemo } from 'react';
|
|
||||||
|
|
||||||
import { EuiButtonIcon, EuiToolTip, IconType } from '@elastic/eui';
|
|
||||||
import type { Action } from '../../actions';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
|
|
||||||
export const ActionItem = ({
|
|
||||||
action,
|
|
||||||
actionContext,
|
|
||||||
showTooltip,
|
|
||||||
}: {
|
|
||||||
action: Action;
|
|
||||||
actionContext: CellActionExecutionContext;
|
|
||||||
showTooltip: boolean;
|
|
||||||
}) => {
|
|
||||||
const actionProps = useMemo(
|
|
||||||
() => ({
|
|
||||||
iconType: action.getIconType(actionContext) as IconType,
|
|
||||||
onClick: () => action.execute(actionContext),
|
|
||||||
'data-test-subj': `actionItem-${action.id}`,
|
|
||||||
'aria-label': action.getDisplayName(actionContext),
|
|
||||||
}),
|
|
||||||
[action, actionContext]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!actionProps.iconType) return null;
|
|
||||||
|
|
||||||
return showTooltip ? (
|
|
||||||
<EuiToolTip
|
|
||||||
content={action.getDisplayNameTooltip ? action.getDisplayNameTooltip(actionContext) : ''}
|
|
||||||
>
|
|
||||||
<EuiButtonIcon {...actionProps} iconSize="s" />
|
|
||||||
</EuiToolTip>
|
|
||||||
) : (
|
|
||||||
<EuiButtonIcon {...actionProps} iconSize="s" />
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 React from 'react';
|
|
||||||
import { ComponentStory } from '@storybook/react';
|
|
||||||
import { CellActionsContextProvider } from './cell_actions_context';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { CellActions, CellActionsMode, CellActionsProps } from './cell_actions';
|
|
||||||
|
|
||||||
const TRIGGER_ID = 'testTriggerId';
|
|
||||||
|
|
||||||
const FIELD = { name: 'name', value: '123', type: 'text' };
|
|
||||||
|
|
||||||
const getCompatibleActions = () =>
|
|
||||||
Promise.resolve([
|
|
||||||
makeAction('Filter in', 'plusInCircle', 2),
|
|
||||||
makeAction('Filter out', 'minusInCircle', 3),
|
|
||||||
makeAction('Minimize', 'minimize', 1),
|
|
||||||
makeAction('Send email', 'email', 4),
|
|
||||||
makeAction('Pin field', 'pin', 5),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default {
|
|
||||||
title: 'CellAction',
|
|
||||||
decorators: [
|
|
||||||
(storyFn: Function) => (
|
|
||||||
<CellActionsContextProvider
|
|
||||||
// call uiActions getTriggerCompatibleActions(triggerId, data)
|
|
||||||
getTriggerCompatibleActions={getCompatibleActions}
|
|
||||||
>
|
|
||||||
<div style={{ paddingTop: '70px' }} />
|
|
||||||
{storyFn()}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const CellActionsTemplate: ComponentStory<React.FC<CellActionsProps>> = (args) => (
|
|
||||||
<CellActions {...args}>Field value</CellActions>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const DefaultWithControls = CellActionsTemplate.bind({});
|
|
||||||
|
|
||||||
DefaultWithControls.argTypes = {
|
|
||||||
mode: {
|
|
||||||
options: [CellActionsMode.HOVER_POPOVER, CellActionsMode.ALWAYS_VISIBLE],
|
|
||||||
defaultValue: CellActionsMode.HOVER_POPOVER,
|
|
||||||
control: {
|
|
||||||
type: 'radio',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
DefaultWithControls.args = {
|
|
||||||
showActionTooltips: true,
|
|
||||||
mode: CellActionsMode.ALWAYS_VISIBLE,
|
|
||||||
triggerId: TRIGGER_ID,
|
|
||||||
field: FIELD,
|
|
||||||
visibleCellActions: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CellActionInline = ({}: {}) => (
|
|
||||||
<CellActions mode={CellActionsMode.ALWAYS_VISIBLE} triggerId={TRIGGER_ID} field={FIELD}>
|
|
||||||
Field value
|
|
||||||
</CellActions>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const CellActionHoverPopup = ({}: {}) => (
|
|
||||||
<CellActions mode={CellActionsMode.HOVER_POPOVER} triggerId={TRIGGER_ID} field={FIELD}>
|
|
||||||
Hover me
|
|
||||||
</CellActions>
|
|
||||||
);
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { act, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { CellActions, CellActionsMode } from './cell_actions';
|
|
||||||
import { CellActionsContextProvider } from './cell_actions_context';
|
|
||||||
|
|
||||||
const TRIGGER_ID = 'test-trigger-id';
|
|
||||||
const FIELD = { name: 'name', value: '123', type: 'text' };
|
|
||||||
|
|
||||||
describe('CellActions', () => {
|
|
||||||
it('renders', async () => {
|
|
||||||
const getActionsPromise = Promise.resolve([]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<CellActions mode={CellActionsMode.ALWAYS_VISIBLE} triggerId={TRIGGER_ID} field={FIELD}>
|
|
||||||
Field value
|
|
||||||
</CellActions>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByTestId('cellActions')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders InlineActions when mode is ALWAYS_VISIBLE', async () => {
|
|
||||||
const getActionsPromise = Promise.resolve([]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<CellActions mode={CellActionsMode.ALWAYS_VISIBLE} triggerId={TRIGGER_ID} field={FIELD}>
|
|
||||||
Field value
|
|
||||||
</CellActions>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByTestId('inlineActions')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders HoverActionsPopover when mode is HOVER_POPOVER', async () => {
|
|
||||||
const getActionsPromise = Promise.resolve([]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<CellActions mode={CellActionsMode.HOVER_POPOVER} triggerId={TRIGGER_ID} field={FIELD}>
|
|
||||||
Field value
|
|
||||||
</CellActions>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByTestId('hoverActionsPopover')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 React, { useMemo, useRef } from 'react';
|
|
||||||
import type { ActionExecutionContext } from '../../actions';
|
|
||||||
import { InlineActions } from './inline_actions';
|
|
||||||
import { HoverActionsPopover } from './hover_actions_popover';
|
|
||||||
|
|
||||||
export interface CellActionField {
|
|
||||||
/**
|
|
||||||
* Field name.
|
|
||||||
* Example: 'host.name'
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* Field type.
|
|
||||||
* Example: 'keyword'
|
|
||||||
*/
|
|
||||||
type: string;
|
|
||||||
/**
|
|
||||||
* Field value.
|
|
||||||
* Example: 'My-Laptop'
|
|
||||||
*/
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CellActionExecutionContext extends ActionExecutionContext {
|
|
||||||
/**
|
|
||||||
* Ref to a DOM node where the action can add custom HTML.
|
|
||||||
*/
|
|
||||||
extraContentNodeRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ref to the node where the cell action are rendered.
|
|
||||||
*/
|
|
||||||
nodeRef: React.MutableRefObject<HTMLDivElement | null>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extra configurations for actions.
|
|
||||||
*/
|
|
||||||
metadata?: Record<string, unknown>;
|
|
||||||
|
|
||||||
field: CellActionField;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum CellActionsMode {
|
|
||||||
HOVER_POPOVER = 'hover-popover',
|
|
||||||
ALWAYS_VISIBLE = 'always-visible',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CellActionsProps {
|
|
||||||
/**
|
|
||||||
* Common set of properties used by most actions.
|
|
||||||
*/
|
|
||||||
field: CellActionField;
|
|
||||||
/**
|
|
||||||
* The trigger in which the actions are registered.
|
|
||||||
*/
|
|
||||||
triggerId: string;
|
|
||||||
/**
|
|
||||||
* UI configuration. Possible options are `HOVER_POPOVER` and `ALWAYS_VISIBLE`.
|
|
||||||
*
|
|
||||||
* `HOVER_POPOVER` shows the actions when the children component is hovered.
|
|
||||||
*
|
|
||||||
* `ALWAYS_VISIBLE` always shows the actions.
|
|
||||||
*/
|
|
||||||
mode: CellActionsMode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* It displays a tooltip for every action button when `true`.
|
|
||||||
*/
|
|
||||||
showActionTooltips?: boolean;
|
|
||||||
/**
|
|
||||||
* It shows 'more actions' button when the number of actions is bigger than this parameter.
|
|
||||||
*/
|
|
||||||
visibleCellActions?: number;
|
|
||||||
/**
|
|
||||||
* Custom set of properties used by some actions.
|
|
||||||
* An action might require a specific set of metadata properties to render.
|
|
||||||
* This data is sent directly to actions.
|
|
||||||
*/
|
|
||||||
metadata?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CellActions: React.FC<CellActionsProps> = ({
|
|
||||||
field,
|
|
||||||
triggerId,
|
|
||||||
children,
|
|
||||||
mode,
|
|
||||||
showActionTooltips = true,
|
|
||||||
visibleCellActions = 3,
|
|
||||||
metadata,
|
|
||||||
}) => {
|
|
||||||
const extraContentNodeRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const nodeRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const actionContext: CellActionExecutionContext = useMemo(
|
|
||||||
() => ({
|
|
||||||
field,
|
|
||||||
trigger: { id: triggerId },
|
|
||||||
extraContentNodeRef,
|
|
||||||
nodeRef,
|
|
||||||
metadata,
|
|
||||||
}),
|
|
||||||
[field, triggerId, metadata]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mode === CellActionsMode.HOVER_POPOVER) {
|
|
||||||
return (
|
|
||||||
<div ref={nodeRef} data-test-subj={'cellActions'}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={showActionTooltips}
|
|
||||||
visibleCellActions={visibleCellActions}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</HoverActionsPopover>
|
|
||||||
|
|
||||||
<div ref={extraContentNodeRef} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={nodeRef} data-test-subj={'cellActions'}>
|
|
||||||
{children}
|
|
||||||
<InlineActions
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={showActionTooltips}
|
|
||||||
visibleCellActions={visibleCellActions}
|
|
||||||
/>
|
|
||||||
<div ref={extraContentNodeRef} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,155 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { act, renderHook } from '@testing-library/react-hooks';
|
|
||||||
import React from 'react';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import {
|
|
||||||
CellActionsContextProvider,
|
|
||||||
useLoadActions,
|
|
||||||
useLoadActionsFn,
|
|
||||||
} from './cell_actions_context';
|
|
||||||
|
|
||||||
describe('CellActionsContextProvider', () => {
|
|
||||||
const actionContext = { trigger: { id: 'triggerId' } } as CellActionExecutionContext;
|
|
||||||
|
|
||||||
it('loads actions when useLoadActionsFn callback is called', async () => {
|
|
||||||
const action = makeAction('action-1', 'icon', 1);
|
|
||||||
const getActionsPromise = Promise.resolve([action]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useLoadActionsFn(),
|
|
||||||
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const [{ value: valueBeforeFnCalled }, loadActions] = result.current;
|
|
||||||
|
|
||||||
// value is undefined before loadActions is called
|
|
||||||
expect(valueBeforeFnCalled).toBeUndefined();
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
loadActions(actionContext);
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [{ value: valueAfterFnCalled }] = result.current;
|
|
||||||
|
|
||||||
expect(valueAfterFnCalled).toEqual([action]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('loads actions when useLoadActions called', async () => {
|
|
||||||
const action = makeAction('action-1', 'icon', 1);
|
|
||||||
const getActionsPromise = Promise.resolve([action]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useLoadActions(actionContext),
|
|
||||||
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.value).toEqual([action]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorts actions by order', async () => {
|
|
||||||
const firstAction = makeAction('action-1', 'icon', 1);
|
|
||||||
const secondAction = makeAction('action-2', 'icon', 2);
|
|
||||||
const getActionsPromise = Promise.resolve([secondAction, firstAction]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useLoadActions(actionContext),
|
|
||||||
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.value).toEqual([firstAction, secondAction]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorts actions by id when order is undefined', async () => {
|
|
||||||
const firstAction = makeAction('action-1');
|
|
||||||
const secondAction = makeAction('action-2');
|
|
||||||
|
|
||||||
const getActionsPromise = Promise.resolve([secondAction, firstAction]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useLoadActions(actionContext),
|
|
||||||
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.value).toEqual([firstAction, secondAction]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('sorts actions by id and order', async () => {
|
|
||||||
const actionWithoutOrder = makeAction('action-1-no-order');
|
|
||||||
const secondAction = makeAction('action-2', 'icon', 2);
|
|
||||||
const thirdAction = makeAction('action-3', 'icon', 3);
|
|
||||||
|
|
||||||
const getActionsPromise = Promise.resolve([secondAction, actionWithoutOrder, thirdAction]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => useLoadActions(actionContext),
|
|
||||||
|
|
||||||
{
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.value).toEqual([secondAction, thirdAction, actionWithoutOrder]);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { orderBy } from 'lodash/fp';
|
|
||||||
import React, { createContext, FC, useCallback, useContext } from 'react';
|
|
||||||
import useAsync from 'react-use/lib/useAsync';
|
|
||||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
|
||||||
import type { Action } from '../../actions';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
|
|
||||||
// It must to match `UiActionsService.getTriggerCompatibleActions`
|
|
||||||
type GetTriggerCompatibleActionsType = (triggerId: string, context: object) => Promise<Action[]>;
|
|
||||||
|
|
||||||
type GetActionsType = (context: CellActionExecutionContext) => Promise<Action[]>;
|
|
||||||
|
|
||||||
const CellActionsContext = createContext<{ getActions: GetActionsType } | null>(null);
|
|
||||||
|
|
||||||
interface CellActionsContextProviderProps {
|
|
||||||
/**
|
|
||||||
* Please assign `uiActions.getTriggerCompatibleActions` function.
|
|
||||||
* This function should return a list of actions for a triggerId that are compatible with the provided context.
|
|
||||||
*/
|
|
||||||
getTriggerCompatibleActions: GetTriggerCompatibleActionsType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CellActionsContextProvider: FC<CellActionsContextProviderProps> = ({
|
|
||||||
children,
|
|
||||||
getTriggerCompatibleActions,
|
|
||||||
}) => {
|
|
||||||
const getSortedCompatibleActions = useCallback<GetActionsType>(
|
|
||||||
(context) =>
|
|
||||||
getTriggerCompatibleActions(context.trigger.id, context).then((actions) =>
|
|
||||||
orderBy(['order', 'id'], ['asc', 'asc'], actions)
|
|
||||||
),
|
|
||||||
[getTriggerCompatibleActions]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CellActionsContext.Provider value={{ getActions: getSortedCompatibleActions }}>
|
|
||||||
{children}
|
|
||||||
</CellActionsContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const useCellActions = () => {
|
|
||||||
const context = useContext(CellActionsContext);
|
|
||||||
if (!context) {
|
|
||||||
throw new Error(
|
|
||||||
'No CellActionsContext found. Please wrap the application with CellActionsContextProvider'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useLoadActions = (context: CellActionExecutionContext) => {
|
|
||||||
const { getActions } = useCellActions();
|
|
||||||
return useAsync(() => getActions(context), []);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useLoadActionsFn = () => {
|
|
||||||
const { getActions } = useCellActions();
|
|
||||||
return useAsyncFn(getActions, []);
|
|
||||||
};
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { fireEvent, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { ExtraActionsButton } from './extra_actions_button';
|
|
||||||
|
|
||||||
describe('ExtraActionsButton', () => {
|
|
||||||
it('renders', () => {
|
|
||||||
const { queryByTestId } = render(<ExtraActionsButton onClick={() => {}} showTooltip={false} />);
|
|
||||||
|
|
||||||
expect(queryByTestId('showExtraActionsButton')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders tooltip when showTooltip=true is received', () => {
|
|
||||||
const { container } = render(<ExtraActionsButton onClick={() => {}} showTooltip />);
|
|
||||||
expect(container.querySelector('.euiToolTipAnchor')).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls onClick when button is clicked', () => {
|
|
||||||
const onClick = jest.fn();
|
|
||||||
const { getByTestId } = render(<ExtraActionsButton onClick={onClick} showTooltip />);
|
|
||||||
|
|
||||||
fireEvent.click(getByTestId('showExtraActionsButton'));
|
|
||||||
expect(onClick).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
|
||||||
import React from 'react';
|
|
||||||
import { SHOW_MORE_ACTIONS } from './translations';
|
|
||||||
|
|
||||||
interface ExtraActionsButtonProps {
|
|
||||||
onClick: () => void;
|
|
||||||
showTooltip: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExtraActionsButton: React.FC<ExtraActionsButtonProps> = ({ onClick, showTooltip }) =>
|
|
||||||
showTooltip ? (
|
|
||||||
<EuiToolTip content={SHOW_MORE_ACTIONS}>
|
|
||||||
<EuiButtonIcon
|
|
||||||
data-test-subj="showExtraActionsButton"
|
|
||||||
aria-label={SHOW_MORE_ACTIONS}
|
|
||||||
iconType="boxesHorizontal"
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
</EuiToolTip>
|
|
||||||
) : (
|
|
||||||
<EuiButtonIcon
|
|
||||||
data-test-subj="showExtraActionsButton"
|
|
||||||
aria-label={SHOW_MORE_ACTIONS}
|
|
||||||
iconType="boxesHorizontal"
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
);
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { act, fireEvent, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { ExtraActionsPopOver, ExtraActionsPopOverWithAnchor } from './extra_actions_popover';
|
|
||||||
|
|
||||||
const actionContext = { field: { name: 'fieldName' } } as CellActionExecutionContext;
|
|
||||||
describe('ExtraActionsPopOver', () => {
|
|
||||||
it('renders', () => {
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<ExtraActionsPopOver
|
|
||||||
actionContext={actionContext}
|
|
||||||
isOpen={false}
|
|
||||||
closePopOver={() => {}}
|
|
||||||
actions={[]}
|
|
||||||
button={<span />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByTestId('extraActionsPopOver')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('executes action and close popover when menu item is clicked', async () => {
|
|
||||||
const executeAction = jest.fn();
|
|
||||||
const closePopOver = jest.fn();
|
|
||||||
const action = { ...makeAction('test-action'), execute: executeAction };
|
|
||||||
const { getByLabelText } = render(
|
|
||||||
<ExtraActionsPopOver
|
|
||||||
actionContext={actionContext}
|
|
||||||
isOpen={true}
|
|
||||||
closePopOver={closePopOver}
|
|
||||||
actions={[action]}
|
|
||||||
button={<span />}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await fireEvent.click(getByLabelText('test-action'));
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(executeAction).toHaveBeenCalled();
|
|
||||||
expect(closePopOver).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ExtraActionsPopOverWithAnchor', () => {
|
|
||||||
const anchorElement = document.createElement('span');
|
|
||||||
document.body.appendChild(anchorElement);
|
|
||||||
|
|
||||||
it('renders', () => {
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<ExtraActionsPopOverWithAnchor
|
|
||||||
actionContext={actionContext}
|
|
||||||
isOpen={false}
|
|
||||||
closePopOver={() => {}}
|
|
||||||
actions={[]}
|
|
||||||
anchorRef={{ current: anchorElement }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(queryByTestId('extraActionsPopOverWithAnchor')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('executes action and close popover when menu item is clicked', () => {
|
|
||||||
const executeAction = jest.fn();
|
|
||||||
const closePopOver = jest.fn();
|
|
||||||
const action = { ...makeAction('test-action'), execute: executeAction };
|
|
||||||
const { getByLabelText } = render(
|
|
||||||
<ExtraActionsPopOverWithAnchor
|
|
||||||
actionContext={actionContext}
|
|
||||||
isOpen={true}
|
|
||||||
closePopOver={closePopOver}
|
|
||||||
actions={[action]}
|
|
||||||
anchorRef={{ current: anchorElement }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
fireEvent.click(getByLabelText('test-action'));
|
|
||||||
|
|
||||||
expect(executeAction).toHaveBeenCalled();
|
|
||||||
expect(closePopOver).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,133 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 {
|
|
||||||
EuiContextMenuItem,
|
|
||||||
EuiContextMenuPanel,
|
|
||||||
EuiPopover,
|
|
||||||
EuiScreenReaderOnly,
|
|
||||||
EuiWrappingPopover,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { euiThemeVars } from '@kbn/ui-theme';
|
|
||||||
import { css } from '@emotion/react';
|
|
||||||
import type { Action } from '../../actions';
|
|
||||||
import { EXTRA_ACTIONS_ARIA_LABEL, YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS } from './translations';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
|
|
||||||
const euiContextMenuItemCSS = css`
|
|
||||||
color: ${euiThemeVars.euiColorPrimaryText};
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface ActionsPopOverProps {
|
|
||||||
actionContext: CellActionExecutionContext;
|
|
||||||
isOpen: boolean;
|
|
||||||
closePopOver: () => void;
|
|
||||||
actions: Action[];
|
|
||||||
button: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExtraActionsPopOver: React.FC<ActionsPopOverProps> = ({
|
|
||||||
actions,
|
|
||||||
actionContext,
|
|
||||||
isOpen,
|
|
||||||
closePopOver,
|
|
||||||
button,
|
|
||||||
}) => (
|
|
||||||
<EuiPopover
|
|
||||||
button={button}
|
|
||||||
isOpen={isOpen}
|
|
||||||
closePopover={closePopOver}
|
|
||||||
panelPaddingSize="xs"
|
|
||||||
anchorPosition={'downCenter'}
|
|
||||||
hasArrow
|
|
||||||
repositionOnScroll
|
|
||||||
ownFocus
|
|
||||||
data-test-subj="extraActionsPopOver"
|
|
||||||
aria-label={EXTRA_ACTIONS_ARIA_LABEL}
|
|
||||||
>
|
|
||||||
<ExtraActionsPopOverContent
|
|
||||||
actions={actions}
|
|
||||||
actionContext={actionContext}
|
|
||||||
closePopOver={closePopOver}
|
|
||||||
/>
|
|
||||||
</EuiPopover>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface ExtraActionsPopOverWithAnchorProps
|
|
||||||
extends Pick<ActionsPopOverProps, 'actionContext' | 'closePopOver' | 'isOpen' | 'actions'> {
|
|
||||||
anchorRef: React.RefObject<HTMLElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ExtraActionsPopOverWithAnchor = ({
|
|
||||||
anchorRef,
|
|
||||||
actionContext,
|
|
||||||
isOpen,
|
|
||||||
closePopOver,
|
|
||||||
actions,
|
|
||||||
}: ExtraActionsPopOverWithAnchorProps) => {
|
|
||||||
return anchorRef.current ? (
|
|
||||||
<EuiWrappingPopover
|
|
||||||
aria-label={EXTRA_ACTIONS_ARIA_LABEL}
|
|
||||||
button={anchorRef.current}
|
|
||||||
isOpen={isOpen}
|
|
||||||
closePopover={closePopOver}
|
|
||||||
panelPaddingSize="xs"
|
|
||||||
anchorPosition={'downCenter'}
|
|
||||||
hasArrow={false}
|
|
||||||
repositionOnScroll
|
|
||||||
ownFocus
|
|
||||||
attachToAnchor={false}
|
|
||||||
data-test-subj="extraActionsPopOverWithAnchor"
|
|
||||||
>
|
|
||||||
<ExtraActionsPopOverContent
|
|
||||||
actions={actions}
|
|
||||||
actionContext={actionContext}
|
|
||||||
closePopOver={closePopOver}
|
|
||||||
/>
|
|
||||||
</EuiWrappingPopover>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ExtraActionsPopOverContentProps = Pick<
|
|
||||||
ActionsPopOverProps,
|
|
||||||
'actionContext' | 'closePopOver' | 'actions'
|
|
||||||
>;
|
|
||||||
|
|
||||||
const ExtraActionsPopOverContent: React.FC<ExtraActionsPopOverContentProps> = ({
|
|
||||||
actionContext,
|
|
||||||
actions,
|
|
||||||
closePopOver,
|
|
||||||
}) => {
|
|
||||||
const items = useMemo(
|
|
||||||
() =>
|
|
||||||
actions.map((action) => (
|
|
||||||
<EuiContextMenuItem
|
|
||||||
css={euiContextMenuItemCSS}
|
|
||||||
key={action.id}
|
|
||||||
icon={action.getIconType(actionContext)}
|
|
||||||
aria-label={action.getDisplayName(actionContext)}
|
|
||||||
onClick={() => {
|
|
||||||
closePopOver();
|
|
||||||
action.execute(actionContext);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{action.getDisplayName(actionContext)}
|
|
||||||
</EuiContextMenuItem>
|
|
||||||
)),
|
|
||||||
[actionContext, actions, closePopOver]
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<EuiScreenReaderOnly>
|
|
||||||
<p>{YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(actionContext.field.name)}</p>
|
|
||||||
</EuiScreenReaderOnly>
|
|
||||||
<EuiContextMenuPanel size="s" items={items} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { act, fireEvent, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { HoverActionsPopover } from './hover_actions_popover';
|
|
||||||
import { CellActionsContextProvider } from './cell_actions_context';
|
|
||||||
|
|
||||||
describe('HoverActionsPopover', () => {
|
|
||||||
const actionContext = {
|
|
||||||
trigger: { id: 'triggerId' },
|
|
||||||
field: { name: 'fieldName' },
|
|
||||||
} as CellActionExecutionContext;
|
|
||||||
const TestComponent = () => <span data-test-subj="test-component" />;
|
|
||||||
jest.useFakeTimers();
|
|
||||||
|
|
||||||
it('renders', () => {
|
|
||||||
const getActions = () => Promise.resolve([]);
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
children={null}
|
|
||||||
visibleCellActions={4}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
/>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
expect(queryByTestId('hoverActionsPopover')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders actions when hovered', async () => {
|
|
||||||
const action = makeAction('test-action');
|
|
||||||
const getActionsPromise = Promise.resolve([action]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { queryByLabelText, getByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
visibleCellActions={4}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</HoverActionsPopover>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByLabelText('test-action')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('hide actions when mouse stops hovering', async () => {
|
|
||||||
const action = makeAction('test-action');
|
|
||||||
const getActionsPromise = Promise.resolve([action]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { queryByLabelText, getByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
visibleCellActions={4}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</HoverActionsPopover>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mouse leaves hover state
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.mouseLeave(getByTestId('test-component'));
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByLabelText('test-action')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders extra actions button', async () => {
|
|
||||||
const actions = [makeAction('test-action-1'), makeAction('test-action-2')];
|
|
||||||
const getActionsPromise = Promise.resolve(actions);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
visibleCellActions={1}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</HoverActionsPopover>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getByTestId('showExtraActionsButton')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows extra actions when extra actions button is clicked', async () => {
|
|
||||||
const actions = [makeAction('test-action-1'), makeAction('test-action-2')];
|
|
||||||
const getActionsPromise = Promise.resolve(actions);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { getByTestId, getByLabelText } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
visibleCellActions={1}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</HoverActionsPopover>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.click(getByTestId('showExtraActionsButton'));
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(getByLabelText('test-action-2')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render visible actions if extra actions are already rendered', async () => {
|
|
||||||
const actions = [
|
|
||||||
makeAction('test-action-1'),
|
|
||||||
// extra actions
|
|
||||||
makeAction('test-action-2'),
|
|
||||||
makeAction('test-action-3'),
|
|
||||||
];
|
|
||||||
const getActionsPromise = Promise.resolve(actions);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
|
|
||||||
const { getByTestId, queryByLabelText } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<HoverActionsPopover
|
|
||||||
visibleCellActions={2}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
>
|
|
||||||
<TestComponent />
|
|
||||||
</HoverActionsPopover>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.click(getByTestId('showExtraActionsButton'));
|
|
||||||
});
|
|
||||||
|
|
||||||
await hoverElement(getByTestId('test-component'), async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
jest.runAllTimers();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByLabelText('test-action-1')).not.toBeInTheDocument();
|
|
||||||
expect(queryByLabelText('test-action-2')).toBeInTheDocument();
|
|
||||||
expect(queryByLabelText('test-action-3')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const hoverElement = async (element: Element, waitForChange: () => Promise<unknown>) => {
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.mouseEnter(element);
|
|
||||||
await waitForChange();
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,168 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { EuiPopover, EuiScreenReaderOnly } from '@elastic/eui';
|
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
import { euiThemeVars } from '@kbn/ui-theme';
|
|
||||||
import { css } from '@emotion/react';
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import { ActionItem } from './cell_action_item';
|
|
||||||
import { ExtraActionsButton } from './extra_actions_button';
|
|
||||||
import { ACTIONS_AREA_LABEL, YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS } from './translations';
|
|
||||||
import { partitionActions } from '../hooks/actions';
|
|
||||||
import { ExtraActionsPopOverWithAnchor } from './extra_actions_popover';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { useLoadActionsFn } from './cell_actions_context';
|
|
||||||
|
|
||||||
/** This class is added to the document body while dragging */
|
|
||||||
export const IS_DRAGGING_CLASS_NAME = 'is-dragging';
|
|
||||||
|
|
||||||
// Overwrite Popover default minWidth to avoid displaying empty space
|
|
||||||
const PANEL_STYLE = { minWidth: `24px` };
|
|
||||||
|
|
||||||
const hoverContentWrapperCSS = css`
|
|
||||||
padding: 0 ${euiThemeVars.euiSizeS};
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To avoid expensive changes to the DOM, delay showing the popover menu
|
|
||||||
*/
|
|
||||||
const HOVER_INTENT_DELAY = 100; // ms
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
children: React.ReactNode;
|
|
||||||
visibleCellActions: number;
|
|
||||||
actionContext: CellActionExecutionContext;
|
|
||||||
showActionTooltips: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const HoverActionsPopover = React.memo<Props>(
|
|
||||||
({ children, visibleCellActions, actionContext, showActionTooltips }) => {
|
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
|
||||||
const [isExtraActionsPopoverOpen, setIsExtraActionsPopoverOpen] = useState(false);
|
|
||||||
const [showHoverContent, setShowHoverContent] = useState(false);
|
|
||||||
const popoverRef = useRef<EuiPopover>(null);
|
|
||||||
|
|
||||||
const [{ value: actions }, loadActions] = useLoadActionsFn();
|
|
||||||
|
|
||||||
const { visibleActions, extraActions } = useMemo(
|
|
||||||
() => partitionActions(actions ?? [], visibleCellActions),
|
|
||||||
[actions, visibleCellActions]
|
|
||||||
);
|
|
||||||
|
|
||||||
const closePopover = useCallback(() => {
|
|
||||||
setShowHoverContent(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const closeExtraActions = useCallback(
|
|
||||||
() => setIsExtraActionsPopoverOpen(false),
|
|
||||||
[setIsExtraActionsPopoverOpen]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onShowExtraActionsClick = useCallback(() => {
|
|
||||||
setIsExtraActionsPopoverOpen(true);
|
|
||||||
closePopover();
|
|
||||||
}, [closePopover, setIsExtraActionsPopoverOpen]);
|
|
||||||
|
|
||||||
const openPopOverDebounced = useMemo(
|
|
||||||
() =>
|
|
||||||
debounce(() => {
|
|
||||||
if (!document.body.classList.contains(IS_DRAGGING_CLASS_NAME)) {
|
|
||||||
setShowHoverContent(true);
|
|
||||||
}
|
|
||||||
}, HOVER_INTENT_DELAY),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// prevent setState on an unMounted component
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
openPopOverDebounced.cancel();
|
|
||||||
};
|
|
||||||
}, [openPopOverDebounced]);
|
|
||||||
|
|
||||||
const onMouseEnter = useCallback(async () => {
|
|
||||||
// Do not open actions with extra action popover is open
|
|
||||||
if (isExtraActionsPopoverOpen) return;
|
|
||||||
|
|
||||||
// memoize actions after the first call
|
|
||||||
if (actions === undefined) {
|
|
||||||
loadActions(actionContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
openPopOverDebounced();
|
|
||||||
}, [isExtraActionsPopoverOpen, actions, openPopOverDebounced, loadActions, actionContext]);
|
|
||||||
|
|
||||||
const onMouseLeave = useCallback(() => {
|
|
||||||
closePopover();
|
|
||||||
}, [closePopover]);
|
|
||||||
|
|
||||||
const content = useMemo(() => {
|
|
||||||
return (
|
|
||||||
// Hack - Forces extra actions popover to close when hover content is clicked.
|
|
||||||
// This hack is required because we anchor the popover to the hover content instead
|
|
||||||
// of anchoring it to the button that triggers the popover.
|
|
||||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
|
||||||
<div ref={contentRef} onMouseEnter={onMouseEnter} onClick={closeExtraActions}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, [onMouseEnter, closeExtraActions, children]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div onMouseLeave={onMouseLeave}>
|
|
||||||
<EuiPopover
|
|
||||||
panelStyle={PANEL_STYLE}
|
|
||||||
ref={popoverRef}
|
|
||||||
anchorPosition={'downCenter'}
|
|
||||||
button={content}
|
|
||||||
closePopover={closePopover}
|
|
||||||
hasArrow={false}
|
|
||||||
isOpen={showHoverContent}
|
|
||||||
panelPaddingSize="none"
|
|
||||||
repositionOnScroll
|
|
||||||
ownFocus={false}
|
|
||||||
data-test-subj={'hoverActionsPopover'}
|
|
||||||
aria-label={ACTIONS_AREA_LABEL}
|
|
||||||
>
|
|
||||||
{showHoverContent ? (
|
|
||||||
<div css={hoverContentWrapperCSS}>
|
|
||||||
<EuiScreenReaderOnly>
|
|
||||||
<p>{YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS(actionContext.field.name)}</p>
|
|
||||||
</EuiScreenReaderOnly>
|
|
||||||
{visibleActions.map((action) => (
|
|
||||||
<ActionItem
|
|
||||||
key={action.id}
|
|
||||||
action={action}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showTooltip={showActionTooltips}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{extraActions.length > 0 ? (
|
|
||||||
<ExtraActionsButton
|
|
||||||
onClick={onShowExtraActionsClick}
|
|
||||||
showTooltip={showActionTooltips}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</EuiPopover>
|
|
||||||
</div>
|
|
||||||
<ExtraActionsPopOverWithAnchor
|
|
||||||
actions={extraActions}
|
|
||||||
anchorRef={contentRef}
|
|
||||||
actionContext={actionContext}
|
|
||||||
closePopOver={closeExtraActions}
|
|
||||||
isOpen={isExtraActionsPopoverOpen}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export { CellActions, CellActionsMode } from './cell_actions';
|
|
||||||
export { CellActionsContextProvider } from './cell_actions_context';
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { act, render } from '@testing-library/react';
|
|
||||||
import React from 'react';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { makeAction } from '../mocks/helpers';
|
|
||||||
import { InlineActions } from './inline_actions';
|
|
||||||
import { CellActionsContextProvider } from '.';
|
|
||||||
|
|
||||||
describe('InlineActions', () => {
|
|
||||||
const actionContext = { trigger: { id: 'triggerId' } } as CellActionExecutionContext;
|
|
||||||
it('renders', async () => {
|
|
||||||
const getActionsPromise = Promise.resolve([]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
const { queryByTestId } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<InlineActions
|
|
||||||
visibleCellActions={5}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
/>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryByTestId('inlineActions')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders all actions', async () => {
|
|
||||||
const getActionsPromise = Promise.resolve([
|
|
||||||
makeAction('action-1'),
|
|
||||||
makeAction('action-2'),
|
|
||||||
makeAction('action-3'),
|
|
||||||
makeAction('action-4'),
|
|
||||||
makeAction('action-5'),
|
|
||||||
]);
|
|
||||||
const getActions = () => getActionsPromise;
|
|
||||||
const { queryAllByRole } = render(
|
|
||||||
<CellActionsContextProvider getTriggerCompatibleActions={getActions}>
|
|
||||||
<InlineActions
|
|
||||||
visibleCellActions={5}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showActionTooltips={false}
|
|
||||||
/>
|
|
||||||
</CellActionsContextProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(async () => {
|
|
||||||
await getActionsPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(queryAllByRole('button').length).toBe(5);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 React, { useCallback, useMemo, useState } from 'react';
|
|
||||||
import { ActionItem } from './cell_action_item';
|
|
||||||
import { usePartitionActions } from '../hooks/actions';
|
|
||||||
import { ExtraActionsPopOver } from './extra_actions_popover';
|
|
||||||
import { ExtraActionsButton } from './extra_actions_button';
|
|
||||||
import { CellActionExecutionContext } from './cell_actions';
|
|
||||||
import { useLoadActions } from './cell_actions_context';
|
|
||||||
|
|
||||||
interface InlineActionsProps {
|
|
||||||
actionContext: CellActionExecutionContext;
|
|
||||||
showActionTooltips: boolean;
|
|
||||||
visibleCellActions: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const InlineActions: React.FC<InlineActionsProps> = ({
|
|
||||||
actionContext,
|
|
||||||
showActionTooltips,
|
|
||||||
visibleCellActions,
|
|
||||||
}) => {
|
|
||||||
const { value: allActions } = useLoadActions(actionContext);
|
|
||||||
const { extraActions, visibleActions } = usePartitionActions(
|
|
||||||
allActions ?? [],
|
|
||||||
visibleCellActions
|
|
||||||
);
|
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
||||||
const togglePopOver = useCallback(() => setIsPopoverOpen((isOpen) => !isOpen), []);
|
|
||||||
const closePopOver = useCallback(() => setIsPopoverOpen(false), []);
|
|
||||||
const button = useMemo(
|
|
||||||
() => <ExtraActionsButton onClick={togglePopOver} showTooltip={showActionTooltips} />,
|
|
||||||
[togglePopOver, showActionTooltips]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span data-test-subj="inlineActions">
|
|
||||||
{visibleActions.map((action, index) => (
|
|
||||||
<ActionItem
|
|
||||||
key={`action-item-${index}`}
|
|
||||||
action={action}
|
|
||||||
actionContext={actionContext}
|
|
||||||
showTooltip={showActionTooltips}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{extraActions.length > 0 ? (
|
|
||||||
<ExtraActionsPopOver
|
|
||||||
actions={extraActions}
|
|
||||||
actionContext={actionContext}
|
|
||||||
button={button}
|
|
||||||
closePopOver={closePopOver}
|
|
||||||
isOpen={isPopoverOpen}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { i18n } from '@kbn/i18n';
|
|
||||||
|
|
||||||
export const YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS = (fieldName: string) =>
|
|
||||||
i18n.translate('uiActions.cellActions.youAreInADialogContainingOptionsScreenReaderOnly', {
|
|
||||||
values: { fieldName },
|
|
||||||
defaultMessage: `You are in a dialog, containing options for field {fieldName}. Press tab to navigate options. Press escape to exit.`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const EXTRA_ACTIONS_ARIA_LABEL = i18n.translate(
|
|
||||||
'uiActions.cellActions.extraActionsAriaLabel',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Extra actions',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const SHOW_MORE_ACTIONS = i18n.translate('uiActions.showMoreActionsLabel', {
|
|
||||||
defaultMessage: 'More actions',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ACTIONS_AREA_LABEL = i18n.translate('uiActions.cellActions.actionsAriaLabel', {
|
|
||||||
defaultMessage: 'Actions',
|
|
||||||
});
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { makeAction } from '../mocks/helpers';
|
|
||||||
import { partitionActions } from './actions';
|
|
||||||
|
|
||||||
describe('InlineActions', () => {
|
|
||||||
it('returns an empty array when actions is an empty array', async () => {
|
|
||||||
const { extraActions, visibleActions } = partitionActions([], 5);
|
|
||||||
|
|
||||||
expect(visibleActions).toEqual([]);
|
|
||||||
expect(extraActions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only visible actions when visibleCellActions > actions.length', async () => {
|
|
||||||
const actions = [makeAction('action-1'), makeAction('action-2'), makeAction('action-3')];
|
|
||||||
const { extraActions, visibleActions } = partitionActions(actions, 4);
|
|
||||||
|
|
||||||
expect(visibleActions.length).toEqual(actions.length);
|
|
||||||
expect(extraActions).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only extra actions when visibleCellActions is 1', async () => {
|
|
||||||
const actions = [makeAction('action-1'), makeAction('action-2'), makeAction('action-3')];
|
|
||||||
const { extraActions, visibleActions } = partitionActions(actions, 1);
|
|
||||||
|
|
||||||
expect(visibleActions).toEqual([]);
|
|
||||||
expect(extraActions.length).toEqual(actions.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only extra actions when visibleCellActions is 0', async () => {
|
|
||||||
const actions = [makeAction('action-1'), makeAction('action-2'), makeAction('action-3')];
|
|
||||||
const { extraActions, visibleActions } = partitionActions(actions, 0);
|
|
||||||
|
|
||||||
expect(visibleActions).toEqual([]);
|
|
||||||
expect(extraActions.length).toEqual(actions.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only extra actions when visibleCellActions is negative', async () => {
|
|
||||||
const actions = [makeAction('action-1'), makeAction('action-2'), makeAction('action-3')];
|
|
||||||
const { extraActions, visibleActions } = partitionActions(actions, -6);
|
|
||||||
|
|
||||||
expect(visibleActions).toEqual([]);
|
|
||||||
expect(extraActions.length).toEqual(actions.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns only one visible action when visibleCellActionss 2 and action.length is 3', async () => {
|
|
||||||
const { extraActions, visibleActions } = partitionActions(
|
|
||||||
[makeAction('action-1'), makeAction('action-2'), makeAction('action-3')],
|
|
||||||
2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(visibleActions.length).toEqual(1);
|
|
||||||
expect(extraActions.length).toEqual(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns two visible actions when visibleCellActions is 3 and action.length is 5', async () => {
|
|
||||||
const { extraActions, visibleActions } = partitionActions(
|
|
||||||
[
|
|
||||||
makeAction('action-1'),
|
|
||||||
makeAction('action-2'),
|
|
||||||
makeAction('action-3'),
|
|
||||||
makeAction('action-4'),
|
|
||||||
makeAction('action-5'),
|
|
||||||
],
|
|
||||||
3
|
|
||||||
);
|
|
||||||
expect(visibleActions.length).toEqual(2);
|
|
||||||
expect(extraActions.length).toEqual(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns three visible actions when visibleCellActions is 3 and action.length is 3', async () => {
|
|
||||||
const { extraActions, visibleActions } = partitionActions(
|
|
||||||
[makeAction('action-1'), makeAction('action-2'), makeAction('action-3')],
|
|
||||||
3
|
|
||||||
);
|
|
||||||
expect(visibleActions.length).toEqual(3);
|
|
||||||
expect(extraActions.length).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { useMemo } from 'react';
|
|
||||||
import { Action } from '../../actions';
|
|
||||||
|
|
||||||
export const partitionActions = (actions: Action[], visibleCellActions: number) => {
|
|
||||||
if (visibleCellActions <= 1) return { extraActions: actions, visibleActions: [] };
|
|
||||||
if (actions.length <= visibleCellActions) return { extraActions: [], visibleActions: actions };
|
|
||||||
|
|
||||||
return {
|
|
||||||
visibleActions: actions.slice(0, visibleCellActions - 1),
|
|
||||||
extraActions: actions.slice(visibleCellActions - 1, actions.length),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface PartitionedActions {
|
|
||||||
extraActions: Array<Action<object>>;
|
|
||||||
visibleActions: Array<Action<object>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePartitionActions = (
|
|
||||||
allActions: Action[],
|
|
||||||
visibleCellActions: number
|
|
||||||
): PartitionedActions => {
|
|
||||||
return useMemo(() => {
|
|
||||||
return partitionActions(allActions ?? [], visibleCellActions);
|
|
||||||
}, [allActions, visibleCellActions]);
|
|
||||||
};
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const makeAction = (actionsName: string, icon: string = 'icon', order?: number) => ({
|
|
||||||
id: actionsName,
|
|
||||||
type: actionsName,
|
|
||||||
order,
|
|
||||||
getIconType: () => icon,
|
|
||||||
getDisplayName: () => actionsName,
|
|
||||||
getDisplayNameTooltip: () => actionsName,
|
|
||||||
isCompatible: () => Promise.resolve(true),
|
|
||||||
execute: () => {
|
|
||||||
alert(actionsName);
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -39,10 +39,3 @@ export {
|
||||||
ACTION_VISUALIZE_LENS_FIELD,
|
ACTION_VISUALIZE_LENS_FIELD,
|
||||||
} from './types';
|
} from './types';
|
||||||
export type { ActionExecutionContext, ActionExecutionMeta, ActionMenuItemProps } from './actions';
|
export type { ActionExecutionContext, ActionExecutionMeta, ActionMenuItemProps } from './actions';
|
||||||
export {
|
|
||||||
CellActions,
|
|
||||||
CellActionsMode,
|
|
||||||
CellActionsContextProvider,
|
|
||||||
} from './cell_actions/components';
|
|
||||||
|
|
||||||
export type { CellActionExecutionContext } from './cell_actions/components/cell_actions';
|
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
"@kbn/utility-types",
|
"@kbn/utility-types",
|
||||||
"@kbn/i18n",
|
"@kbn/i18n",
|
||||||
"@kbn/es-query",
|
"@kbn/es-query",
|
||||||
"@kbn/ui-theme",
|
|
||||||
"@kbn/ui-actions-browser",
|
"@kbn/ui-actions-browser",
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue