Skip to content

Commit

Permalink
Add editor warning; add workflow fetching on cold reload
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 412dc71 commit 9379dbf
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 22 deletions.
2 changes: 2 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/del
* MISCELLANEOUS
*/
export const NEW_WORKFLOW_ID_URL = 'new';
export const START_FROM_SCRATCH_WORKFLOW_NAME = 'Start From Scratch';
export const DEFAULT_NEW_WORKFLOW_NAME = 'new_workflow';
20 changes: 15 additions & 5 deletions public/pages/workflow_detail/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

import React, { useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { EuiPageHeader, EuiButton } from '@elastic/eui';
import { Workflow } from '../../../../common';
import { EuiPageHeader, EuiButton, EuiLoadingSpinner } from '@elastic/eui';
import { DEFAULT_NEW_WORKFLOW_NAME, Workflow } from '../../../../common';
import { saveWorkflow } from '../utils';
import { rfContext, AppState, removeDirty } from '../../../store';

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

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

return (
<EuiPageHeader
pageTitle={props.formattedWorkflowName}
pageTitle={
props.workflow ? (
props.workflow.name
) : props.isNewWorkflow && !props.workflow ? (
DEFAULT_NEW_WORKFLOW_NAME
) : (
<EuiLoadingSpinner size="xl" />
)
}
rightSideItems={[
// TODO: add launch logic
<EuiButton fill={false} onClick={() => {}}>
Prototype
Launch
</EuiButton>,
<EuiButton
fill={false}
disabled={!props.workflow || !isDirty}
// TODO: if isNewWorkflow is true, clear the workflow cache if saving is successful.
onClick={() => {
// @ts-ignore
saveWorkflow(props.workflow, reactFlowInstance);
Expand Down
37 changes: 27 additions & 10 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ import { EuiPage, EuiPageBody } from '@elastic/eui';
import { BREADCRUMBS } from '../../utils';
import { getCore } from '../../services';
import { WorkflowDetailHeader } from './components';
import { AppState } from '../../store';
import { AppState, searchWorkflows } from '../../store';
import { ResizableWorkspace } from './workspace';
import { Launches } from './launches';
import { Prototype } from './prototype';
import { NEW_WORKFLOW_ID_URL } from '../../../common';
import {
DEFAULT_NEW_WORKFLOW_NAME,
NEW_WORKFLOW_ID_URL,
} from '../../../common';

export interface WorkflowDetailRouterProps {
workflowId: string;
Expand Down Expand Up @@ -49,20 +52,21 @@ function replaceActiveTab(activeTab: string, props: WorkflowDetailProps) {
* 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 dispatch = useDispatch();
const { workflows, cachedWorkflow } = useSelector(
(state: AppState) => state.workflows
);
const { isDirty } = useSelector((state: AppState) => state.workspace);

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

// tab state
Expand Down Expand Up @@ -92,6 +96,19 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
]);
});

// On initial load:
// - fetch workflow, if there is an existing workflow ID
// - add a window listener to warn users if they exit/refresh
// without saving latest changes
useEffect(() => {
if (!isNewWorkflow) {
// TODO: can optimize to only fetch a single workflow
dispatch(searchWorkflows({ query: { match_all: {} } }));
}
window.onbeforeunload = (e) =>
isDirty || isNewWorkflow ? true : undefined;
}, []);

const tabs = [
{
id: WORKFLOW_DETAILS_TAB.EDITOR,
Expand Down Expand Up @@ -128,7 +145,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
<EuiPageBody>
<WorkflowDetailHeader
workflow={workflow}
formattedWorkflowName={workflowName}
isNewWorkflow={isNewWorkflow}
tabs={tabs}
/>
{selectedTabId === WORKFLOW_DETAILS_TAB.EDITOR && (
Expand Down
20 changes: 15 additions & 5 deletions public/pages/workflows/new_workflow/new_workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
import { useDispatch } from 'react-redux';
import { UseCase } from './use_case';
import { getPresetWorkflows } from './presets';
import { Workflow } from '../../../../common';
import {
DEFAULT_NEW_WORKFLOW_NAME,
START_FROM_SCRATCH_WORKFLOW_NAME,
Workflow,
} from '../../../../common';
import { cacheWorkflow } from '../../../store';

interface NewWorkflowProps {}
Expand Down Expand Up @@ -66,7 +70,7 @@ export function NewWorkflow(props: NewWorkflowProps) {
dispatch(
cacheWorkflow({
...workflow,
name: toSnakeCase(workflow.name),
name: processWorkflowName(workflow.name),
})
)
}
Expand All @@ -92,9 +96,15 @@ function fetchFilteredWorkflows(
);
}

// 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.
// Utility fn to process workflow names from their presentable/readable titles
// on the UI, to a valid name format.
// This leads to less friction if users decide to save the name later on.
function processWorkflowName(workflowName: string): string {
return workflowName === START_FROM_SCRATCH_WORKFLOW_NAME
? DEFAULT_NEW_WORKFLOW_NAME
: toSnakeCase(workflowName);
}

function toSnakeCase(text: string): string {
return text
.replace(/\W+/g, ' ')
Expand Down
8 changes: 6 additions & 2 deletions public/pages/workflows/new_workflow/presets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Workflow, WorkspaceFlowState } from '../../../../common';
import {
START_FROM_SCRATCH_WORKFLOW_NAME,
Workflow,
WorkspaceFlowState,
} from '../../../../common';

// TODO: fetch from the backend when the workflow library is complete.
/**
Expand Down Expand Up @@ -34,7 +38,7 @@ export function getPresetWorkflows(): Workflow[] {
} as WorkspaceFlowState,
},
{
name: 'Start From Scratch',
name: START_FROM_SCRATCH_WORKFLOW_NAME,
description:
'Build your workflow from scratch according to your specific use cases. Start by adding components for your ingest or query needs.',
useCase: '',
Expand Down

0 comments on commit 9379dbf

Please sign in to comment.