Skip to content

Commit

Permalink
[inspector] clusters tab search bar (#171806)
Browse files Browse the repository at this point in the history
#167666

PR adds table sorting. In picture below, table is sorted by request time
<img width="200" alt="Screenshot 2023-11-22 at 2 13 41 PM"
src="https://github.com/elastic/kibana/assets/373691/710b0a3e-875b-463a-8344-f171b37df506">

PR adds search bar. Screen shot below shows status filter popover
<img width="200" alt="Screenshot 2023-11-22 at 2 14 18 PM"
src="https://github.com/elastic/kibana/assets/373691/644eeb47-0eba-4742-a381-4f997fbdf379">

Then, once search selections are made, the search bar filters the health
bar and table. In the screen shot below, the table only display remote1
because its the only cluster that matches the status.
<img width="200" alt="Screenshot 2023-11-22 at 2 14 11 PM"
src="https://github.com/elastic/kibana/assets/373691/e9491c88-1f11-4179-ad4a-476f8fd210c0">

### test instructions 
1. Follow CCS setup instructions from
#164350.
2. Open discover
3. Open inspector "clusters and shards" tab. Try sorting table and using
search bar to narrow clusters

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
nreese and kibanamachine authored Nov 27, 2023
1 parent 2b36209 commit 8af39c3
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 155 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@

import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiHealth, EuiText } from '@elastic/eui';
import { EuiHealth, EuiText, EuiTextProps } from '@elastic/eui';
import { HEALTH_HEX_CODES } from './gradient';

interface Props {
count?: number;
status: string;
textProps?: EuiTextProps;
}

