Skip to content

Commit

Permalink
Add navigation; handle new workflow state on details page
Browse files Browse the repository at this point in the history
Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler committed Mar 1, 2024
1 parent 263b0ba commit 412dc71
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 12 deletions.
5 changes: 5 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,8 @@ export const SEARCH_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/se
export const GET_WORKFLOW_STATE_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/state`;
export const CREATE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/create`;
export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/delete`;

/**
* MISCELLANEOUS
*/
export const NEW_WORKFLOW_ID_URL = 'new';
3 changes: 2 additions & 1 deletion public/pages/workflow_detail/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { rfContext, AppState, removeDirty } from '../../../store';

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

Expand All @@ -22,7 +23,7 @@ export function WorkflowDetailHeader(props: WorkflowDetailHeaderProps) {

return (
<EuiPageHeader
pageTitle={props.workflow ? props.workflow.name : ''}
pageTitle={props.formattedWorkflowName}
rightSideItems={[
<EuiButton fill={false} onClick={() => {}}>
Prototype
Expand Down
28 changes: 23 additions & 5 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import React, { useEffect, useState } from 'react';
import { RouteComponentProps, useLocation } from 'react-router-dom';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { ReactFlowProvider } from 'reactflow';
import queryString from 'query-string';
import { EuiPage, EuiPageBody } from '@elastic/eui';
Expand All @@ -16,6 +16,7 @@ import { AppState } from '../../store';
import { ResizableWorkspace } from './workspace';
import { Launches } from './launches';
import { Prototype } from './prototype';
import { NEW_WORKFLOW_ID_URL } from '../../../common';

export interface WorkflowDetailRouterProps {
workflowId: string;
Expand Down Expand Up @@ -45,13 +46,26 @@ function replaceActiveTab(activeTab: string, props: WorkflowDetailProps) {
* 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.
* New, unsaved workflows are cached in the redux store and displayed here.
*/

// TODO: if exiting the page, or if saving, clear the cached workflow. Can use redux clearCachedWorkflow()
export function WorkflowDetail(props: WorkflowDetailProps) {
const { workflows } = useSelector((state: AppState) => state.workflows);
const { workflows, cachedWorkflow } = useSelector(
(state: AppState) => state.workflows
);

const workflow = workflows[props.match?.params?.workflowId];
const workflowName = workflow ? workflow.name : '';
const isNewWorkflow = props.match?.params?.workflowId === NEW_WORKFLOW_ID_URL;
const workflow = isNewWorkflow
? cachedWorkflow
: workflows[props.match?.params?.workflowId];
const workflowName = workflow
? workflow.name
: isNewWorkflow && !cachedWorkflow
? 'new_workflow'
: '';

// tab state
const tabFromUrl = queryString.parse(useLocation().search)[
ACTIVE_TAB_PARAM
] as WORKFLOW_DETAILS_TAB;
Expand Down Expand Up @@ -112,7 +126,11 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
<ReactFlowProvider>
<EuiPage>
<EuiPageBody>
<WorkflowDetailHeader workflow={workflow} tabs={tabs} />
<WorkflowDetailHeader
workflow={workflow}
formattedWorkflowName={workflowName}
tabs={tabs}
/>
{selectedTabId === WORKFLOW_DETAILS_TAB.EDITOR && (
<ResizableWorkspace workflow={workflow} />
)}
Expand Down
27 changes: 24 additions & 3 deletions public/pages/workflows/new_workflow/new_workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import {
EuiFlexGroup,
EuiFieldSearch,
} from '@elastic/eui';

import { useDispatch } from 'react-redux';
import { UseCase } from './use_case';
import { getPresetWorkflows } from './presets';
import { Workflow } from '../../../../common';
import { cacheWorkflow } from '../../../store';

interface NewWorkflowProps {}

Expand All @@ -26,6 +27,7 @@ interface NewWorkflowProps {}
* workflow for users to start with.
*/
export function NewWorkflow(props: NewWorkflowProps) {
const dispatch = useDispatch();
// preset workflow state
const presetWorkflows = getPresetWorkflows();
const [filteredWorkflows, setFilteredWorkflows] = useState<Workflow[]>(
Expand Down Expand Up @@ -54,12 +56,20 @@ export function NewWorkflow(props: NewWorkflowProps) {
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGrid columns={3} gutterSize="l">
{filteredWorkflows.map((workflow: Workflow) => {
{filteredWorkflows.map((workflow: Workflow, index) => {
return (
<EuiFlexItem>
<EuiFlexItem key={index}>
<UseCase
title={workflow.name}
description={workflow.description || ''}
onClick={() =>
dispatch(
cacheWorkflow({
...workflow,
name: toSnakeCase(workflow.name),
})
)
}
/>
</EuiFlexItem>
);
Expand All @@ -81,3 +91,14 @@ function fetchFilteredWorkflows(
workflow.name.toLowerCase().includes(searchQuery.toLowerCase())
);
}

// Utility fn to convert to snakecase. Used when caching the workflow
// to make a valid name and cause less friction if users decide
// to save it later on.
function toSnakeCase(text: string): string {
return text
.replace(/\W+/g, ' ')
.split(/ |\B(?=[A-Z])/)
.map((word) => word.toLowerCase())
.join('_');
}
8 changes: 5 additions & 3 deletions public/pages/workflows/new_workflow/use_case.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import {
EuiHorizontalRule,
EuiButton,
} from '@elastic/eui';
import { NEW_WORKFLOW_ID_URL, PLUGIN_ID } from '../../../../common';

interface UseCaseProps {
title: string;
description: string;
onClick: () => {};
}

export function UseCase(props: UseCaseProps) {
Expand All @@ -40,9 +43,8 @@ export function UseCase(props: UseCaseProps) {
<EuiButton
disabled={false}
isLoading={false}
onClick={() => {
// TODO: possibly link to the workflow details with a pre-configured flow
}}
onClick={props.onClick}
href={`${PLUGIN_ID}#/workflows/${NEW_WORKFLOW_ID_URL}`}
>
Go
</EuiButton>
Expand Down
24 changes: 24 additions & 0 deletions public/store/reducers/workflows_reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const initialState = {
loading: false,
errorMessage: '',
workflows: {} as WorkflowDict,
cachedWorkflow: undefined as Workflow | undefined,
};

