[Infrastructure UI] Prepare saved view components to consume new view hooks (#155174)

## 📓  Summary

Part of #152617 

This PR does some refactoring on the presentational components used to
render the saved views on both Infra Inventory and Metrics Explorer,
preparing for the additional work required once [[Infrastructure UI]
Saved object hooks that use the SO client are removed in favour of the
new endpoints#154725](https://github.com/elastic/kibana/issues/154725)
will be implemented.

## 🐞 Bug fixes

While working on this code, some pre-existing issues have been
discovered and fixed:
- "Make default" star icon was not aligned correctly when rendered
alone:
<img width="751" alt="Screenshot 2023-04-19 at 15 22 24"
src="https://user-images.githubusercontent.com/34506779/233088425-34992395-4d18-46bc-9124-5d99101406ce.png">

- Delete view confirm prompt not closed after removing a view:


https://user-images.githubusercontent.com/34506779/233088780-9b1bfe57-170c-4e66-9303-f41448eb8447.mov

---------

Co-authored-by: Marco Antonio Ghiani <marcoantonio.ghiani@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marco Antonio Ghiani 2023-04-25 17:20:16 +02:00 committed by GitHub
parent 077245606b
commit c6fa0f9e09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 245 additions and 337 deletions

View file

@ -5,7 +5,8 @@
* 2.0.
*/
import React, { useCallback, useState } from 'react';
import React, { useState } from 'react';
import useToggle from 'react-use/lib/useToggle';
import {
EuiButtonEmpty,
@ -26,121 +27,100 @@ import { SavedView } from '../../containers/saved_view/saved_view';
interface Props<ViewState> {
views: Array<SavedView<ViewState>>;
loading: boolean;
defaultViewId: string;
sourceIsLoading: boolean;
close(): void;
makeDefault(id: string): void;
onClose(): void;
onMakeDefaultView(id: string): void;
setView(viewState: ViewState): void;
deleteView(id: string): void;
onDeleteView(id: string): void;
}
interface DeleteConfimationProps {
isDisabled?: boolean;
confirmedAction(): void;
onConfirm(): void;
}
const DeleteConfimation = (props: DeleteConfimationProps) => {
const [confirmVisible, setConfirmVisible] = useState(false);
const showConfirm = useCallback(() => setConfirmVisible(true), []);
const hideConfirm = useCallback(() => setConfirmVisible(false), []);
const DeleteConfimation = ({ isDisabled, onConfirm }: DeleteConfimationProps) => {
const [isConfirmVisible, toggleVisibility] = useToggle(false);
return (
<>
{confirmVisible && (
<EuiFlexGroup>
<EuiButtonEmpty onClick={hideConfirm} data-test-subj="hideConfirm">
<FormattedMessage defaultMessage="cancel" id="xpack.infra.waffle.savedViews.cancel" />
</EuiButtonEmpty>
<EuiButton
disabled={props.isDisabled}
fill={true}
iconType="trash"
color="danger"
onClick={props.confirmedAction}
data-test-subj="showConfirm"
>
<FormattedMessage
defaultMessage="Delete view?"
id="xpack.infra.openView.actionNames.deleteConfirmation"
/>
</EuiButton>
</EuiFlexGroup>
)}
{!confirmVisible && (
<EuiButtonEmpty
data-test-subj="infraDeleteConfimationButton"
iconType="trash"
color="danger"
onClick={showConfirm}
return isConfirmVisible ? (
<EuiFlexGroup>
<EuiButtonEmpty onClick={toggleVisibility} data-test-subj="hideConfirm">
<FormattedMessage defaultMessage="cancel" id="xpack.infra.waffle.savedViews.cancel" />
</EuiButtonEmpty>
<EuiButton
disabled={isDisabled}
fill={true}
iconType="trash"
color="danger"
onClick={onConfirm}
data-test-subj="showConfirm"
>
<FormattedMessage
defaultMessage="Delete view?"
id="xpack.infra.openView.actionNames.deleteConfirmation"
/>
)}
</>
</EuiButton>
</EuiFlexGroup>
) : (
<EuiButtonEmpty
data-test-subj="infraDeleteConfimationButton"
iconType="trash"
color="danger"
onClick={toggleVisibility}
/>
);
};
export function SavedViewManageViewsFlyout<ViewState>({
close,
onClose,
views,
defaultViewId,
setView,
makeDefault,
deleteView,
onMakeDefaultView,
onDeleteView,
loading,
sourceIsLoading,
}: Props<ViewState>) {
const [inProgressView, setInProgressView] = useState<string | null>(null);
const renderName = useCallback(
(name: string, item: SavedView<ViewState>) => (
<EuiButtonEmpty
data-test-subj="infraRenderNameButton"
onClick={() => {
setView(item);
close();
const renderName = (name: string, item: SavedView<ViewState>) => (
<EuiButtonEmpty
key={item.id}
data-test-subj="infraRenderNameButton"
onClick={() => {
setView(item);
onClose();
}}
>
{name}
</EuiButtonEmpty>
);
const renderDeleteAction = (item: SavedView<ViewState>) => {
return (
<DeleteConfimation
key={item.id}
isDisabled={item.isDefault}
onConfirm={() => {
onDeleteView(item.id);
}}
>
{name}
</EuiButtonEmpty>
),
[setView, close]
);
/>
);
};
const renderDeleteAction = useCallback(
(item: SavedView<ViewState>) => {
if (item.id === '0') {
return <></>;
}
return (
<DeleteConfimation
isDisabled={item.isDefault}
confirmedAction={() => {
deleteView(item.id);
}}
/>
);
},
[deleteView]
);
const renderMakeDefaultAction = useCallback(
(item: SavedView<ViewState>) => {
const isDefault = item.id === defaultViewId;
return (
<>
<EuiButtonEmpty
data-test-subj="infraRenderMakeDefaultActionButton"
isLoading={inProgressView === item.id && sourceIsLoading}
iconType={isDefault ? 'starFilled' : 'starEmpty'}
onClick={() => {
setInProgressView(item.id);
makeDefault(item.id);
}}
/>
</>
);
},
[makeDefault, defaultViewId, sourceIsLoading, inProgressView]
);
const renderMakeDefaultAction = (item: SavedView<ViewState>) => {
return (
<EuiButtonEmpty
key={item.id}
data-test-subj="infraRenderMakeDefaultActionButton"
isLoading={inProgressView === item.id && sourceIsLoading}
iconType={item.isDefault ? 'starFilled' : 'starEmpty'}
onClick={() => {
setInProgressView(item.id);
onMakeDefaultView(item.id);
}}
/>
);
};
const columns = [
{
@ -156,11 +136,10 @@ export function SavedViewManageViewsFlyout<ViewState>({
}),
actions: [
{
available: () => true,
render: renderMakeDefaultAction,
},
{
available: (item: SavedView<ViewState>) => true,
available: (item: SavedView<ViewState>) => item.id !== '0',
render: renderDeleteAction,
},
],
@ -169,7 +148,7 @@ export function SavedViewManageViewsFlyout<ViewState>({
return (
<EuiPortal>
<EuiFlyout onClose={close} data-test-subj="loadViewsFlyout">
<EuiFlyout onClose={onClose} data-test-subj="loadViewsFlyout">
<EuiFlyoutHeader>
<EuiTitle size="m">
<h2>
@ -180,7 +159,6 @@ export function SavedViewManageViewsFlyout<ViewState>({
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiInMemoryTable
items={views}
@ -191,9 +169,8 @@ export function SavedViewManageViewsFlyout<ViewState>({
sorting={true}
/>
</EuiFlyoutBody>
<EuiModalFooter>
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}>
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={onClose}>
<FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" />
</EuiButtonEmpty>
</EuiModalFooter>

View file

@ -9,11 +9,12 @@ import React, { useCallback, useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButton, EuiPopover, EuiListGroup, EuiListGroupItem } from '@elastic/eui';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { SavedViewCreateModal } from './create_modal';
import { SavedViewUpdateModal } from './update_modal';
import { FormattedMessage } from '@kbn/i18n-react';
import { SavedViewManageViewsFlyout } from './manage_views_flyout';
import { useSavedViewContext } from '../../containers/saved_view/saved_view';
import { SavedViewListModal } from './view_list_modal';
import { useBoolean } from '../../hooks/use_boolean';
import { UpsertViewModal } from './upsert_modal';
interface Props<ViewState> {
viewState: ViewState;
@ -28,7 +29,6 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
updateView,
deletedId,
deleteView,
defaultViewId,
makeDefault,
sourceIsLoading,
find,
@ -39,48 +39,40 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
currentView,
setCurrentView,
} = useSavedViewContext();
const [modalOpen, setModalOpen] = useState(false);
const [viewListModalOpen, setViewListModalOpen] = useState(false);
const [isPopoverOpen, { off: closePopover, toggle: togglePopover }] = useBoolean(false);
const [isManageFlyoutOpen, { on: openManageFlyout, off: closeManageFlyout }] = useBoolean(false);
const [isUpdateModalOpen, { on: openUpdateModal, off: closeUpdateModal }] = useBoolean(false);
const [isLoadModalOpen, { on: openLoadModal, off: closeLoadModal }] = useBoolean(false);
const [isCreateModalOpen, { on: openCreateModal, off: closeCreateModal }] = useBoolean(false);
const [isInvalid, setIsInvalid] = useState(false);
const [isSavedViewMenuOpen, setIsSavedViewMenuOpen] = useState(false);
const [createModalOpen, setCreateModalOpen] = useState(false);
const [updateModalOpen, setUpdateModalOpen] = useState(false);
const hideSavedViewMenu = useCallback(() => {
setIsSavedViewMenuOpen(false);
}, [setIsSavedViewMenuOpen]);
const openViewListModal = useCallback(() => {
hideSavedViewMenu();
const goToManageViews = () => {
closePopover();
find();
setViewListModalOpen(true);
}, [setViewListModalOpen, find, hideSavedViewMenu]);
const closeViewListModal = useCallback(() => {
setViewListModalOpen(false);
}, [setViewListModalOpen]);
const openSaveModal = useCallback(() => {
hideSavedViewMenu();
setIsInvalid(false);
setCreateModalOpen(true);
}, [hideSavedViewMenu]);
const openUpdateModal = useCallback(() => {
hideSavedViewMenu();
setIsInvalid(false);
setUpdateModalOpen(true);
}, [hideSavedViewMenu]);
const closeModal = useCallback(() => setModalOpen(false), []);
const closeCreateModal = useCallback(() => setCreateModalOpen(false), []);
const closeUpdateModal = useCallback(() => setUpdateModalOpen(false), []);
const loadViews = useCallback(() => {
hideSavedViewMenu();
openManageFlyout();
};
const goToLoadView = () => {
closePopover();
find();
setModalOpen(true);
}, [find, hideSavedViewMenu]);
const showSavedViewMenu = useCallback(() => {
if (isSavedViewMenuOpen) {
setIsSavedViewMenuOpen(false);
return;
}
setIsSavedViewMenuOpen(true);
}, [setIsSavedViewMenuOpen, isSavedViewMenuOpen]);
openLoadModal();
};
const goToCreateView = () => {
closePopover();
setIsInvalid(false);
openCreateModal();
};
const goToUpdateView = () => {
closePopover();
setIsInvalid(false);
openUpdateModal();
};
const save = useCallback(
(name: string, hasTime: boolean = false) => {
const currentState = {
@ -146,7 +138,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
data-test-subj="savedViews-popover"
button={
<EuiButton
onClick={showSavedViewMenu}
onClick={togglePopover}
data-test-subj="savedViews-openPopover"
iconType="arrowDown"
iconSide="right"
@ -159,81 +151,90 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
})}
</EuiButton>
}
isOpen={isSavedViewMenuOpen}
closePopover={hideSavedViewMenu}
isOpen={isPopoverOpen}
closePopover={closePopover}
anchorPosition="leftCenter"
>
<EuiListGroup flush={true}>
<EuiListGroupItem
data-test-subj="savedViews-manageViews"
iconType={'indexSettings'}
onClick={loadViews}
iconType="indexSettings"
onClick={goToManageViews}
label={i18n.translate('xpack.infra.savedView.manageViews', {
defaultMessage: 'Manage views',
})}
/>
<EuiListGroupItem
data-test-subj="savedViews-updateView"
iconType={'refresh'}
onClick={openUpdateModal}
iconType="refresh"
onClick={goToUpdateView}
isDisabled={!currentView || currentView.id === '0'}
label={i18n.translate('xpack.infra.savedView.updateView', {
defaultMessage: 'Update view',
})}
/>
<EuiListGroupItem
data-test-subj="savedViews-loadView"
iconType={'importAction'}
onClick={openViewListModal}
iconType="importAction"
onClick={goToLoadView}
label={i18n.translate('xpack.infra.savedView.loadView', {
defaultMessage: 'Load view',
})}
/>
<EuiListGroupItem
data-test-subj="savedViews-saveNewView"
iconType={'save'}
onClick={openSaveModal}
iconType="save"
onClick={goToCreateView}
label={i18n.translate('xpack.infra.savedView.saveNewView', {
defaultMessage: 'Save new view',
})}
/>
</EuiListGroup>
</EuiPopover>
{createModalOpen && (
<SavedViewCreateModal isInvalid={isInvalid} close={closeCreateModal} save={save} />
)}
{updateModalOpen && (
<SavedViewUpdateModal
currentView={currentView}
{isCreateModalOpen && (
<UpsertViewModal
isInvalid={isInvalid}
close={closeUpdateModal}
save={update}
onClose={closeCreateModal}
onSave={save}
title={
<FormattedMessage
defaultMessage="Save View"
id="xpack.infra.waffle.savedView.createHeader"
/>
}
/>
)}
{viewListModalOpen && (
{isUpdateModalOpen && (
<UpsertViewModal
isInvalid={isInvalid}
onClose={closeUpdateModal}
onSave={update}
initialName={currentView.name}
initialIncludeTime={Boolean(currentView.time)}
title={
<FormattedMessage
defaultMessage="Update View"
id="xpack.infra.waffle.savedView.updateHeader"
/>
}
/>
)}
{isLoadModalOpen && (
<SavedViewListModal<any>
currentView={currentView}
views={views}
close={closeViewListModal}
onClose={closeLoadModal}
setView={setCurrentView}
/>
)}
{modalOpen && (
{isManageFlyoutOpen && (
<SavedViewManageViewsFlyout<ViewState>
sourceIsLoading={sourceIsLoading}
loading={loading}
views={views}
defaultViewId={defaultViewId}
makeDefault={makeDefault}
deleteView={deleteView}
close={closeModal}
onMakeDefaultView={makeDefault}
onDeleteView={deleteView}
onClose={closeManageFlyout}
setView={setCurrentView}
/>
)}

View file

@ -1,111 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useCallback, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
EuiButtonEmpty,
EuiButton,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiFieldText,
EuiSpacer,
EuiSwitch,
EuiText,
} from '@elastic/eui';
interface Props<ViewState> {
isInvalid: boolean;
close(): void;
save(name: string, shouldIncludeTime: boolean): void;
currentView: ViewState;
}
export function SavedViewUpdateModal<ViewState extends { id: string; name: string }>({
close,
save,
isInvalid,
currentView,
}: Props<ViewState>) {
const [viewName, setViewName] = useState(currentView.name);
const [includeTime, setIncludeTime] = useState(false);
const onCheckChange = useCallback((e) => setIncludeTime(e.target.checked), []);
const textChange = useCallback((e) => setViewName(e.target.value), []);
const saveView = useCallback(() => {
save(viewName, includeTime);
}, [includeTime, save, viewName]);
return (
<EuiModal onClose={close}>
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
defaultMessage="Update View"
id="xpack.infra.waffle.savedView.updateHeader"
/>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiFieldText
isInvalid={isInvalid}
placeholder={i18n.translate('xpack.infra.waffle.savedViews.viewNamePlaceholder', {
defaultMessage: 'Name',
})}
data-test-subj="savedViewViweName"
value={viewName}
onChange={textChange}
aria-label={i18n.translate('xpack.infra.waffle.savedViews.viewNamePlaceholder', {
defaultMessage: 'Name',
})}
/>
<EuiSpacer size="xl" />
<EuiSwitch
id={'saved-view-save-time-checkbox'}
label={
<FormattedMessage
defaultMessage="Store time with view"
id="xpack.infra.waffle.savedViews.includeTimeFilterLabel"
/>
}
checked={includeTime}
onChange={onCheckChange}
/>
<EuiSpacer size="s" />
<EuiText size={'xs'} grow={false} style={{ maxWidth: 400 }}>
<FormattedMessage
defaultMessage="This changes the time filter to the currently selected time each time the view is loaded"
id="xpack.infra.waffle.savedViews.includeTimeHelpText"
/>
</EuiText>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty data-test-subj="infraSavedViewUpdateModalCancelButton" onClick={close}>
<FormattedMessage
defaultMessage="Cancel"
id="xpack.infra.waffle.savedViews.cancelButton"
/>
</EuiButtonEmpty>
<EuiButton
color="primary"
disabled={!viewName}
fill={true}
onClick={saveView}
data-test-subj="updateSavedViewButton"
>
<FormattedMessage defaultMessage="Save" id="xpack.infra.waffle.savedViews.saveButton" />
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback, useState } from 'react';
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import {
@ -21,43 +21,54 @@ import {
EuiSwitch,
EuiText,
} from '@elastic/eui';
import { EuiSwitchEvent } from '@elastic/eui';
interface Props {
isInvalid: boolean;
close(): void;
save(name: string, shouldIncludeTime: boolean): void;
onClose(): void;
onSave(name: string, shouldIncludeTime: boolean): void;
initialName?: string;
initialIncludeTime?: boolean;
title: React.ReactNode;
}
export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => {
const [viewName, setViewName] = useState('');
const [includeTime, setIncludeTime] = useState(false);
const onCheckChange = useCallback((e) => setIncludeTime(e.target.checked), []);
const textChange = useCallback((e) => setViewName(e.target.value), []);
export const UpsertViewModal = ({
onClose,
onSave,
isInvalid,
initialName = '',
initialIncludeTime = false,
title,
}: Props) => {
const [viewName, setViewName] = useState(initialName);
const [includeTime, setIncludeTime] = useState(initialIncludeTime);
const saveView = useCallback(() => {
save(viewName, includeTime);
}, [includeTime, save, viewName]);
const handleNameChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
setViewName(e.target.value);
};
const handleTimeCheckChange = (e: EuiSwitchEvent) => {
setIncludeTime(e.target.checked);
};
const saveView = () => {
onSave(viewName, includeTime);
};
return (
<EuiModal onClose={close} data-test-subj="savedViews-createModal">
<EuiModal onClose={onClose} data-test-subj="savedViews-upsertModal">
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
defaultMessage="Save View"
id="xpack.infra.waffle.savedView.createHeader"
/>
</EuiModalHeaderTitle>
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiFieldText
isInvalid={isInvalid}
placeholder={i18n.translate('xpack.infra.waffle.savedViews.viewNamePlaceholder', {
defaultMessage: 'Name',
})}
data-test-subj="savedViewViweName"
data-test-subj="savedViewName"
value={viewName}
onChange={textChange}
onChange={handleNameChange}
aria-label={i18n.translate('xpack.infra.waffle.savedViews.viewNamePlaceholder', {
defaultMessage: 'Name',
})}
@ -72,19 +83,18 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => {
/>
}
checked={includeTime}
onChange={onCheckChange}
onChange={handleTimeCheckChange}
/>
<EuiSpacer size="s" />
<EuiText size={'xs'} grow={false} style={{ maxWidth: 400 }}>
<EuiText size="xs" grow={false} style={{ maxWidth: 400 }}>
<FormattedMessage
defaultMessage="This changes the time filter to the currently selected time each time the view is loaded"
id="xpack.infra.waffle.savedViews.includeTimeHelpText"
/>
</EuiText>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty data-test-subj="infraSavedViewCreateModalCancelButton" onClick={close}>
<EuiButtonEmpty data-test-subj="infraSavedViewCreateModalCancelButton" onClick={onClose}>
<FormattedMessage
defaultMessage="Cancel"
id="xpack.infra.waffle.savedViews.cancelButton"
@ -92,8 +102,8 @@ export const SavedViewCreateModal = ({ close, save, isInvalid }: Props) => {
</EuiButtonEmpty>
<EuiButton
color="primary"
disabled={!viewName}
fill={true}
disabled={viewName.length === 0}
fill
onClick={saveView}
data-test-subj="createSavedViewButton"
>

View file

@ -7,7 +7,7 @@
import React, { useCallback, useState, useMemo } from 'react';
import { EuiButtonEmpty, EuiModalFooter, EuiButton } from '@elastic/eui';
import { EuiButtonEmpty, EuiModalFooter, EuiButton, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiModal, EuiModalHeader, EuiModalHeaderTitle, EuiModalBody } from '@elastic/eui';
import { EuiSelectable } from '@elastic/eui';
@ -17,13 +17,13 @@ import { SavedView } from '../../containers/saved_view/saved_view';
interface Props<ViewState> {
views: Array<SavedView<ViewState>>;
close(): void;
onClose(): void;
setView(viewState: ViewState): void;
currentView?: ViewState;
}
export function SavedViewListModal<ViewState extends { id: string; name: string }>({
close,
onClose,
views,
setView,
currentView,
@ -36,18 +36,18 @@ export function SavedViewListModal<ViewState extends { id: string; name: string
const loadView = useCallback(() => {
if (!options) {
close();
onClose();
return;
}
const selected = options.find((o) => o.checked);
if (!selected) {
close();
onClose();
return;
}
setView(views.find((v) => v.id === selected.key)!);
close();
}, [options, views, setView, close]);
onClose();
}, [options, views, setView, onClose]);
const defaultOptions = useMemo<EuiSelectableOption[]>(() => {
return views.map((v) => ({
@ -58,7 +58,7 @@ export function SavedViewListModal<ViewState extends { id: string; name: string
}, [views, currentView]);
return (
<EuiModal onClose={close} data-test-subj="savedViews-loadModal">
<EuiModal onClose={onClose} data-test-subj="savedViews-loadModal">
<EuiModalHeader>
<EuiModalHeaderTitle>
<FormattedMessage
@ -69,8 +69,8 @@ export function SavedViewListModal<ViewState extends { id: string; name: string
</EuiModalHeader>
<EuiModalBody>
<EuiSelectable
singleSelection={true}
searchable={true}
singleSelection
searchable
options={options || defaultOptions}
onChange={onChange}
searchProps={{
@ -79,27 +79,22 @@ export function SavedViewListModal<ViewState extends { id: string; name: string
}),
}}
listProps={{ bordered: true }}
data-test-subj="savedViews-loadList"
>
{(list, search) => (
<>
{search}
<div style={{ marginTop: 20 }} data-test-subj="savedViews-loadList">
{list}
</div>
<EuiSpacer size="m" />
{list}
</>
)}
</EuiSelectable>
</EuiModalBody>
<EuiModalFooter>
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={close}>
<EuiButtonEmpty data-test-subj="cancelSavedViewModal" onClick={onClose}>
<FormattedMessage defaultMessage="Cancel" id="xpack.infra.openView.cancelButton" />
</EuiButtonEmpty>
<EuiButton
fill={true}
color={'primary'}
data-test-subj="loadSavedViewModal"
onClick={loadView}
>
<EuiButton fill color="primary" data-test-subj="loadSavedViewModal" onClick={loadView}>
<FormattedMessage defaultMessage="Load view" id="xpack.infra.openView.loadButton" />
</EuiButton>
</EuiModalFooter>

View file

@ -0,0 +1,36 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useMemo } from 'react';
import useToggle from 'react-use/lib/useToggle';
export type VoidHandler = () => void;
export type DispatchWithOptionalAction<Type> = (_arg?: Type | unknown) => void;
export interface UseBooleanHandlers {
on: VoidHandler;
off: VoidHandler;
toggle: DispatchWithOptionalAction<boolean>;
}
export type UseBooleanResult = [boolean, UseBooleanHandlers];
export const useBoolean = (initialValue: boolean = false): UseBooleanResult => {
const [value, toggle] = useToggle(initialValue);
const handlers = useMemo(
() => ({
toggle,
on: () => toggle(true),
off: () => toggle(false),
}),
[toggle]
);
return [value, handlers];
};

View file

@ -251,7 +251,7 @@ export function InfraHomePageProvider({ getService, getPageObjects }: FtrProvide
},
async openEnterViewNameAndSave() {
await testSubjects.setValue('savedViewViweName', 'View1');
await testSubjects.setValue('savedViewName', 'View1');
await testSubjects.click('createSavedViewButton');
},

View file

@ -65,13 +65,13 @@ export function InfraSavedViewsProvider({ getService }: FtrProviderContext) {
},
async getCreateSavedViewModal() {
return await testSubjects.find('savedViews-createModal');
return await testSubjects.find('savedViews-upsertModal');
},
async createNewSavedView(name: string) {
await testSubjects.setValue('savedViewViweName', name);
await testSubjects.setValue('savedViewName', name);
await testSubjects.click('createSavedViewButton');
await testSubjects.missingOrFail('savedViews-createModal');
await testSubjects.missingOrFail('savedViews-upsertModal');
},
async ensureViewIsLoaded(name: string) {