Skip to content

Commit edcd453

Browse files
authored
Add multi-filtering to tables; refactor details pages into tabs (#63)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent 10810e3 commit edcd453

19 files changed

+591
-27
lines changed

common/interfaces.ts

+20
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,29 @@ export type Workflow = {
7676
workspaceFlowState?: WorkspaceFlowState;
7777
template: UseCaseTemplate;
7878
lastUpdated: number;
79+
state: WORKFLOW_STATE;
7980
};
8081

8182
export enum USE_CASE {
8283
SEMANTIC_SEARCH = 'semantic_search',
8384
CUSTOM = 'custom',
8485
}
86+
87+
/**
88+
********** MISC TYPES/INTERFACES ************
89+
*/
90+
91+
// TODO: finalize how we have the launch data model
92+
export type WorkflowLaunch = {
93+
id: string;
94+
state: WORKFLOW_STATE;
95+
lastUpdated: number;
96+
};
97+
98+
// TODO: finalize list of possible workflow states from backend
99+
export enum WORKFLOW_STATE {
100+
SUCCEEDED = 'Succeeded',
101+
FAILED = 'Failed',
102+
IN_PROGRESS = 'In progress',
103+
NOT_STARTED = 'Not started',
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.multi-select-filter {
2+
&--width {
3+
width: 150px;
4+
}
5+
}

public/general_components/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export { MultiSelectFilter } from './multi_select_filter';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React, { useState } from 'react';
7+
import {
8+
EuiFilterSelectItem,
9+
EuiFilterGroup,
10+
EuiPopover,
11+
EuiFilterButton,
12+
EuiFlexItem,
13+
} from '@elastic/eui';
14+
15+
// styling
16+
import './general-component-styles.scss';
17+
18+
interface MultiSelectFilterProps {
19+
title: string;
20+
filters: EuiFilterSelectItem[];
21+
setSelectedFilters: (filters: EuiFilterSelectItem[]) => void;
22+
}
23+
24+
/**
25+
* A general multi-select filter.
26+
*/
27+
export function MultiSelectFilter(props: MultiSelectFilterProps) {
28+
const [filters, setFilters] = useState(props.filters);
29+
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
30+
31+
function onButtonClick() {
32+
setIsPopoverOpen(!isPopoverOpen);
33+
}
34+
function onPopoverClose() {
35+
setIsPopoverOpen(false);
36+
}
37+
38+
function updateFilter(index: number) {
39+
if (!filters[index]) {
40+
return;
41+
}
42+
const newFilters = [...filters];
43+
// @ts-ignore
44+
newFilters[index].checked =
45+
// @ts-ignore
46+
newFilters[index].checked === 'on' ? undefined : 'on';
47+
48+
setFilters(newFilters);
49+
props.setSelectedFilters(
50+
// @ts-ignore
51+
newFilters.filter((filter) => filter.checked === 'on')
52+
);
53+
}
54+
55+
return (
56+
<EuiFlexItem grow={false} className="multi-select-filter--width">
57+
<EuiFilterGroup>
58+
<EuiPopover
59+
button={
60+
<EuiFilterButton
61+
iconType="arrowDown"
62+
onClick={onButtonClick}
63+
isSelected={isPopoverOpen}
64+
numFilters={filters.length}
65+
hasActiveFilters={
66+
// @ts-ignore
67+
!!filters.find((filter) => filter.checked === 'on')
68+
}
69+
numActiveFilters={
70+
// @ts-ignore
71+
filters.filter((filter) => filter.checked === 'on').length
72+
}
73+
>
74+
{props.title}
75+
</EuiFilterButton>
76+
}
77+
isOpen={isPopoverOpen}
78+
closePopover={onPopoverClose}
79+
panelPaddingSize="none"
80+
>
81+
<div className="euiFilterSelect__items multi-select-filter--width">
82+
{filters.map((filter, index) => (
83+
<EuiFilterSelectItem
84+
// @ts-ignore
85+
checked={filter.checked}
86+
key={index}
87+
onClick={() => updateFilter(index)}
88+
>
89+
{/* @ts-ignore */}
90+
{filter.name}
91+
</EuiFilterSelectItem>
92+
))}
93+
</div>
94+
</EuiPopover>
95+
</EuiFilterGroup>
96+
</EuiFlexItem>
97+
);
98+
}

public/pages/workflow_detail/components/header.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { saveWorkflow } from '../utils';
1111
import { rfContext, AppState, removeDirty } from '../../../store';
1212

1313
interface WorkflowDetailHeaderProps {
14+
tabs: any[];
1415
workflow?: Workflow;
1516
}
1617

@@ -22,7 +23,6 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
2223
return (
2324
<EuiPageHeader
2425
pageTitle={props.workflow ? props.workflow.name : ''}
25-
description={props.workflow ? props.workflow.description : ''}
2626
rightSideItems={[
2727
<EuiButton fill={false} onClick={() => {}}>
2828
Prototype
@@ -39,6 +39,8 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
3939
Save
4040
</EuiButton>,
4141
]}
42+
tabs={props.tabs}
43+
bottomBorder={true}
4244
/>
4345
);
4446
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export const columns = [
7+
{
8+
field: 'id',
9+
name: 'Launch ID',
10+
sortable: true,
11+
},
12+
{
13+
field: 'state',
14+
name: 'Status',
15+
sortable: true,
16+
},
17+
{
18+
field: 'lastUpdatedTime',
19+
name: 'Last updated time',
20+
sortable: true,
21+
},
22+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export { Launches } from './launches';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import { EuiText } from '@elastic/eui';
8+
9+
interface LaunchDetailsProps {}
10+
11+
export function LaunchDetails(props: LaunchDetailsProps) {
12+
return <EuiText>TODO: add selected launch details here</EuiText>;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React, { useState, useEffect } from 'react';
7+
import { debounce } from 'lodash';
8+
import {
9+
EuiInMemoryTable,
10+
Direction,
11+
EuiFlexGroup,
12+
EuiFlexItem,
13+
EuiFieldSearch,
14+
EuiFilterSelectItem,
15+
} from '@elastic/eui';
16+
import { WORKFLOW_STATE, WorkflowLaunch } from '../../../../common';
17+
import { columns } from './columns';
18+
import { MultiSelectFilter } from '../../../general_components';
19+
import { getStateOptions } from '../../../utils';
20+
21+
interface LaunchListProps {}
22+
23+
/**
24+
* The searchable list of launches for this particular workflow.
25+
*/
26+
export function LaunchList(props: LaunchListProps) {
27+
// TODO: finalize how we persist launches for a particular workflow.
28+
// We may just add UI metadata tags to group workflows under a single, overall "workflow"
29+
// const { workflows } = useSelector((state: AppState) => state.workflows);
30+
const workflowLaunches = [
31+
{
32+
id: 'Launch_1',
33+
state: WORKFLOW_STATE.IN_PROGRESS,
34+
lastUpdated: 12345678,
35+
},
36+
{
37+
id: 'Launch_2',
38+
state: WORKFLOW_STATE.FAILED,
39+
lastUpdated: 12345677,
40+
},
41+
] as WorkflowLaunch[];
42+
43+
// search bar state
44+
const [searchQuery, setSearchQuery] = useState<string>('');
45+
const debounceSearchQuery = debounce((query: string) => {
46+
setSearchQuery(query);
47+
}, 100);
48+
49+
// filters state
50+
const [selectedStates, setSelectedStates] = useState<EuiFilterSelectItem[]>(
51+
getStateOptions()
52+
);
53+
const [filteredLaunches, setFilteredLaunches] = useState<WorkflowLaunch[]>(
54+
workflowLaunches
55+
);
56+
57+
// When a filter selection or search query changes, update the filtered launches
58+
useEffect(() => {
59+
setFilteredLaunches(
60+
fetchFilteredLaunches(workflowLaunches, selectedStates, searchQuery)
61+
);
62+
}, [selectedStates, searchQuery]);
63+
64+
const sorting = {
65+
sort: {
66+
field: 'id',
67+
direction: 'asc' as Direction,
68+
},
69+
};
70+
71+
return (
72+
<EuiFlexGroup direction="column">
73+
<EuiFlexItem>
74+
<EuiFlexGroup direction="row" gutterSize="m">
75+
<EuiFlexItem grow={true}>
76+
<EuiFieldSearch
77+
fullWidth={true}
78+
placeholder="Search launches..."
79+
onChange={(e) => debounceSearchQuery(e.target.value)}
80+
/>
81+
</EuiFlexItem>
82+
<MultiSelectFilter
83+
filters={getStateOptions()}
84+
title="Status"
85+
setSelectedFilters={setSelectedStates}
86+
/>
87+
</EuiFlexGroup>
88+
</EuiFlexItem>
89+
<EuiFlexItem>
90+
<EuiInMemoryTable<WorkflowLaunch>
91+
items={filteredLaunches}
92+
rowHeader="id"
93+
columns={columns}
94+
sorting={sorting}
95+
pagination={true}
96+
message={'No existing launches found'}
97+
/>
98+
</EuiFlexItem>
99+
</EuiFlexGroup>
100+
);
101+
}
102+
103+
// Collect the final launch list after applying all filters
104+
function fetchFilteredLaunches(
105+
allLaunches: WorkflowLaunch[],
106+
stateFilters: EuiFilterSelectItem[],
107+
searchQuery: string
108+
): WorkflowLaunch[] {
109+
// @ts-ignore
110+
const stateFilterStrings = stateFilters.map((filter) => filter.name);
111+
const filteredLaunches = allLaunches.filter((launch) =>
112+
stateFilterStrings.includes(launch.state)
113+
);
114+
return searchQuery.length === 0
115+
? filteredLaunches
116+
: filteredLaunches.filter((launch) =>
117+
launch.id.toLowerCase().includes(searchQuery.toLowerCase())
118+
);
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import {
8+
EuiFlexGroup,
9+
EuiFlexItem,
10+
EuiPageContent,
11+
EuiSpacer,
12+
EuiTitle,
13+
} from '@elastic/eui';
14+
import { Workflow } from '../../../../common';
15+
import { LaunchList } from './launch_list';
16+
import { LaunchDetails } from './launch_details';
17+
18+
interface LaunchesProps {
19+
workflow?: Workflow;
20+
}
21+
22+
/**
23+
* The launches page to browse launch history and view individual launch details.
24+
*/
25+
export function Launches(props: LaunchesProps) {
26+
return (
27+
<EuiPageContent>
28+
<EuiTitle>
29+
<h2>Launches</h2>
30+
</EuiTitle>
31+
<EuiSpacer size="m" />
32+
<EuiFlexGroup direction="row">
33+
<EuiFlexItem>
34+
<LaunchList />
35+
</EuiFlexItem>
36+
<EuiFlexItem>
37+
<LaunchDetails />
38+
</EuiFlexItem>
39+
</EuiFlexGroup>
40+
</EuiPageContent>
41+
);
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export { Prototype } from './prototype';

0 commit comments

Comments
 (0)