mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
077245606b
commit
c6fa0f9e09
8 changed files with 245 additions and 337 deletions
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
>
|
|
@ -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>
|
||||
|
|
36
x-pack/plugins/infra/public/hooks/use_boolean.ts
Normal file
36
x-pack/plugins/infra/public/hooks/use_boolean.ts
Normal 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];
|
||||
};
|
|
@ -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');
|
||||
},
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue