Skip to content

Commit

Permalink
[Infrastructure UI] Hosts view flyout metadata search (#154556)
Browse files Browse the repository at this point in the history
Closes #154347 

## Summary

This PR adds search functionality to the metadata tab.
In order to optimize the search I changed the table to
`EuiInMemoryTable` and handled the search there. One benefit is that
table filtering is the responsibility of the table and the cases to
handle errors/no data found are much easier.
<img width="2435" alt="Screenshot 2023-04-06 at 15 36 35"
src="https://user-images.githubusercontent.com/14139027/230400195-b77b7783-9c4d-48b0-85e6-cb38180a29d3.png">

<img width="2434" alt="Screenshot 2023-04-06 at 15 58 22"
src="https://user-images.githubusercontent.com/14139027/230400337-1013626c-c802-4b45-88f1-bff67f0ec37e.png">

This also helped to get rid of some of the callouts condition and leave
the table component to decide what to render based on the items and
loading state. That way the loading looks much smoother rather than
replacing the table with a loading component - also when loading and
there are no results the loading indicator is inside the table.

![image](https://user-images.githubusercontent.com/14139027/230401218-04871ce9-ceba-4803-8259-7978c4152ee9.png)

## Testing
1. Open the flyout for a single host
2. On the metadata tab start searching
    a. Try to search for field name/value - should get a result
b. Do a typo with an invalid character (or just enter only ```, `+`,
etc) - an error message should be displayed (and ⚠️ icon in the search
bar)
c. Try to search for something that it's not a field name/value - should
display the `No metadata found.` message in the table.
3. Copy a URL after searching for something and paste it into a new
browser tab/window - it should persist the search term. (In case of a
search error the search filter is not persisted)



https://user-images.githubusercontent.com/14139027/230400149-6ba4dc32-efaa-4068-8abb-24b6ae43de76.mov
  • Loading branch information
jennypavlova authored Apr 12, 2023
1 parent 57d6f41 commit 5a03c5d
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,23 @@ describe('Single Host Metadata (Hosts View)', () => {
mockUseMetadata({ metadata: [] });
const result = renderHostMetadata();

expect(result.queryByTestId('infraMetadataNoData')).toBeInTheDocument();
expect(result.queryByTestId('infraHostMetadataSearchBarInput')).toBeInTheDocument();
expect(result.queryByTestId('infraHostMetadataNoData')).toBeInTheDocument();
});

it('should return spinner if loading', async () => {
it('should show the metadata table if metadata is returned', async () => {
mockUseMetadata({ metadata: [{ name: 'host.os.name', value: 'Ubuntu' }] });
const result = renderHostMetadata();

expect(result.queryByTestId('infraHostMetadataSearchBarInput')).toBeInTheDocument();
expect(result.queryByTestId('infraMetadataTable')).toBeInTheDocument();
});

it('should return loading text if loading', async () => {
mockUseMetadata({ loading: true });
const result = renderHostMetadata();

expect(result.queryByTestId('infraHostMetadataSearchBarInput')).toBeInTheDocument();
expect(result.queryByTestId('infraHostMetadataLoading')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import React, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiLoadingChart } from '@elastic/eui';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useSourceContext } from '../../../../../../containers/metrics_source';
Expand All @@ -31,17 +30,13 @@ export const Metadata = ({ node, currentTimeRange, nodeType }: TabProps) => {
const { sourceId } = useSourceContext();
const {
loading: metadataLoading,
error,
error: fetchMetadataError,
metadata,
} = useMetadata(nodeId, nodeType, inventoryModel.requiredMetrics, sourceId, currentTimeRange);

const fields = useMemo(() => getAllFields(metadata), [metadata]);

if (metadataLoading) {
return <LoadingPlaceholder />;
}

if (error) {
if (fetchMetadataError) {
return (
<EuiCallOut
title={i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.errorTitle', {
Expand Down Expand Up @@ -71,33 +66,5 @@ export const Metadata = ({ node, currentTimeRange, nodeType }: TabProps) => {
);
}

return fields.length > 0 ? (
<Table rows={fields} />
) : (
<EuiCallOut
data-test-subj="infraMetadataNoData"
title={i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.noMetadataFound', {
defaultMessage: 'Sorry, there is no metadata related to this host.',
})}
size="m"
iconType="iInCircle"
/>
);
};

const LoadingPlaceholder = () => {
return (
<div
style={{
width: '100%',
height: '200px',
padding: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<EuiLoadingChart data-test-subj="infraHostMetadataLoading" size="xl" />
</div>
);
return <Table rows={fields} loading={metadataLoading} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,21 @@
* 2.0.
*/

import { EuiText, EuiFlexGroup, EuiFlexItem, EuiLink, EuiBasicTable } from '@elastic/eui';
import {
EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiInMemoryTable,
EuiSearchBarProps,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import useToggle from 'react-use/lib/useToggle';
import { debounce } from 'lodash';
import { Query } from '@elastic/eui';
import { useHostFlyoutOpen } from '../../../hooks/use_host_flyout_open_url_state';

interface Row {
name: string;
Expand All @@ -18,6 +28,11 @@ interface Row {

interface Props {
rows: Row[];
loading: boolean;
}

interface SearchErrorType {
message: string;
}

/**
Expand All @@ -31,8 +46,65 @@ const VALUE_LABEL = i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadat
defaultMessage: 'Value',
});

/**
* Component translations
*/
const SEARCH_PLACEHOLDER = i18n.translate(
'xpack.infra.hostsViewPage.hostDetail.metadata.searchForMetadata',
{
defaultMessage: 'Search for metadata…',
}
);

const NO_METADATA_FOUND = i18n.translate(
'xpack.infra.hostsViewPage.hostDetail.metadata.noMetadataFound',
{
defaultMessage: 'No metadata found.',
}
);

const LOADING = i18n.translate('xpack.infra.hostsViewPage.hostDetail.metadata.loading', {
defaultMessage: 'Loading...',
});

export const Table = (props: Props) => {
const { rows } = props;
const { rows, loading } = props;
const [searchError, setSearchError] = useState<SearchErrorType | null>(null);
const [hostFlyoutOpen, setHostFlyoutOpen] = useHostFlyoutOpen();

const debouncedSearchOnChange = useMemo(
() =>
debounce<(queryText: string) => void>((queryText) => {
setHostFlyoutOpen({ metadataSearch: String(queryText) ?? '' });
}, 500),
[setHostFlyoutOpen]
);

const searchBarOnChange = useCallback(
({ queryText, error }) => {
if (error) {
setSearchError(error);
} else {
setSearchError(null);
debouncedSearchOnChange(queryText);
}
},
[debouncedSearchOnChange]
);

const search: EuiSearchBarProps = {
onChange: searchBarOnChange,
box: {
'data-test-subj': 'infraHostMetadataSearchBarInput',
incremental: true,
schema: true,
placeholder: SEARCH_PLACEHOLDER,
},
query: hostFlyoutOpen.metadataSearch
? Query.parse(hostFlyoutOpen.metadataSearch)
: Query.MATCH_ALL,
};

const columns = useMemo(
() => [
{
Expand All @@ -54,12 +126,22 @@ export const Table = (props: Props) => {
);

return (
<EuiBasicTable
<EuiInMemoryTable
data-test-subj="infraMetadataTable"
tableLayout={'fixed'}
responsive={false}
columns={columns}
items={rows}
search={search}
loading={loading}
error={searchError ? `${searchError.message}` : ''}
message={
loading ? (
<div data-test-subj="infraHostMetadataLoading">{LOADING}</div>
) : (
<div data-test-subj="infraHostMetadataNoData">{NO_METADATA_FOUND}</div>
)
}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const GET_DEFAULT_TABLE_PROPERTIES = {
clickedItemId: '',
selectedTabId: FlyoutTabIds.METADATA,
searchFilter: '',
metadataSearch: '',
};
const HOST_TABLE_PROPERTIES_URL_STATE_KEY = 'hostFlyoutOpen';

Expand Down Expand Up @@ -55,12 +56,22 @@ const SetSearchFilterRT = rt.partial({
searchFilter: SearchFilterRT,
});

const ActionRT = rt.intersection([SetClickedItemIdRT, SetFlyoutTabId, SetSearchFilterRT]);
const SetMetadataSearchRT = rt.partial({
metadataSearch: SearchFilterRT,
});

const ActionRT = rt.intersection([
SetClickedItemIdRT,
SetFlyoutTabId,
SetSearchFilterRT,
SetMetadataSearchRT,
]);

const HostFlyoutOpenRT = rt.type({
clickedItemId: ClickedItemIdRT,
selectedTabId: FlyoutTabIdRT,
searchFilter: SearchFilterRT,
metadataSearch: SearchFilterRT,
});

type HostFlyoutOpen = rt.TypeOf<typeof HostFlyoutOpenRT>;
Expand Down

0 comments on commit 5a03c5d

Please sign in to comment.