const WORKFLOWS_ACTION_PREFIX = 'workflows';
Expand All @@ -86,6 +87,8 @@ const SEARCH_WORKFLOWS_ACTION = `${WORKFLOWS_ACTION_PREFIX}/searchWorkflows`;
const GET_WORKFLOW_STATE_ACTION = `${WORKFLOWS_ACTION_PREFIX}/getWorkflowState`;
const CREATE_WORKFLOW_ACTION = `${WORKFLOWS_ACTION_PREFIX}/createWorkflow`;
const DELETE_WORKFLOW_ACTION = `${WORKFLOWS_ACTION_PREFIX}/deleteWorkflow`;
const CACHE_WORKFLOW_ACTION = `${WORKFLOWS_ACTION_PREFIX}/cacheWorkflow`;
const CLEAR_CACHED_WORKFLOW_ACTION = `${WORKFLOWS_ACTION_PREFIX}/clearCachedWorkflow`;

export const getWorkflow = createAsyncThunk(
GET_WORKFLOW_ACTION,
Expand Down Expand Up @@ -167,6 +170,20 @@ export const deleteWorkflow = createAsyncThunk(
}
);

export const cacheWorkflow = createAsyncThunk(
CACHE_WORKFLOW_ACTION,
async (workflow: Workflow) => {
return workflow;
}
);

// A no-op function to trigger a reducer case.
// Will clear any stored workflow in the cachedWorkflow state
export const clearCachedWorkflow = createAsyncThunk(
CLEAR_CACHED_WORKFLOW_ACTION,
async () => {}
);

const workflowsSlice = createSlice({
name: 'workflows',
initialState,
Expand Down Expand Up @@ -238,6 +255,13 @@ const workflowsSlice = createSlice({
state.loading = false;
state.errorMessage = '';
})
.addCase(cacheWorkflow.fulfilled, (state, action) => {
const workflow = action.payload;
state.cachedWorkflow = workflow;
})
.addCase(clearCachedWorkflow.fulfilled, (state, action) => {
state.cachedWorkflow = undefined;
})
// Rejected states: set state consistently across all actions
.addCase(getWorkflow.rejected, (state, action) => {
state.errorMessage = action.payload as string;
Expand Down

0 comments on commit 412dc71

Please sign in to comment.