Skip to content

Commit

Permalink
Add multi-filtering to tables; refactor details pages into tabs (#63)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler authored Feb 26, 2024
1 parent 10810e3 commit edcd453
Show file tree
Hide file tree
Showing 19 changed files with 591 additions and 27 deletions.
20 changes: 20 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,29 @@ export type Workflow = {
workspaceFlowState?: WorkspaceFlowState;
template: UseCaseTemplate;
lastUpdated: number;
state: WORKFLOW_STATE;
};

export enum USE_CASE {
SEMANTIC_SEARCH = 'semantic_search',
CUSTOM = 'custom',
}

/**
********** MISC TYPES/INTERFACES ************
*/

// TODO: finalize how we have the launch data model
export type WorkflowLaunch = {
id: string;
state: WORKFLOW_STATE;
lastUpdated: number;
};

// TODO: finalize list of possible workflow states from backend
export enum WORKFLOW_STATE {
SUCCEEDED = 'Succeeded',
FAILED = 'Failed',
IN_PROGRESS = 'In progress',
NOT_STARTED = 'Not started',
}
5 changes: 5 additions & 0 deletions public/general_components/general-component-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.multi-select-filter {
&--width {
width: 150px;
}
}
6 changes: 6 additions & 0 deletions public/general_components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { MultiSelectFilter } from './multi_select_filter';
98 changes: 98 additions & 0 deletions public/general_components/multi_select_filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import {
EuiFilterSelectItem,
EuiFilterGroup,
EuiPopover,
EuiFilterButton,
EuiFlexItem,
} from '@elastic/eui';

// styling
import './general-component-styles.scss';

interface MultiSelectFilterProps {
title: string;
filters: EuiFilterSelectItem[];
setSelectedFilters: (filters: EuiFilterSelectItem[]) => void;
}

/**
* A general multi-select filter.
*/
export function MultiSelectFilter(props: MultiSelectFilterProps) {
const [filters, setFilters] = useState(props.filters);
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

function onButtonClick() {
setIsPopoverOpen(!isPopoverOpen);
}
function onPopoverClose() {
setIsPopoverOpen(false);
}

function updateFilter(index: number) {
if (!filters[index]) {
return;
}
const newFilters = [...filters];
// @ts-ignore
newFilters[index].checked =
// @ts-ignore
newFilters[index].checked === 'on' ? undefined : 'on';

setFilters(newFilters);
props.setSelectedFilters(
// @ts-ignore
newFilters.filter((filter) => filter.checked === 'on')
);
}

return (
<EuiFlexItem grow={false} className="multi-select-filter--width">
<EuiFilterGroup>
<EuiPopover
button={
<EuiFilterButton
iconType="arrowDown"
onClick={onButtonClick}
isSelected={isPopoverOpen}
numFilters={filters.length}
hasActiveFilters={
// @ts-ignore
!!filters.find((filter) => filter.checked === 'on')
}
numActiveFilters={
// @ts-ignore
filters.filter((filter) => filter.checked === 'on').length
}
>
{props.title}
</EuiFilterButton>
}
isOpen={isPopoverOpen}
closePopover={onPopoverClose}
panelPaddingSize="none"
>
<div className="euiFilterSelect__items multi-select-filter--width">
{filters.map((filter, index) => (
<EuiFilterSelectItem
// @ts-ignore
checked={filter.checked}
key={index}
onClick={() => updateFilter(index)}
>
{/* @ts-ignore */}
{filter.name}
</EuiFilterSelectItem>
))}
</div>
</EuiPopover>
</EuiFilterGroup>
</EuiFlexItem>
);
}
4 changes: 3 additions & 1 deletion public/pages/workflow_detail/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { saveWorkflow } from '../utils';
import { rfContext, AppState, removeDirty } from '../../../store';

interface WorkflowDetailHeaderProps {
tabs: any[];
workflow?: Workflow;
}

Expand All @@ -22,7 +23,6 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
return (
<EuiPageHeader
pageTitle={props.workflow ? props.workflow.name : ''}
description={props.workflow ? props.workflow.description : ''}
rightSideItems={[
<EuiButton fill={false} onClick={() => {}}>
Prototype
Expand All @@ -39,6 +39,8 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {
Save
</EuiButton>,
]}
tabs={props.tabs}
bottomBorder={true}
/>
);
}
22 changes: 22 additions & 0 deletions public/pages/workflow_detail/launches/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const columns = [
{
field: 'id',
name: 'Launch ID',
sortable: true,
},
{
field: 'state',
name: 'Status',
sortable: true,
},
{
field: 'lastUpdatedTime',
name: 'Last updated time',
sortable: true,
},
];
6 changes: 6 additions & 0 deletions public/pages/workflow_detail/launches/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { Launches } from './launches';
13 changes: 13 additions & 0 deletions public/pages/workflow_detail/launches/launch_details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiText } from '@elastic/eui';

