Skip to content

Commit

Permalink
Add a component details side panel (opensearch-project#57)
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Oct 9, 2023
1 parent f30ac98 commit 1196260
Show file tree
Hide file tree
Showing 14 changed files with 234 additions and 44 deletions.
7 changes: 5 additions & 2 deletions public/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Props extends RouteComponentProps {}

export const AiFlowDashboardsApp = (props: Props) => {
const sidebar = (
<EuiPageSideBar style={{ minWidth: 190 }} hidden={false}>
<EuiPageSideBar style={{ minWidth: 190 }} hidden={false} paddingSize="l">
<EuiSideNav
style={{ width: 190 }}
items={[
Expand Down Expand Up @@ -50,7 +50,10 @@ export const AiFlowDashboardsApp = (props: Props) => {
return (
<EuiPageTemplate
template="empty"
pageContentProps={{ paddingSize: 'm' }}
paddingSize="none"
grow={true}
restrictWidth={false}
pageContentProps={{ paddingSize: 's' }}
pageSideBar={sidebar}
>
<Switch>
Expand Down
8 changes: 8 additions & 0 deletions public/pages/overview/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import {
import { BREADCRUMBS } from '../../utils';
import { getCore } from '../../services';

/**
* The overview page. This contains a detailed description on what
* this plugin offers, and links to different resources (blogs, demos,
* documentation, etc.)
*
* This may be hidden for the initial release until we have sufficient content
* such that this page adds enough utility & user value.
*/
export function Overview() {
useEffect(() => {
getCore().chrome.setBreadcrumbs([
Expand Down
9 changes: 7 additions & 2 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { EuiPage, EuiPageBody } from '@elastic/eui';
import { BREADCRUMBS } from '../../utils';
import { getCore } from '../../services';
import { WorkflowDetailHeader } from './components';
import { Workspace } from './workspace';
import { AppState } from '../../store';
import { ResizableWorkspace } from './workspace';

export interface WorkflowDetailRouterProps {
workflowId: string;
Expand All @@ -20,6 +20,11 @@ export interface WorkflowDetailRouterProps {
interface WorkflowDetailProps
extends RouteComponentProps<WorkflowDetailRouterProps> {}

/**
* The workflow details page. This is where users will configure, create, and
* test their created workflows. Additionally, can be used to load existing workflows
* to view details and/or make changes to them.
*/
export function WorkflowDetail(props: WorkflowDetailProps) {
const { workflows } = useSelector((state: AppState) => state.workflows);

Expand All @@ -40,7 +45,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
<EuiPage>
<EuiPageBody>
<WorkflowDetailHeader workflow={workflow} />
<Workspace workflow={workflow} />
<ResizableWorkspace workflow={workflow} />
</EuiPageBody>
</EuiPage>
);
Expand Down
99 changes: 99 additions & 0 deletions public/pages/workflow_detail/workspace/component_details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useContext } from 'react';
import { useOnSelectionChange } from 'reactflow';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
EuiPanel,
EuiTitle,
EuiEmptyPrompt,
EuiText,
} from '@elastic/eui';
import { ReactFlowComponent } from '../../../../common';
import { rfContext } from '../../../store';
import { InputFieldList } from '../workspace_component/input_field_list';

// styling
import './workspace-styles.scss';

interface ComponentDetailsProps {
onToggleChange: () => void;
isOpen: boolean;
}

/**
* A panel that will be nested in a resizable container to dynamically show
* the details and user-required inputs based on the selected component
* in the flow workspace.
*/
export function ComponentDetails(props: ComponentDetailsProps) {
// TODO: use this instance to update the internal node state. ex: update field data in the selected node based
// on user input
const { reactFlowInstance } = useContext(rfContext);

const [selectedComponent, setSelectedComponent] = useState<
ReactFlowComponent
>();

/**
* Hook provided by reactflow to listen on when nodes are selected / de-selected.
* - populate panel content appropriately
* - open the panel if a node is selected and the panel is closed
* - it is assumed that only one node can be selected at once
*/
useOnSelectionChange({
onChange: ({ nodes, edges }) => {
if (nodes && nodes.length > 0) {
setSelectedComponent(nodes[0]);
if (!props.isOpen) {
props.onToggleChange();
}
} else {
setSelectedComponent(undefined);
}
},
});

return (
<EuiFlexGroup
direction="column"
gutterSize="none"
className="workspace-panel"
>
<EuiFlexItem className="resizable-panel-border">
<EuiPanel paddingSize="m">
{selectedComponent ? (
<>
<EuiTitle size="m">
<h2>{selectedComponent?.data.label || ''}</h2>
</EuiTitle>
<EuiSpacer size="s" />
<InputFieldList
inputFields={selectedComponent?.data.fields || []}
/>
</>
) : (
<EuiEmptyPrompt
iconType={'cross'}
title={<h2>No component selected</h2>}
titleSize="s"
body={
<>
<EuiText>
Add a component, or select a component to view or edit its
configuration.
</EuiText>
</>
}
/>
)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
}
2 changes: 1 addition & 1 deletion public/pages/workflow_detail/workspace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
* SPDX-License-Identifier: Apache-2.0
*/

export { Workspace } from './workspace';
export { ResizableWorkspace } from './resizable_workspace';
16 changes: 7 additions & 9 deletions public/pages/workflow_detail/workspace/reactflow-styles.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
$handle-color: #5a5a5a;
$handle-color-valid: #55dd99;
$handle-color-invalid: #ff6060;
$handle-color: $euiColorDarkestShade;
$handle-color-valid: $euiColorSuccess;
$handle-color-invalid: $euiColorDanger;

.reactflow-workspace {
background: $euiColorEmptyShade;
}

.reactflow-parent-wrapper {
display: flex;
Expand All @@ -13,12 +17,6 @@ $handle-color-invalid: #ff6060;
height: 100%;
}

.workspace {
width: 80vh;
height: 50vh;
padding: 0;
}

.reactflow-workspace .react-flow__node {
width: 300px;
}
Expand Down
68 changes: 68 additions & 0 deletions public/pages/workflow_detail/workspace/resizable_workspace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useRef, useState } from 'react';
import { ReactFlowProvider } from 'reactflow';
import { EuiResizableContainer } from '@elastic/eui';
import { Workflow } from '../../../../common';
import { Workspace } from './workspace';
import { ComponentDetails } from './component_details';

interface ResizableWorkspaceProps {
workflow?: Workflow;
}

const COMPONENT_DETAILS_PANEL_ID = 'component_details_panel_id';

/**
* The overall workspace component that maintains state related to the 2 resizable
* panels - the ReactFlow workspace panel and the selected component details panel.
*/
export function ResizableWorkspace(props: ResizableWorkspaceProps) {
const [isOpen, setIsOpen] = useState<boolean>(true);
const collapseFn = useRef(
(id: string, options: { direction: 'left' | 'right' }) => {}
);

const onToggleChange = () => {
collapseFn.current(COMPONENT_DETAILS_PANEL_ID, { direction: 'left' });
setIsOpen(!isOpen);
};

return (
<EuiResizableContainer
direction="horizontal"
style={{ marginLeft: '-14px' }}
>
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
if (togglePanel) {
collapseFn.current = (panelId: string, { direction }) =>
togglePanel(panelId, { direction });
}

return (
<ReactFlowProvider>
<EuiResizablePanel mode="main" initialSize={75} minSize="50%">
<Workspace workflow={props.workflow} />
</EuiResizablePanel>
<EuiResizableButton />
<EuiResizablePanel
id={COMPONENT_DETAILS_PANEL_ID}
mode="collapsible"
initialSize={25}
minSize="10%"
onToggleCollapsedInternal={() => onToggleChange()}
>
<ComponentDetails
onToggleChange={onToggleChange}
isOpen={isOpen}
/>
</EuiResizablePanel>
</ReactFlowProvider>
);
}}
</EuiResizableContainer>
);
}
12 changes: 12 additions & 0 deletions public/pages/workflow_detail/workspace/workspace-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.workspace-panel {
// this ratio will allow the workspace to render on a standard
// laptop (3024 x 1964) without introducing overflow/scrolling
height: 60vh;
padding: 0;
}

.resizable-panel-border {
border-style: groove;
border-color: gray;
border-width: 1px;
}
19 changes: 9 additions & 10 deletions public/pages/workflow_detail/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ReactFlow, {
useNodesState,
useEdgesState,
addEdge,
BackgroundVariant,
} from 'reactflow';
import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
import { rfContext, setDirty } from '../../../store';
Expand All @@ -23,6 +24,7 @@ import { DeletableEdge } from '../workspace_edge';
// styling
import 'reactflow/dist/style.css';
import './reactflow-styles.scss';
import './workspace-styles.scss';
import '../workspace_edge/deletable-edge-styles.scss';

interface WorkspaceProps {
Expand Down Expand Up @@ -117,17 +119,11 @@ export function Workspace(props: WorkspaceProps) {
return (
<EuiFlexGroup
direction="column"
gutterSize="m"
gutterSize="none"
justifyContent="spaceBetween"
className="workspace"
className="workspace-panel"
>
<EuiFlexItem
style={{
borderStyle: 'groove',
borderColor: 'gray',
borderWidth: '1px',
}}
>
<EuiFlexItem className="resizable-panel-border">
{/**
* We have these wrapper divs & reactFlowWrapper ref to control and calculate the
* ReactFlow bounds when calculating node positioning.
Expand All @@ -149,7 +145,10 @@ export function Workspace(props: WorkspaceProps) {
fitView
>
<Controls />
<Background />
<Background
color="#343741"
variant={'dots' as BackgroundVariant}
/>
</ReactFlow>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface InputFieldListProps {

export function InputFieldList(props: InputFieldListProps) {
return (
<EuiFlexItem>
<EuiFlexItem grow={false}>
{props.inputFields?.map((field, idx) => {
let el;
switch (field.type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState } from 'react';
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiCard } from '@elastic/eui';
import { IComponent } from '../../../component_types';
import { InputFieldList } from './input_field_list';
import { NewOrExistingTabs } from './new_or_existing_tabs';
import { InputHandle } from './input_handle';
import { OutputHandle } from './output_handle';

Expand All @@ -23,32 +21,17 @@ interface WorkspaceComponentProps {
export function WorkspaceComponent(props: WorkspaceComponentProps) {
const component = props.data;

const [selectedTabId, setSelectedTabId] = useState<string>('existing');

const isCreatingNew = component.allowsCreation && selectedTabId === 'new';
const fieldsToDisplay = isCreatingNew
? component.createFields
: component.fields;

return (
<EuiCard title={component.label}>
<EuiFlexGroup direction="column">
{/* <EuiFlexItem>
{component.allowsCreation ? (
<NewOrExistingTabs
setSelectedTabId={setSelectedTabId}
selectedTabId={selectedTabId}
/>
) : undefined}
</EuiFlexItem> */}
{component.inputs?.map((input, index) => {
return (
<EuiFlexItem key={index}>
<InputHandle input={input} data={component} />
</EuiFlexItem>
);
})}
<InputFieldList inputFields={fieldsToDisplay} />
{/* TODO: finalize from UX what we show in the component itself. Readonly fields? Configure in the component JSON definition? */}
{component.outputs?.map((output, index) => {
return (
<EuiFlexItem key={index}>
Expand Down
Loading

0 comments on commit 1196260

Please sign in to comment.