[Search Sessions] Fixes management actions not accessible (#105940)

This commit is contained in:
Anton Dosov 2021-07-19 13:11:16 +02:00 committed by GitHub
parent 1c4818e0db
commit 00d8f05f2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 249 deletions

View file

@ -12,20 +12,18 @@ import React, { useState } from 'react';
import { CoreStart } from 'kibana/public';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { TableText } from '../';
import { OnActionClick, OnActionComplete, OnActionDismiss } from './types';
import { IClickActionDescriptor } from '../';
import { OnActionDismiss } from './types';
import { UISession } from '../../types';
interface DeleteButtonProps {
id: string;
name: string;
api: SearchSessionsMgmtAPI;
onActionComplete: OnActionComplete;
overlays: CoreStart['overlays'];
onActionClick: OnActionClick;
searchSession: UISession;
}
const DeleteConfirm = (props: DeleteButtonProps & { onActionDismiss: OnActionDismiss }) => {
const { id, name, api, onActionComplete, onActionDismiss } = props;
const { searchSession, api, onActionDismiss } = props;
const { name, id } = searchSession;
const [isLoading, setIsLoading] = useState(false);
const title = i18n.translate('xpack.data.mgmt.searchSessions.cancelModal.title', {
@ -51,7 +49,6 @@ const DeleteConfirm = (props: DeleteButtonProps & { onActionDismiss: OnActionDis
onConfirm={async () => {
setIsLoading(true);
await api.sendCancel(id);
onActionComplete();
onActionDismiss();
}}
confirmButtonText={confirm}
@ -65,24 +62,21 @@ const DeleteConfirm = (props: DeleteButtonProps & { onActionDismiss: OnActionDis
);
};
export const DeleteButton = (props: DeleteButtonProps) => {
const { overlays, onActionClick } = props;
return (
<>
<TableText
onClick={() => {
onActionClick();
const ref = overlays.openModal(
toMountPoint(<DeleteConfirm onActionDismiss={() => ref?.close()} {...props} />)
);
}}
>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.actionDelete"
defaultMessage="Delete"
/>
</TableText>
</>
);
};
export const createDeleteActionDescriptor = (
api: SearchSessionsMgmtAPI,
uiSession: UISession,
core: CoreStart
): IClickActionDescriptor => ({
iconType: 'crossInACircleFilled',
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionDelete" defaultMessage="Delete" />
),
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(
<DeleteConfirm onActionDismiss={() => ref?.close()} searchSession={uiSession} api={api} />
)
);
await ref.onClose;
},
});

View file

@ -9,29 +9,25 @@ import { EuiConfirmModal } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState } from 'react';
import { Duration } from 'moment';
import moment from 'moment';
import { CoreStart } from 'kibana/public';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { TableText } from '../';
import { OnActionClick, OnActionComplete, OnActionDismiss } from './types';
import { IClickActionDescriptor } from '../';
import { OnActionDismiss } from './types';
import { UISession } from '../../types';
import extendSessionIcon from '../../icons/extend_session.svg';
interface ExtendButtonProps {
id: string;
name: string;
expires: string | null;
extendBy: Duration;
searchSession: UISession;
api: SearchSessionsMgmtAPI;
overlays: CoreStart['overlays'];
onActionClick: OnActionClick;
onActionComplete: OnActionComplete;
}
const ExtendConfirm = ({ ...props }: ExtendButtonProps & { onActionDismiss: OnActionDismiss }) => {
const { id, name, expires, api, extendBy, onActionComplete, onActionDismiss } = props;
const { searchSession, api, onActionDismiss } = props;
const { id, name, expires } = searchSession;
const [isLoading, setIsLoading] = useState(false);
const extendByDuration = moment.duration(extendBy);
const extendByDuration = moment.duration(api.getExtendByDuration());
const newExpiration = moment(expires).add(extendByDuration);
@ -61,7 +57,6 @@ const ExtendConfirm = ({ ...props }: ExtendButtonProps & { onActionDismiss: OnAc
await api.sendExtend(id, `${newExpiration.toISOString()}`);
setIsLoading(false);
onActionDismiss();
onActionComplete();
}}
confirmButtonText={confirm}
confirmButtonDisabled={isLoading}
@ -74,24 +69,21 @@ const ExtendConfirm = ({ ...props }: ExtendButtonProps & { onActionDismiss: OnAc
);
};
export const ExtendButton = (props: ExtendButtonProps) => {
const { overlays, onActionClick } = props;
return (
<>
<TableText
onClick={() => {
onActionClick();
const ref = overlays.openModal(
toMountPoint(<ExtendConfirm onActionDismiss={() => ref?.close()} {...props} />)
);
}}
>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.actionExtend"
defaultMessage="Extend"
/>
</TableText>
</>
);
};
export const createExtendActionDescriptor = (
api: SearchSessionsMgmtAPI,
uiSession: UISession,
core: CoreStart
): IClickActionDescriptor => ({
iconType: extendSessionIcon,
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionExtend" defaultMessage="Extend" />
),
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(
<ExtendConfirm onActionDismiss={() => ref?.close()} searchSession={uiSession} api={api} />
)
);
await ref.onClose;
},
});