interface LaunchDetailsProps {}

export function LaunchDetails(props: LaunchDetailsProps) {
return <EuiText>TODO: add selected launch details here</EuiText>;
}
119 changes: 119 additions & 0 deletions public/pages/workflow_detail/launches/launch_list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from 'react';
import { debounce } from 'lodash';
import {
EuiInMemoryTable,
Direction,
EuiFlexGroup,
EuiFlexItem,
EuiFieldSearch,
EuiFilterSelectItem,
} from '@elastic/eui';
import { WORKFLOW_STATE, WorkflowLaunch } from '../../../../common';
import { columns } from './columns';
import { MultiSelectFilter } from '../../../general_components';
import { getStateOptions } from '../../../utils';

interface LaunchListProps {}

/**
* The searchable list of launches for this particular workflow.
*/
export function LaunchList(props: LaunchListProps) {
// TODO: finalize how we persist launches for a particular workflow.
// We may just add UI metadata tags to group workflows under a single, overall "workflow"
// const { workflows } = useSelector((state: AppState) => state.workflows);
const workflowLaunches = [
{
id: 'Launch_1',
state: WORKFLOW_STATE.IN_PROGRESS,
lastUpdated: 12345678,
},
{
id: 'Launch_2',
state: WORKFLOW_STATE.FAILED,
lastUpdated: 12345677,
},
] as WorkflowLaunch[];

// search bar state
const [searchQuery, setSearchQuery] = useState<string>('');
const debounceSearchQuery = debounce((query: string) => {
setSearchQuery(query);
}, 100);

// filters state
const [selectedStates, setSelectedStates] = useState<EuiFilterSelectItem[]>(
getStateOptions()
);
const [filteredLaunches, setFilteredLaunches] = useState<WorkflowLaunch[]>(
workflowLaunches
);

// When a filter selection or search query changes, update the filtered launches
useEffect(() => {
setFilteredLaunches(
fetchFilteredLaunches(workflowLaunches, selectedStates, searchQuery)
);
}, [selectedStates, searchQuery]);

const sorting = {
sort: {
field: 'id',
direction: 'asc' as Direction,
},
};

return (
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="m">
<EuiFlexItem grow={true}>
<EuiFieldSearch
fullWidth={true}
placeholder="Search launches..."
onChange={(e) => debounceSearchQuery(e.target.value)}
/>
</EuiFlexItem>
<MultiSelectFilter
filters={getStateOptions()}
title="Status"
setSelectedFilters={setSelectedStates}
/>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiInMemoryTable<WorkflowLaunch>
items={filteredLaunches}
rowHeader="id"
columns={columns}
sorting={sorting}
pagination={true}
message={'No existing launches found'}
/>
</EuiFlexItem>
</EuiFlexGroup>
);
}

// Collect the final launch list after applying all filters
function fetchFilteredLaunches(
allLaunches: WorkflowLaunch[],
stateFilters: EuiFilterSelectItem[],
searchQuery: string
): WorkflowLaunch[] {
// @ts-ignore
const stateFilterStrings = stateFilters.map((filter) => filter.name);
const filteredLaunches = allLaunches.filter((launch) =>
stateFilterStrings.includes(launch.state)
);
return searchQuery.length === 0
? filteredLaunches
: filteredLaunches.filter((launch) =>
launch.id.toLowerCase().includes(searchQuery.toLowerCase())
);
}
42 changes: 42 additions & 0 deletions public/pages/workflow_detail/launches/launches.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import {
EuiFlexGroup,
EuiFlexItem,
EuiPageContent,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { Workflow } from '../../../../common';
import { LaunchList } from './launch_list';
import { LaunchDetails } from './launch_details';

interface LaunchesProps {
workflow?: Workflow;
}

/**
* The launches page to browse launch history and view individual launch details.
*/
export function Launches(props: LaunchesProps) {
return (
<EuiPageContent>
<EuiTitle>
<h2>Launches</h2>
</EuiTitle>
<EuiSpacer size="m" />
<EuiFlexGroup direction="row">
<EuiFlexItem>
<LaunchList />
</EuiFlexItem>
<EuiFlexItem>
<LaunchDetails />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPageContent>
);
}
6 changes: 6 additions & 0 deletions public/pages/workflow_detail/prototype/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { Prototype } from './prototype';
Loading

0 comments on commit edcd453

Please sign in to comment.