Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Metrics UI] UX improvements for saved views #69910

Merged
merged 16 commits into from
Jun 29, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add loading indicator and other polish
phillipb committed Jun 26, 2020
commit 1b677ed7d35afeccc7d08903b4524b9f4d5c9630
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ interface Props<ViewState> {
views: Array<SavedView<ViewState>>;
loading: boolean;
defaultViewId: string;
sourceIsLoading: boolean;
close(): void;
makeDefault(id: string): void;
setView(viewState: ViewState): void;
@@ -76,7 +77,9 @@ export function SavedViewManageViewsFlyout<ViewState>({
makeDefault,
deleteView,
loading,
sourceIsLoading,
}: Props<ViewState>) {
const [inProgressView, setInProgressView] = useState<string | null>(null);
const renderName = useCallback(
(name: string, item: SavedView<ViewState>) => (
<EuiButtonEmpty
@@ -111,13 +114,17 @@ export function SavedViewManageViewsFlyout<ViewState>({
return (
<>
<EuiButtonEmpty
isLoading={inProgressView === item.id && sourceIsLoading}
iconType={isDefault ? 'starFilled' : 'starEmpty'}
onClick={() => makeDefault(item.id)}
onClick={() => {
setInProgressView(item.id);
makeDefault(item.id);
}}
/>
</>
);
},
[makeDefault, defaultViewId]
[makeDefault, defaultViewId, sourceIsLoading, inProgressView]
);

const columns = [
Original file line number Diff line number Diff line change
@@ -28,7 +28,9 @@ interface Props<ViewState> {
viewState: ViewState;
}

export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
export function SavedViewsToolbarControls<ViewState extends { id: string; name: string }>(
props: Props<ViewState>
) {
const kibana = useKibana();
const {
views,
@@ -39,6 +41,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
deleteView,
defaultViewId,
makeDefault,
sourceIsLoading,
find,
errorOnFind,
errorOnCreate,
@@ -220,11 +223,17 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {
)}

{updateModalOpen && (
<SavedViewUpdateModal isInvalid={isInvalid} close={closeUpdateModal} save={update} />
<SavedViewUpdateModal
currentView={currentView}
isInvalid={isInvalid}
close={closeUpdateModal}
save={update}
/>
)}

{viewListModalOpen && (
<SavedViewListModal<ViewState>
currentView={currentView}
views={views}
close={closeViewListModal}
setView={setCurrentView}
@@ -233,6 +242,7 @@ export function SavedViewsToolbarControls<ViewState>(props: Props<ViewState>) {

{modalOpen && (
<SavedViewManageViewsFlyout<ViewState>
sourceIsLoading={sourceIsLoading}
loading={loading}
views={views}
defaultViewId={defaultViewId}
Original file line number Diff line number Diff line change
@@ -22,14 +22,20 @@ import {
EuiText,
} from '@elastic/eui';

interface Props {
interface Props<ViewState> {
isInvalid: boolean;
close(): void;
save(name: string, shouldIncludeTime: boolean): void;
currentView: ViewState;
}

export const SavedViewUpdateModal = ({ close, save, isInvalid }: Props) => {
const [viewName, setViewName] = useState('');
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), []);
@@ -104,4 +110,4 @@ export const SavedViewUpdateModal = ({ close, save, isInvalid }: Props) => {
</EuiModal>
</EuiOverlayMask>
);
};
}
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { useCallback, useState } from 'react';
import React, { useCallback, useState, useMemo } from 'react';

import { EuiButtonEmpty, EuiModalFooter, EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
@@ -24,9 +24,15 @@ interface Props<ViewState> {
views: Array<SavedView<ViewState>>;
close(): void;
setView(viewState: ViewState): void;
currentView?: ViewState;
}

export function SavedViewListModal<ViewState>({ close, views, setView }: Props<ViewState>) {
export function SavedViewListModal<ViewState extends { id: string; name: string }>({
close,
views,
setView,
currentView,
}: Props<ViewState>) {
const [options, setOptions] = useState<EuiSelectableOption[] | null>(null);

const onChange = useCallback((opts: EuiSelectableOption[]) => {
@@ -48,6 +54,14 @@ export function SavedViewListModal<ViewState>({ close, views, setView }: Props<V
close();
}, [options, views, setView, close]);

const defaultOptions = useMemo<EuiSelectableOption[]>(() => {
return views.map((v) => ({
label: v.name,
key: v.id,
checked: currentView?.id === v.id ? 'on' : undefined,
}));
}, [views, currentView]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<EuiOverlayMask>
<EuiModal onClose={close}>
@@ -63,7 +77,7 @@ export function SavedViewListModal<ViewState>({ close, views, setView }: Props<V
<EuiSelectable
singleSelection={true}
searchable={true}
options={options || views.map((v) => ({ label: v.name, key: v.id }))}
options={options || defaultOptions}
onChange={onChange}
searchProps={{
placeholder: i18n.translate('xpack.infra.savedView.searchPlaceholder', {
15 changes: 10 additions & 5 deletions x-pack/plugins/infra/public/containers/saved_view/saved_view.tsx
Original file line number Diff line number Diff line change
@@ -38,9 +38,13 @@ interface Props {
}

export const useSavedView = (props: Props) => {
const { source, sourceExists, createSourceConfiguration, updateSourceConfiguration } = useContext(
Source.Context
);
const {
source,
isLoading: sourceIsLoading,
sourceExists,
createSourceConfiguration,
updateSourceConfiguration,
} = useContext(Source.Context);
const { viewType, defaultViewState } = props;
type ViewState = typeof defaultViewState;
const { data, loading, find, error: errorOnFind, hasView } = useFindSavedObject<
@@ -115,8 +119,8 @@ export const useSavedView = (props: Props) => {
const updateView = useCallback(
(id, d: { [p: string]: any }) => {
const doSave = async () => {
const exists = await hasView(d.name);
if (exists) {
const view = await hasView(d.name);
if (view && view.id !== id) {
setCreateError(
i18n.translate('xpack.infra.savedView.errorOnCreate.duplicateViewName', {
defaultMessage: `A view with that name already exists.`,
@@ -244,6 +248,7 @@ export const useSavedView = (props: Props) => {
errorOnCreate: createError,
shouldLoadDefault: props.shouldLoadDefault,
makeDefault,
sourceIsLoading,
deleteView,
loadingDefaultView,
setCurrentView,
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ export const useFindSavedObject = <SavedObjectType extends SavedObjectAttributes
const objects = await savedObjectsClient.find<SavedObjectType>({
type,
});
return objects.savedObjects.filter((o) => o.attributes.name === name).length > 0;
return objects.savedObjects.find((o) => o.attributes.name === name);
phillipb marked this conversation as resolved.
Show resolved Hide resolved
};

return {