View file

@ -5,93 +5,31 @@
* 2.0.
*/
import React from 'react';
import { CoreStart } from 'kibana/public';
import { IClickActionDescriptor } from '../';
import extendSessionIcon from '../../icons/extend_session.svg';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { UISession } from '../../types';
import { DeleteButton } from './delete_button';
import { ExtendButton } from './extend_button';
import { InspectButton } from './inspect_button';
import { ACTION, OnActionClick, OnActionComplete } from './types';
import { RenameButton } from './rename_button';
import { createDeleteActionDescriptor } from './delete_button';
import { createExtendActionDescriptor } from './extend_button';
import { createInspectActionDescriptor } from './inspect_button';
import { ACTION } from './types';
import { createRenameActionDescriptor } from './rename_button';
export const getAction = (
api: SearchSessionsMgmtAPI,
actionType: string,
uiSession: UISession,
core: CoreStart,
onActionClick: OnActionClick,
onActionComplete: OnActionComplete
core: CoreStart
): IClickActionDescriptor | null => {
const { id, name, expires } = uiSession;
const { overlays, uiSettings } = core;
switch (actionType) {
case ACTION.INSPECT:
return {
iconType: 'document',
textColor: 'default',
label: (
<InspectButton
overlays={overlays}
searchSession={uiSession}
uiSettings={uiSettings}
onActionClick={onActionClick}
/>
),
};
return createInspectActionDescriptor(api, uiSession, core);
case ACTION.DELETE:
return {
iconType: 'crossInACircleFilled',
textColor: 'default',
label: (
<DeleteButton
api={api}
id={id}
name={name}
overlays={overlays}
onActionComplete={onActionComplete}
onActionClick={onActionClick}
/>
),
};
return createDeleteActionDescriptor(api, uiSession, core);
case ACTION.EXTEND:
return {
iconType: extendSessionIcon,
textColor: 'default',
label: (
<ExtendButton
api={api}
id={id}
name={name}
expires={expires}
overlays={overlays}
extendBy={api.getExtendByDuration()}
onActionComplete={onActionComplete}
onActionClick={onActionClick}
/>
),
};
return createExtendActionDescriptor(api, uiSession, core);
case ACTION.RENAME:
return {
iconType: 'pencil',
textColor: 'default',
label: (
<RenameButton
api={api}
id={id}
name={name}
overlays={overlays}
onActionComplete={onActionComplete}
onActionClick={onActionClick}
/>
),
};
return createRenameActionDescriptor(api, uiSession, core);
default:
// eslint-disable-next-line no-console
console.error(`Unknown action: ${actionType}`);

View file

@ -10,20 +10,18 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { Fragment } from 'react';
import { CoreStart } from 'kibana/public';
import { UISession } from '../../types';
import { TableText } from '..';
import { IClickActionDescriptor } from '..';
import {
CodeEditor,
createKibanaReactContext,
toMountPoint,
} from '../../../../../../../../src/plugins/kibana_react/public';
import './inspect_button.scss';
import { OnActionClick } from './types';
import { SearchSessionsMgmtAPI } from '../../lib/api';
interface InspectFlyoutProps {
searchSession: UISession;
overlays: CoreStart['overlays'];
uiSettings: CoreStart['uiSettings'];
onActionClick: OnActionClick;
}
const InspectFlyout = ({ uiSettings, searchSession }: InspectFlyoutProps) => {
@ -84,24 +82,23 @@ const InspectFlyout = ({ uiSettings, searchSession }: InspectFlyoutProps) => {
</KibanaReactContextProvider>
);
};
export const InspectButton = (props: InspectFlyoutProps) => {
const { overlays, onActionClick } = props;
return (
<Fragment>
<TableText
onClick={() => {
onActionClick();
const flyout = <InspectFlyout {...props} />;
overlays.openFlyout(toMountPoint(flyout));
}}
>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.flyoutTitle"
aria-label="Inspect"
defaultMessage="Inspect"
/>
</TableText>
</Fragment>
);
};
export const createInspectActionDescriptor = (
api: SearchSessionsMgmtAPI,
uiSession: UISession,
core: CoreStart
): IClickActionDescriptor => ({
iconType: 'document',
label: (
<FormattedMessage
id="xpack.data.mgmt.searchSessions.flyoutTitle"
aria-label="Inspect"
defaultMessage="Inspect"
/>
),
onClick: async () => {
const flyout = <InspectFlyout uiSettings={core.uiSettings} searchSession={uiSession} />;
const overlay = core.overlays.openFlyout(toMountPoint(flyout));
await overlay.onClose;
},
});

View file

@ -9,11 +9,7 @@ import {
EuiButtonIcon,
EuiContextMenu,
EuiContextMenuPanelDescriptor,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPopover,
EuiTextProps,
EuiToolTip,
} from '@elastic/eui';
import {
@ -22,20 +18,12 @@ import {
} from '@elastic/eui/src/components/context_menu/context_menu';
import { i18n } from '@kbn/i18n';
import { CoreStart } from 'kibana/public';
import React, { ReactElement, useState } from 'react';
import { TableText } from '../';
import React, { useState } from 'react';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { UISession } from '../../types';
import { getAction } from './get_action';
import { ACTION, OnActionComplete } from './types';
// interfaces
interface PopoverActionProps {
textColor?: EuiTextProps['color'];
iconType: string;
children: string | ReactElement;
}
interface PopoverActionItemsProps {
session: UISession;
api: SearchSessionsMgmtAPI;
@ -43,18 +31,6 @@ interface PopoverActionItemsProps {
core: CoreStart;
}
// helper
const PopoverAction = ({ textColor, iconType, children, ...props }: PopoverActionProps) => (
<EuiFlexGroup gutterSize="s" alignItems="center" component="span" {...props}>
<EuiFlexItem grow={false} component="span">
<EuiIcon color={textColor} type={iconType} />
</EuiFlexItem>
<EuiFlexItem grow={true} component="span">
<TableText color={textColor}>{children}</TableText>
</EuiFlexItem>
</EuiFlexGroup>
);
export const PopoverActionsMenu = ({
api,
onActionComplete,
@ -71,10 +47,6 @@ export const PopoverActionsMenu = ({
setPopover(false);
};
const onActionClick = () => {
closePopover();
};
const renderPopoverButton = () => (
<EuiToolTip
content={i18n.translate('xpack.data.mgmt.searchSessions.actions.tooltip.moreActions', {
@ -95,9 +67,9 @@ export const PopoverActionsMenu = ({
const actions = session.actions || [];
// Generic set of actions - up to the API to return what is available
const items = actions.reduce((itemSet, actionType) => {
const actionDef = getAction(api, actionType, session, core, onActionClick, onActionComplete);
const actionDef = getAction(api, actionType, session, core);
if (actionDef) {
const { label, textColor, iconType } = actionDef;
const { label, iconType, onClick } = actionDef;
// add a line above the delete action (when there are multiple)
// NOTE: Delete action MUST be the final action[] item
@ -109,16 +81,15 @@ export const PopoverActionsMenu = ({
...itemSet,
{
key: `action-${actionType}`,
name: (
<PopoverAction
textColor={textColor}
iconType={iconType}
data-test-subj={`sessionManagementPopoverAction-${actionType}`}
>
{label}
</PopoverAction>
),
},
name: label,
icon: iconType,
'data-test-subj': `sessionManagementPopoverAction-${actionType}`,
onClick: async () => {
closePopover();
await onClick();
onActionComplete();
},
} as EuiContextMenuPanelItemDescriptorEntry,
];
}
return itemSet;
@ -126,20 +97,16 @@ export const PopoverActionsMenu = ({
const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }];
return (
<>
{actions.length ? (
<EuiPopover
id={`popover-${session.id}`}
button={renderPopoverButton()}
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition="downLeft"
panelPaddingSize={'s'}
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
) : null}
</>
);
return actions.length ? (
<EuiPopover
id={`popover-${session.id}`}
button={renderPopoverButton()}
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition="downLeft"
panelPaddingSize={'s'}
>
<EuiContextMenu initialPanelId={0} panels={panels} />
</EuiPopover>
) : null;
};

View file

@ -22,24 +22,22 @@ import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState } from 'react';
import { CoreStart } from 'kibana/public';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { TableText } from '../';
import { IClickActionDescriptor } from '../';
import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { OnActionClick, OnActionComplete, OnActionDismiss } from './types';
import { OnActionDismiss } from './types';
import { UISession } from '../../types';
interface RenameButtonProps {
id: string;
name: string;
searchSession: UISession;
api: SearchSessionsMgmtAPI;
overlays: CoreStart['overlays'];
onActionComplete: OnActionComplete;
onActionClick: OnActionClick;
}
const RenameDialog = ({
onActionDismiss,
...props
}: RenameButtonProps & { onActionDismiss: OnActionDismiss }) => {
const { id, name: originalName, api, onActionComplete } = props;
const { api, searchSession } = props;
const { id, name: originalName } = searchSession;
const [isLoading, setIsLoading] = useState(false);
const [newName, setNewName] = useState(originalName);
@ -92,7 +90,6 @@ const RenameDialog = ({
await api.sendRename(id, newName);
setIsLoading(false);
onActionDismiss();
onActionComplete();
}}
fill
isLoading={isLoading}
@ -104,24 +101,21 @@ const RenameDialog = ({
);
};
export const RenameButton = (props: RenameButtonProps) => {
const { overlays, onActionClick } = props;
const onClick = () => {
onActionClick();
const ref = overlays.openModal(
toMountPoint(<RenameDialog onActionDismiss={() => ref?.close()} {...props} />)
export const createRenameActionDescriptor = (
api: SearchSessionsMgmtAPI,
uiSession: UISession,
core: CoreStart
): IClickActionDescriptor => ({
iconType: 'pencil',
label: (
<FormattedMessage id="xpack.data.mgmt.searchSessions.actionRename" defaultMessage="Edit name" />
),
onClick: async () => {
const ref = core.overlays.openModal(
toMountPoint(
<RenameDialog onActionDismiss={() => ref?.close()} api={api} searchSession={uiSession} />
)
);
};
return (
<>
<TableText onClick={onClick}>
<FormattedMessage
id="xpack.data.mgmt.searchSessions.actionRename"
defaultMessage="Edit name"
/>
</TableText>
</>
);
};
await ref.onClose;
},
});

View file

@ -6,7 +6,6 @@
*/
export type OnActionComplete = () => void;
export type OnActionClick = () => void;
export type OnActionDismiss = () => void;
export enum ACTION {

View file

@ -20,9 +20,9 @@ export const TableText = ({ children, ...props }: EuiTextProps) => {
};
export interface IClickActionDescriptor {
label: string | React.ReactElement;
label: React.ReactNode;
iconType: 'trash' | 'cancel' | typeof extendSessionIcon;
textColor: EuiTextProps['color'];
onClick: () => Promise<void> | void;
}
export interface IHrefActionDescriptor {