export function ClusterHealth({ count, status }: Props) {
const defaultTextProps: EuiTextProps = {
size: 'xs',
color: 'subdued',
};

export function ClusterHealth({ count, status, textProps = defaultTextProps }: Props) {
if (typeof count === 'number' && count === 0) {
return null;
}
Expand Down Expand Up @@ -48,9 +54,7 @@ export function ClusterHealth({ count, status }: Props) {
const label = typeof count === 'number' ? `${count} ${statusLabel}` : statusLabel;
return (
<EuiHealth color={color}>
<EuiText size="xs" color="subdued">
{label}
</EuiText>
<EuiText {...textProps}>{label}</EuiText>
</EuiHealth>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import type { ClusterDetails } from '@kbn/es-types';
import { render, screen, fireEvent } from '@testing-library/react';
import { ClustersTable } from './clusters_table';

describe('ClustersTable', () => {
describe('sorting', () => {
const clusters = {
remote1: {
status: 'successful',
took: 50,
} as unknown as ClusterDetails,
remote2: {
status: 'skipped',
took: 1000,
} as unknown as ClusterDetails,
remote3: {
status: 'failed',
took: 90,
} as unknown as ClusterDetails,
};

test('should render rows in native order', () => {
render(<ClustersTable clusters={clusters} />);
const tableRows = screen.getAllByRole('row');
expect(tableRows.length).toBe(4); // 1 header row, 3 data rows
expect(tableRows[1]).toHaveTextContent('Nameremote1StatussuccessfulResponse time50ms');
expect(tableRows[2]).toHaveTextContent('Nameremote2StatusskippedResponse time1000ms');
expect(tableRows[3]).toHaveTextContent('Nameremote3StatusfailedResponse time90ms');
});

test('should sort by response time', () => {
render(<ClustersTable clusters={clusters} />);
const button = screen.getByRole('button', {
name: 'Response time',
});
fireEvent.click(button);
const tableRowsAsc = screen.getAllByRole('row');
expect(tableRowsAsc.length).toBe(4); // 1 header row, 3 data rows
expect(tableRowsAsc[1]).toHaveTextContent('Nameremote1StatussuccessfulResponse time50ms');
expect(tableRowsAsc[2]).toHaveTextContent('Nameremote3StatusfailedResponse time90ms');
expect(tableRowsAsc[3]).toHaveTextContent('Nameremote2StatusskippedResponse time1000ms');

fireEvent.click(button);
const tableRowsDesc = screen.getAllByRole('row');
expect(tableRowsDesc.length).toBe(4); // 1 header row, 3 data rows
expect(tableRowsDesc[1]).toHaveTextContent('Nameremote2StatusskippedResponse time1000ms');
expect(tableRowsDesc[2]).toHaveTextContent('Nameremote3StatusfailedResponse time90ms');
expect(tableRowsDesc[3]).toHaveTextContent('Nameremote1StatussuccessfulResponse time50ms');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@
* Side Public License, v 1.
*/

import React, { useState, ReactNode } from 'react';
import React, { useMemo, useState, ReactNode } from 'react';
import type { ClusterDetails } from '@kbn/es-types';
import { i18n } from '@kbn/i18n';
import { EuiBasicTable, type EuiBasicTableColumn, EuiButtonIcon, EuiText } from '@elastic/eui';
import {
Comparators,
EuiBasicTable,
type EuiBasicTableColumn,
EuiButtonIcon,
EuiText,
Criteria,
} from '@elastic/eui';
import { ClusterView } from './cluster_view';
import { ClusterHealth } from '../clusters_health';
import { LOCAL_CLUSTER_KEY } from '../local_cluster';
Expand All @@ -21,7 +28,7 @@ function getInitialExpandedRow(clusters: Record<string, ClusterDetails>) {
: {};
}

interface ClusterColumn {
interface ClusterItem {
name: string;
status: string;
responseTime?: number;
Expand All @@ -35,6 +42,8 @@ export function ClustersTable({ clusters }: Props) {
const [expandedRows, setExpandedRows] = useState<Record<string, ReactNode>>(
getInitialExpandedRow(clusters)
);
const [sortField, setSortField] = useState<undefined | keyof ClusterItem>();
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');

const toggleDetails = (name: string) => {
const nextExpandedRows = { ...expandedRows };
Expand All @@ -46,7 +55,17 @@ export function ClustersTable({ clusters }: Props) {
setExpandedRows(nextExpandedRows);
};

const columns: Array<EuiBasicTableColumn<ClusterColumn>> = [
const items = useMemo(() => {
return Object.keys(clusters).map((key) => {
return {
name: key,
status: clusters[key].status,
responseTime: clusters[key].took,
};
});
}, [clusters]);

const columns: Array<EuiBasicTableColumn<ClusterItem>> = [
{
field: 'name',
name: i18n.translate('inspector.requests.clusters.table.nameLabel', {
Expand Down Expand Up @@ -78,6 +97,7 @@ export function ClustersTable({ clusters }: Props) {
</>
);
},
sortable: items.length > 1,
width: '60%',
},
{
Expand All @@ -88,6 +108,7 @@ export function ClustersTable({ clusters }: Props) {
render: (status: string) => {
return <ClusterHealth status={status} />;
},
sortable: items.length > 1,
},
{
align: 'right' as 'right',
Expand All @@ -105,22 +126,38 @@ export function ClustersTable({ clusters }: Props) {
: null}
</EuiText>
),
sortable: items.length > 1,
},
];

return (
<EuiBasicTable
items={Object.keys(clusters).map((key) => {
return {
name: key,
status: clusters[key].status,
responseTime: clusters[key].took,
};
})}
items={
sortField
? items.sort(Comparators.property(sortField, Comparators.default(sortDirection)))
: items
}
isExpandable={true}
itemIdToExpandedRowMap={expandedRows}
itemId="name"
columns={columns}
sorting={{
sort: sortField
? {
field: sortField,
direction: sortDirection,
}
: undefined,
}}
onChange={({ sort }: Criteria<ClusterItem>) => {
if (sort) {
setSortField(sort.field);
setSortDirection(sort.direction);
}
}}
noItemsMessage={i18n.translate('inspector.requests.clusters.table.noItemsFound', {
defaultMessage: 'No clusters found',
})}
/>
);
}
Loading

0 comments on commit 8af39c3

Please sign in to comment.