From 31e3b431892f4e0fedb1dd64c4cf882bac56e638 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Fri, 17 May 2024 08:21:49 -0700 Subject: [PATCH] Refactor and simplify reactflow component data/state (#158) Signed-off-by: Tyler Ohlsen (cherry picked from commit 5fe2d10938b467e749f72173822392d0725d2ae5) --- common/interfaces.ts | 39 +- public/component_types/base_component.tsx | 12 +- .../component_types/indexer/base_indexer.ts | 32 ++ public/component_types/indexer/index.ts | 1 - public/component_types/indexer/indexer.ts | 62 --- public/component_types/indexer/knn_indexer.ts | 16 +- public/component_types/other/document.tsx | 10 +- .../other/query/match_query.tsx | 8 - .../other/query/neural_query.tsx | 8 - public/component_types/other/query/query.tsx | 13 +- public/component_types/other/results.tsx | 12 +- .../transformer/base_transformer.ts | 18 +- public/component_types/transformer/index.ts | 1 - .../transformer/ml_transformer.ts | 1 - .../transformer/normalization_transformer.ts | 4 +- .../transformer/results_transformer.ts | 15 - .../transformer/sparse_encoder_transformer.ts | 58 +-- .../transformer/text_embedding_transformer.ts | 57 +-- .../component_details/component_details.tsx | 46 --- .../component_details/component_inputs.tsx | 80 ---- .../empty_component_inputs.tsx | 26 -- .../component_details/index.ts | 6 - .../component_details/input_field_list.tsx | 87 ---- .../component_details/input_fields/index.ts | 9 - .../input_fields/json_field.tsx | 28 -- .../input_fields/model_field.tsx | 238 ----------- .../input_fields/select_field.tsx | 85 ---- .../input_fields/text_field.tsx | 69 ---- .../provisioned_component_inputs.tsx | 23 -- .../workflow_detail/resizable_workspace.tsx | 2 +- public/pages/workflow_detail/utils/index.ts | 1 - public/pages/workflow_detail/utils/utils.ts | 18 - .../pages/workflow_detail/workflow_detail.tsx | 2 +- .../pages/workflow_detail/workspace/index.ts | 2 +- .../workspace_components/input_handle.tsx | 4 +- .../new_or_existing_tabs.tsx | 48 --- .../workspace_components/output_handle.tsx | 5 +- .../workspace_component.tsx | 41 +- public/pages/workflows/new_workflow/utils.ts | 377 +++++++++--------- public/utils/utils.ts | 61 --- 40 files changed, 271 insertions(+), 1354 deletions(-) create mode 100644 public/component_types/indexer/base_indexer.ts delete mode 100644 public/component_types/indexer/indexer.ts delete mode 100644 public/pages/workflow_detail/component_details/component_details.tsx delete mode 100644 public/pages/workflow_detail/component_details/component_inputs.tsx delete mode 100644 public/pages/workflow_detail/component_details/empty_component_inputs.tsx delete mode 100644 public/pages/workflow_detail/component_details/index.ts delete mode 100644 public/pages/workflow_detail/component_details/input_field_list.tsx delete mode 100644 public/pages/workflow_detail/component_details/input_fields/index.ts delete mode 100644 public/pages/workflow_detail/component_details/input_fields/json_field.tsx delete mode 100644 public/pages/workflow_detail/component_details/input_fields/model_field.tsx delete mode 100644 public/pages/workflow_detail/component_details/input_fields/select_field.tsx delete mode 100644 public/pages/workflow_detail/component_details/input_fields/text_field.tsx delete mode 100644 public/pages/workflow_detail/component_details/provisioned_component_inputs.tsx delete mode 100644 public/pages/workflow_detail/utils/utils.ts delete mode 100644 public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx diff --git a/common/interfaces.ts b/common/interfaces.ts index c369fb8a..494639be 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -6,12 +6,7 @@ import { Node, Edge } from 'reactflow'; import { FormikValues } from 'formik'; import { ObjectSchema } from 'yup'; -import { - COMPONENT_CLASS, - COMPONENT_CATEGORY, - PROCESSOR_TYPE, - MODEL_TYPE, -} from './constants'; +import { COMPONENT_CLASS, PROCESSOR_TYPE, MODEL_TYPE } from './constants'; export type Index = { name: string; @@ -106,17 +101,17 @@ export type WorkspaceSchemaObj = { }; export type WorkspaceSchema = ObjectSchema; -/** - * Represents a single base class as an input handle for a component. - * It may accept multiples of that class. - */ export interface IComponentInput { id: string; label: string; - baseClass: COMPONENT_CLASS; acceptMultiple: boolean; } +export interface IComponentOutput { + id: string; + label: string; +} + /** * An input field for a component. Specifies enough configuration for the * UI node to render it properly (help text, links, etc.) @@ -132,15 +127,6 @@ export interface IComponentField { selectType?: SelectType; } -/** - * Represents the list of base classes as a single output handle for - * a component. - */ -export interface IComponentOutput { - label: string; - baseClasses: COMPONENT_CLASS[]; -} - /** * The base interface the components will implement. */ @@ -148,20 +134,7 @@ export interface IComponent { type: COMPONENT_CLASS; label: string; description: string; - // will be used for grouping together in the drag-and-drop component library - // and determining which flows the component can be drug into the workspace flows - categories: COMPONENT_CATEGORY[]; - // determines if this component allows for new creation. this means to - // allow a "create" option on the UI component, as well as potentially - // include in the use case template construction ('provisioning' flow) - allowsCreation: boolean; - // the list of base classes that will be used in the component output - baseClasses?: COMPONENT_CLASS[]; inputs?: IComponentInput[]; - fields?: IComponentField[]; - // if the component supports creation, we will have a different set of input fields - // the user needs to fill out - createFields?: IComponentField[]; outputs?: IComponentOutput[]; } diff --git a/public/component_types/base_component.tsx b/public/component_types/base_component.tsx index df75e682..10c0583f 100644 --- a/public/component_types/base_component.tsx +++ b/public/component_types/base_component.tsx @@ -4,10 +4,8 @@ */ import { - COMPONENT_CATEGORY, COMPONENT_CLASS, IComponent, - IComponentField, IComponentInput, IComponentOutput, } from '../../common'; @@ -19,16 +17,14 @@ export abstract class BaseComponent implements IComponent { type: COMPONENT_CLASS; label: string; description: string; - categories: COMPONENT_CATEGORY[]; - allowsCreation: boolean; - baseClasses: COMPONENT_CLASS[]; inputs?: IComponentInput[]; - fields?: IComponentField[]; - createFields?: IComponentField[]; outputs?: IComponentOutput[]; // No-op constructor. If there are general / defaults for field values, add in here. - constructor() {} + constructor() { + this.inputs = []; + this.outputs = []; + } // Persist a standard toObj() fn that all component classes can use. This is necessary // so we have standard JS Object when serializing comoponent state in redux. diff --git a/public/component_types/indexer/base_indexer.ts b/public/component_types/indexer/base_indexer.ts new file mode 100644 index 00000000..f526121b --- /dev/null +++ b/public/component_types/indexer/base_indexer.ts @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { COMPONENT_CLASS } from '../../../common'; +import { BaseComponent } from '../base_component'; + +/** + * A base indexer UI component + */ +export abstract class BaseIndexer extends BaseComponent { + constructor() { + super(); + this.type = COMPONENT_CLASS.INDEXER; + this.label = 'Indexer'; + this.description = 'A general indexer'; + this.inputs = [ + { + id: 'input', + label: 'Input', + acceptMultiple: false, + }, + ]; + this.outputs = [ + { + id: 'output', + label: 'Output', + }, + ]; + } +} diff --git a/public/component_types/indexer/index.ts b/public/component_types/indexer/index.ts index 815396af..264add90 100644 --- a/public/component_types/indexer/index.ts +++ b/public/component_types/indexer/index.ts @@ -3,5 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './indexer'; export * from './knn_indexer'; diff --git a/public/component_types/indexer/indexer.ts b/public/component_types/indexer/indexer.ts deleted file mode 100644 index f23ccc5f..00000000 --- a/public/component_types/indexer/indexer.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; -import { BaseComponent } from '../base_component'; - -/** - * A base indexer UI component - */ -export class Indexer extends BaseComponent { - constructor() { - super(); - this.type = COMPONENT_CLASS.INDEXER; - this.label = 'Indexer'; - this.description = 'A general indexer'; - this.categories = [COMPONENT_CATEGORY.INGEST, COMPONENT_CATEGORY.SEARCH]; - this.allowsCreation = true; - this.baseClasses = [this.type]; - this.inputs = [ - { - id: 'document', - label: 'Document', - baseClass: COMPONENT_CLASS.DOCUMENT, - acceptMultiple: false, - }, - { - id: 'query', - label: 'Query', - baseClass: COMPONENT_CLASS.QUERY, - acceptMultiple: true, - }, - ]; - this.fields = [ - { - label: 'Index Name', - id: 'indexName', - type: 'select', - }, - ]; - this.createFields = [ - { - label: 'Index Name', - id: 'indexName', - type: 'string', - }, - // { - // label: 'Mappings', - // id: 'indexMappings', - // type: 'json', - // placeholder: 'Enter an index mappings JSON blob...', - // }, - ]; - this.outputs = [ - { - label: 'Results', - baseClasses: [COMPONENT_CLASS.RESULTS], - }, - ]; - } -} diff --git a/public/component_types/indexer/knn_indexer.ts b/public/component_types/indexer/knn_indexer.ts index fccef524..498243dd 100644 --- a/public/component_types/indexer/knn_indexer.ts +++ b/public/component_types/indexer/knn_indexer.ts @@ -4,28 +4,16 @@ */ import { COMPONENT_CLASS } from '../../../common'; -import { Indexer } from './indexer'; +import { BaseIndexer } from './base_indexer'; /** * A specialized indexer component for vector/K-NN indices */ -export class KnnIndexer extends Indexer { +export class KnnIndexer extends BaseIndexer { constructor() { super(); this.type = COMPONENT_CLASS.KNN_INDEXER; this.label = 'K-NN Index'; this.description = 'A specialized indexer for K-NN indices'; - this.baseClasses = [...this.baseClasses, this.type]; - this.createFields = [ - // @ts-ignore - ...this.createFields, - // TODO: finalize what to expose / what to have for defaults here - // { - // label: 'K-NN Settings', - // id: 'knnSettings', - // type: 'json', - // placeholder: 'Enter K-NN settings JSON blob...', - // }, - ]; } } diff --git a/public/component_types/other/document.tsx b/public/component_types/other/document.tsx index e1cda879..72af7689 100644 --- a/public/component_types/other/document.tsx +++ b/public/component_types/other/document.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; +import { COMPONENT_CLASS } from '../../../common'; import { BaseComponent } from '../base_component'; /** @@ -16,14 +16,10 @@ export class Document extends BaseComponent { this.type = COMPONENT_CLASS.DOCUMENT; this.label = 'Document'; this.description = 'A document to be ingested'; - this.categories = [COMPONENT_CATEGORY.INGEST]; - this.allowsCreation = false; - this.baseClasses = [this.type]; - this.inputs = []; this.outputs = [ { - label: this.label, - baseClasses: this.baseClasses, + id: 'output', + label: 'Output', }, ]; } diff --git a/public/component_types/other/query/match_query.tsx b/public/component_types/other/query/match_query.tsx index 1cb60984..06cfe823 100644 --- a/public/component_types/other/query/match_query.tsx +++ b/public/component_types/other/query/match_query.tsx @@ -16,13 +16,5 @@ export class MatchQuery extends Query { this.type = COMPONENT_CLASS.MATCH_QUERY; this.label = 'Match Query'; this.description = 'An OpenSearch match query'; - this.inputs = []; - this.baseClasses = [...this.baseClasses, this.type]; - this.outputs = [ - { - label: this.label, - baseClasses: this.baseClasses, - }, - ]; } } diff --git a/public/component_types/other/query/neural_query.tsx b/public/component_types/other/query/neural_query.tsx index 51fabb28..49136209 100644 --- a/public/component_types/other/query/neural_query.tsx +++ b/public/component_types/other/query/neural_query.tsx @@ -16,13 +16,5 @@ export class NeuralQuery extends Query { this.type = COMPONENT_CLASS.NEURAL_QUERY; this.label = 'Neural query'; this.description = 'An OpenSearch neural query'; - this.inputs = []; - this.baseClasses = [...this.baseClasses, this.type]; - this.outputs = [ - { - label: this.label, - baseClasses: this.baseClasses, - }, - ]; } } diff --git a/public/component_types/other/query/query.tsx b/public/component_types/other/query/query.tsx index f347218e..c6e35890 100644 --- a/public/component_types/other/query/query.tsx +++ b/public/component_types/other/query/query.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../../common'; +import { COMPONENT_CLASS } from '../../../../common'; import { BaseComponent } from '../../base_component'; /** @@ -16,10 +16,11 @@ export abstract class Query extends BaseComponent { this.type = COMPONENT_CLASS.QUERY; this.label = 'Query'; this.description = 'An OpenSearch query'; - this.categories = [COMPONENT_CATEGORY.SEARCH]; - this.allowsCreation = false; - this.baseClasses = [this.type]; - this.inputs = []; - this.outputs = []; + this.outputs = [ + { + id: 'output', + label: 'Output', + }, + ]; } } diff --git a/public/component_types/other/results.tsx b/public/component_types/other/results.tsx index 9c6ad3fb..e2505682 100644 --- a/public/component_types/other/results.tsx +++ b/public/component_types/other/results.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; +import { COMPONENT_CLASS } from '../../../common'; import { BaseComponent } from '../base_component'; /** @@ -16,15 +16,5 @@ export class Results extends BaseComponent { this.type = COMPONENT_CLASS.RESULTS; this.label = 'Results'; this.description = 'OpenSearch results'; - this.categories = [COMPONENT_CATEGORY.SEARCH]; - this.allowsCreation = false; - this.baseClasses = [this.type]; - this.inputs = []; - this.outputs = [ - { - label: this.label, - baseClasses: this.baseClasses, - }, - ]; } } diff --git a/public/component_types/transformer/base_transformer.ts b/public/component_types/transformer/base_transformer.ts index fe372ad1..a1c924bd 100644 --- a/public/component_types/transformer/base_transformer.ts +++ b/public/component_types/transformer/base_transformer.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; +import { COMPONENT_CLASS } from '../../../common'; import { BaseComponent } from '../base_component'; /** @@ -14,8 +14,18 @@ export abstract class BaseTransformer extends BaseComponent { super(); this.type = COMPONENT_CLASS.TRANSFORMER; this.label = 'Transformer'; - this.categories = [COMPONENT_CATEGORY.INGEST, COMPONENT_CATEGORY.SEARCH]; - this.allowsCreation = false; - this.baseClasses = [this.type]; + this.inputs = [ + { + id: 'input', + label: 'Input', + acceptMultiple: false, + }, + ]; + this.outputs = [ + { + id: 'output', + label: 'Output', + }, + ]; } } diff --git a/public/component_types/transformer/index.ts b/public/component_types/transformer/index.ts index ce3afc3e..df8cf864 100644 --- a/public/component_types/transformer/index.ts +++ b/public/component_types/transformer/index.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './ml_transformer'; export * from './text_embedding_transformer'; export * from './sparse_encoder_transformer'; export * from './results_transformer'; diff --git a/public/component_types/transformer/ml_transformer.ts b/public/component_types/transformer/ml_transformer.ts index 0cb29d7c..68803425 100644 --- a/public/component_types/transformer/ml_transformer.ts +++ b/public/component_types/transformer/ml_transformer.ts @@ -15,6 +15,5 @@ export class MLTransformer extends BaseTransformer { this.type = COMPONENT_CLASS.ML_TRANSFORMER; this.label = 'ML Transformer'; this.description = 'A general ML transformer'; - this.baseClasses = [...this.baseClasses, this.type]; } } diff --git a/public/component_types/transformer/normalization_transformer.ts b/public/component_types/transformer/normalization_transformer.ts index 01026f64..e4195386 100644 --- a/public/component_types/transformer/normalization_transformer.ts +++ b/public/component_types/transformer/normalization_transformer.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; +import { COMPONENT_CLASS } from '../../../common'; import { ResultsTransformer } from './results_transformer'; /** @@ -15,7 +15,5 @@ export class NormalizationTransformer extends ResultsTransformer { (this.type = COMPONENT_CLASS.NORMALIZATION_TRANSFORMER), (this.label = 'Normalization Transformer'); this.description = 'A transformer to normalize search results'; - this.baseClasses = [...this.baseClasses, this.type]; - this.categories = [COMPONENT_CATEGORY.SEARCH]; } } diff --git a/public/component_types/transformer/results_transformer.ts b/public/component_types/transformer/results_transformer.ts index 7d92ef00..4aae7322 100644 --- a/public/component_types/transformer/results_transformer.ts +++ b/public/component_types/transformer/results_transformer.ts @@ -15,20 +15,5 @@ export class ResultsTransformer extends BaseTransformer { (this.type = COMPONENT_CLASS.RESULTS_TRANSFORMER), (this.label = 'Results Transformer'); this.description = 'A general results transformer'; - this.baseClasses = [...this.baseClasses, this.type]; - this.inputs = [ - { - id: 'results', - label: 'Results', - baseClass: COMPONENT_CLASS.RESULTS, - acceptMultiple: false, - }, - ]; - this.outputs = [ - { - label: 'Transformed Results', - baseClasses: [COMPONENT_CLASS.RESULTS], - }, - ]; } } diff --git a/public/component_types/transformer/sparse_encoder_transformer.ts b/public/component_types/transformer/sparse_encoder_transformer.ts index 6a631935..53da4129 100644 --- a/public/component_types/transformer/sparse_encoder_transformer.ts +++ b/public/component_types/transformer/sparse_encoder_transformer.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; -import { MLTransformer } from '.'; +import { COMPONENT_CLASS } from '../../../common'; +import { MLTransformer } from './ml_transformer'; /** * A specialized sparse encoder ML transformer UI component @@ -16,59 +16,5 @@ export class SparseEncoderTransformer extends MLTransformer { this.label = 'Sparse Encoder'; this.description = 'A specialized ML transformer to perform sparse encoding'; - this.categories = [COMPONENT_CATEGORY.INGEST]; - this.baseClasses = [...this.baseClasses, this.type]; - this.inputs = [ - { - id: 'document', - label: 'Document', - baseClass: COMPONENT_CLASS.DOCUMENT, - acceptMultiple: false, - }, - { - id: 'query', - label: 'Query', - baseClass: COMPONENT_CLASS.QUERY, - acceptMultiple: false, - }, - ]; - this.createFields = [ - { - label: 'Sparse Encoding Model', - id: 'model', - type: 'model', - helpText: - 'A sparse encoding model to be used for generating sparse vectors.', - helpLink: - 'https://opensearch.org/docs/latest/ml-commons-plugin/integrating-ml-models/#choosing-a-model', - }, - { - label: 'Input Field', - id: 'inputField', - type: 'string', - helpText: - 'The name of the document field from which to obtain text for generating sparse embeddings.', - helpLink: - 'https://opensearch.org/docs/latest/ingest-pipelines/processors/sparse-encoding/#configuration-parameters', - }, - { - label: 'Vector Field', - id: 'vectorField', - type: 'string', - helpText: `The name of the document's vector field in which to store the generated sparse embeddings.`, - helpLink: - 'https://opensearch.org/docs/latest/ingest-pipelines/processors/sparse-encoding/#configuration-parameters', - }, - ]; - this.outputs = [ - { - label: 'Transformed Document', - baseClasses: [COMPONENT_CLASS.DOCUMENT], - }, - { - label: 'Transformed Query', - baseClasses: [COMPONENT_CLASS.QUERY], - }, - ]; } } diff --git a/public/component_types/transformer/text_embedding_transformer.ts b/public/component_types/transformer/text_embedding_transformer.ts index e20e9cf7..fa50053f 100644 --- a/public/component_types/transformer/text_embedding_transformer.ts +++ b/public/component_types/transformer/text_embedding_transformer.ts @@ -3,8 +3,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { COMPONENT_CATEGORY, COMPONENT_CLASS } from '../../../common'; -import { MLTransformer } from '.'; +import { COMPONENT_CLASS } from '../../../common'; +import { MLTransformer } from './ml_transformer'; /** * A specialized text embedding ML transformer UI component @@ -15,58 +15,5 @@ export class TextEmbeddingTransformer extends MLTransformer { this.type = COMPONENT_CLASS.TEXT_EMBEDDING_TRANSFORMER; this.label = 'Text Embedder'; this.description = 'A specialized ML transformer for embedding text'; - this.categories = [COMPONENT_CATEGORY.INGEST]; - this.baseClasses = [...this.baseClasses, this.type]; - this.inputs = [ - { - id: 'document', - label: 'Document', - baseClass: COMPONENT_CLASS.DOCUMENT, - acceptMultiple: false, - }, - { - id: 'query', - label: 'Query', - baseClass: COMPONENT_CLASS.QUERY, - acceptMultiple: false, - }, - ]; - this.createFields = [ - { - label: 'Text Embedding Model', - id: 'model', - type: 'model', - helpText: 'A text embedding model for embedding text.', - helpLink: - 'https://opensearch.org/docs/latest/ml-commons-plugin/integrating-ml-models/#choosing-a-model', - }, - { - label: 'Input Field', - id: 'inputField', - type: 'string', - helpText: - 'The name of the document field from which to obtain text for generating text embeddings.', - helpLink: - 'https://opensearch.org/docs/latest/ingest-pipelines/processors/text-embedding/', - }, - { - label: 'Vector Field', - id: 'vectorField', - type: 'string', - helpText: `The name of the document's vector field in which to store the generated text embeddings.`, - helpLink: - 'https://opensearch.org/docs/latest/ingest-pipelines/processors/text-embedding/', - }, - ]; - this.outputs = [ - { - label: 'Transformed Document', - baseClasses: [COMPONENT_CLASS.DOCUMENT], - }, - { - label: 'Transformed Query', - baseClasses: [COMPONENT_CLASS.QUERY], - }, - ]; } } diff --git a/public/pages/workflow_detail/component_details/component_details.tsx b/public/pages/workflow_detail/component_details/component_details.tsx deleted file mode 100644 index 1de35468..00000000 --- a/public/pages/workflow_detail/component_details/component_details.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiPanel } from '@elastic/eui'; -import { ReactFlowComponent, Workflow } from '../../../../common'; -import { ComponentInputs } from './component_inputs'; -import { EmptyComponentInputs } from './empty_component_inputs'; -import { ProvisionedComponentInputs } from './provisioned_component_inputs'; - -// styling -import '../workspace/workspace-styles.scss'; - -interface ComponentDetailsProps { - workflow: Workflow | undefined; - onFormChange: () => void; - isDeprovisionable: boolean; - selectedComponent?: ReactFlowComponent; -} - -/** - * 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) { - return ( - - {/* TODO: determine if we need this view if we want the workspace to remain - readonly once provisioned */} - {/* {props.isDeprovisionable ? ( - - ) : */} - {props.selectedComponent ? ( - - ) : props.workflow ? ( - - ) : undefined} - - ); -} diff --git a/public/pages/workflow_detail/component_details/component_inputs.tsx b/public/pages/workflow_detail/component_details/component_inputs.tsx deleted file mode 100644 index 262bb547..00000000 --- a/public/pages/workflow_detail/component_details/component_inputs.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useState } from 'react'; -import { EuiHorizontalRule, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import { InputFieldList } from './input_field_list'; -import { NODE_CATEGORY, ReactFlowComponent } from '../../../../common'; -import { NewOrExistingTabs } from '../workspace/workspace_components/new_or_existing_tabs'; - -interface ComponentInputsProps { - selectedComponent: ReactFlowComponent; - onFormChange: () => void; -} - -export function ComponentInputs(props: ComponentInputsProps) { - // Tab state - enum TAB { - NEW = 'new', - EXISTING = 'existing', - } - const [selectedTabId, setSelectedTabId] = useState(TAB.NEW); - - // Have custom layouts for parent/group flows - if (props.selectedComponent.type === NODE_CATEGORY.INGEST_GROUP) { - return ( - <> - -

Ingest flow

-
- - - Configure a flow to transform your data as it is ingested into - OpenSearch. - - - ); - } else if (props.selectedComponent.type === NODE_CATEGORY.SEARCH_GROUP) { - return ( - <> - -

Search flow

-
- - - Configure a flow to transform input when searching against your - OpenSearch cluster. - - - ); - } else { - return ( - <> - -

{props.selectedComponent.data.label || ''}

-
- - {props.selectedComponent.data.description} - - {/* TODO: Add tabs back once it is finalized how much flexibility we want */} - {/* */} - - - - - ); - } -} diff --git a/public/pages/workflow_detail/component_details/empty_component_inputs.tsx b/public/pages/workflow_detail/component_details/empty_component_inputs.tsx deleted file mode 100644 index 19ce0754..00000000 --- a/public/pages/workflow_detail/component_details/empty_component_inputs.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; - -// Simple prompt to display when no components are selected. -export function EmptyComponentInputs() { - return ( - No component selected} - titleSize="s" - body={ - <> - - Add a component, or select a component to view or edit its - configuration. - - - } - /> - ); -} diff --git a/public/pages/workflow_detail/component_details/index.ts b/public/pages/workflow_detail/component_details/index.ts deleted file mode 100644 index d53dcc60..00000000 --- a/public/pages/workflow_detail/component_details/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export * from './component_details'; diff --git a/public/pages/workflow_detail/component_details/input_field_list.tsx b/public/pages/workflow_detail/component_details/input_field_list.tsx deleted file mode 100644 index 29d0f2a6..00000000 --- a/public/pages/workflow_detail/component_details/input_field_list.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { TextField, JsonField, SelectField, ModelField } from './input_fields'; -import { IComponentField } from '../../../../common'; - -/** - * A helper component to format all of the input fields for a component. Dynamically - * render based on the input type. - */ - -interface InputFieldListProps { - componentId: string; - componentFields: IComponentField[] | undefined; - onFormChange: () => void; -} - -const INPUT_FIELD_SPACER_SIZE = 'm'; - -export function InputFieldList(props: InputFieldListProps) { - const inputFields = props.componentFields || []; - return ( - - {inputFields.map((field, idx) => { - let el; - switch (field.type) { - case 'string': { - el = ( - - - - - ); - break; - } - case 'select': { - el = ( - - - - - ); - break; - } - case 'model': { - el = ( - - - - - ); - break; - } - case 'json': { - el = ( - - - - - ); - break; - } - } - return el; - })} - - ); -} diff --git a/public/pages/workflow_detail/component_details/input_fields/index.ts b/public/pages/workflow_detail/component_details/input_fields/index.ts deleted file mode 100644 index 7d0561f5..00000000 --- a/public/pages/workflow_detail/component_details/input_fields/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -export { TextField } from './text_field'; -export { JsonField } from './json_field'; -export { SelectField } from './select_field'; -export { ModelField } from './model_field'; diff --git a/public/pages/workflow_detail/component_details/input_fields/json_field.tsx b/public/pages/workflow_detail/component_details/input_fields/json_field.tsx deleted file mode 100644 index 73177bc0..00000000 --- a/public/pages/workflow_detail/component_details/input_fields/json_field.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiText, EuiTextArea } from '@elastic/eui'; - -interface JsonFieldProps { - label: string; - placeholder: string; -} - -/** - * An input field for a component where users manually enter - * in some custom JSON - */ -// TODO: integrate with formik -export function JsonField(props: JsonFieldProps) { - return ( - <> - - {props.label} - - - - ); -} diff --git a/public/pages/workflow_detail/component_details/input_fields/model_field.tsx b/public/pages/workflow_detail/component_details/input_fields/model_field.tsx deleted file mode 100644 index 1da7aa05..00000000 --- a/public/pages/workflow_detail/component_details/input_fields/model_field.tsx +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { Field, FieldProps, useFormikContext } from 'formik'; -import { - EuiFormRow, - EuiLink, - EuiRadioGroup, - EuiRadioGroupOption, - EuiSpacer, - EuiSuperSelect, - EuiSuperSelectOption, - EuiText, -} from '@elastic/eui'; -import { - BERT_SENTENCE_TRANSFORMER, - IComponentField, - MODEL_STATE, - ROBERTA_SENTENCE_TRANSFORMER, - WorkspaceFormValues, - ModelFormValue, - MODEL_CATEGORY, - MPNET_SENTENCE_TRANSFORMER, - NEURAL_SPARSE_TRANSFORMER, - NEURAL_SPARSE_DOC_TRANSFORMER, - NEURAL_SPARSE_TOKENIZER_TRANSFORMER, -} from '../../../../../common'; -import { isFieldInvalid } from '../../../../utils'; -import { AppState } from '../../../../store'; - -interface ModelFieldProps { - field: IComponentField; - componentId: string; - onFormChange: () => void; -} - -type ModelItem = ModelFormValue & { - name: string; -}; - -// TODO: there is no concrete UX for model selection and model provisioning. This component is TBD -// and simply provides the ability to select existing models, or deploy some pretrained ones, -// and persist all of this in form state. -/** - * A specific field for selecting existing deployed models, or available pretrained models. - */ -export function ModelField(props: ModelFieldProps) { - // Initial store is fetched when loading base page. We don't - // re-fetch here as it could overload client-side if user clicks back and forth / - // keeps re-rendering this component (and subsequently re-fetching data) as they're building flows - const models = useSelector((state: AppState) => state.models.models); - - const formField = `${props.componentId}.${props.field.id}`; - const { errors, touched } = useFormikContext(); - - // Deployed models state - const [deployedModels, setDeployedModels] = useState([]); - const [pretrainedModels, setPretrainedModels] = useState([]); - const [selectableModels, setSelectableModels] = useState([]); - - // Radio options state - const radioOptions = [ - { - id: MODEL_CATEGORY.DEPLOYED, - label: 'Existing deployed models', - }, - { - id: MODEL_CATEGORY.PRETRAINED, - label: 'Pretrained models', - }, - ] as EuiRadioGroupOption[]; - const [selectedRadioId, setSelectedRadioId] = useState< - MODEL_CATEGORY | undefined - >(undefined); - - // Initialize available deployed models - useEffect(() => { - if (models) { - const modelItems = [] as ModelItem[]; - Object.keys(models).forEach((modelId) => { - if (models[modelId].state === MODEL_STATE.DEPLOYED) { - modelItems.push({ - id: modelId, - name: models[modelId].name, - category: MODEL_CATEGORY.DEPLOYED, - algorithm: models[modelId].algorithm, - } as ModelItem); - } - }); - setDeployedModels(modelItems); - } - }, [models]); - - // Initialize available pretrained models - useEffect(() => { - const modelItems = [ - { - id: ROBERTA_SENTENCE_TRANSFORMER.name, - name: ROBERTA_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: ROBERTA_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: MPNET_SENTENCE_TRANSFORMER.name, - name: MPNET_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: MPNET_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: BERT_SENTENCE_TRANSFORMER.name, - name: BERT_SENTENCE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: BERT_SENTENCE_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_TRANSFORMER.name, - name: NEURAL_SPARSE_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_DOC_TRANSFORMER.name, - name: NEURAL_SPARSE_DOC_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_DOC_TRANSFORMER.algorithm, - }, - { - id: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.name, - name: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.shortenedName, - category: MODEL_CATEGORY.PRETRAINED, - algorithm: NEURAL_SPARSE_TOKENIZER_TRANSFORMER.algorithm, - }, - ]; - setPretrainedModels(modelItems); - }, []); - - // Update the valid available models when the radio selection changes. - // e.g., only show deployed models when 'deployed' button is selected - useEffect(() => { - if (selectedRadioId !== undefined) { - // TODO: add fine-grained filtering so only relevant pretrained and existing models - // are visible based on the use case - if (selectedRadioId === MODEL_CATEGORY.DEPLOYED) { - setSelectableModels(deployedModels); - } else { - setSelectableModels(pretrainedModels); - } - } - }, [selectedRadioId, deployedModels, pretrainedModels]); - - return ( - - {({ field, form }: FieldProps) => { - // a hook to update the model category and trigger reloading - // of valid models to select from - useEffect(() => { - setSelectedRadioId(field.value.category); - }, [field.value.category]); - return ( - - - Learn more - - - ) : undefined - } - helpText={props.field.helpText || undefined} - > - <> - { - // if user selects a new category: - // 1. clear the saved ID - // 2. update the field category - form.setFieldValue(formField, { - id: '', - category: radioId, - } as ModelFormValue); - props.onFormChange(); - }} - > - - - ({ - value: option.id, - inputDisplay: ( - <> - {option.name} - - ), - dropdownDisplay: ( - <> - {option.name} - - {option.category} - - - {option.algorithm} - - - ), - disabled: false, - } as EuiSuperSelectOption) - )} - valueOfSelected={field.value.id || ''} - onChange={(option: string) => { - form.setFieldValue(formField, { - id: option, - category: selectedRadioId, - } as ModelFormValue); - props.onFormChange(); - }} - isInvalid={isFieldInvalid( - props.componentId, - props.field.id, - errors, - touched - )} - /> - - - ); - }} - - ); -} diff --git a/public/pages/workflow_detail/component_details/input_fields/select_field.tsx b/public/pages/workflow_detail/component_details/input_fields/select_field.tsx deleted file mode 100644 index 9687989d..00000000 --- a/public/pages/workflow_detail/component_details/input_fields/select_field.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect, useState } from 'react'; -import { Field, FieldProps, useFormikContext } from 'formik'; -import { - EuiFormRow, - EuiLink, - EuiSuperSelect, - EuiSuperSelectOption, - EuiText, -} from '@elastic/eui'; -import { IComponentField, WorkspaceFormValues } from '../../../../../common'; -import { getInitialValue, isFieldInvalid } from '../../../../utils'; - -interface SelectFieldProps { - field: IComponentField; - componentId: string; - onFormChange: () => void; -} - -/** - * An input field for a component where users select from a list of available - * options. - */ -export function SelectField(props: SelectFieldProps) { - // Options state - const [options, setOptions] = useState([]); - - // Populate options depending on the select type - useEffect(() => { - // TODO: figure out how we want to utilize select types to customize the options - if (props.field.selectType === 'model') { - } - }, []); - - const formField = `${props.componentId}.${props.field.id}`; - const { errors, touched } = useFormikContext(); - - return ( - - {({ field, form }: FieldProps) => { - return ( - - - Learn more - - - ) : undefined - } - helpText={props.field.helpText || undefined} - > - - ({ - value: option, - inputDisplay: {option}, - disabled: false, - } as EuiSuperSelectOption) - )} - valueOfSelected={field.value || getInitialValue(props.field.type)} - onChange={(option) => { - form.setFieldValue(formField, option); - props.onFormChange(); - }} - isInvalid={isFieldInvalid( - props.componentId, - props.field.id, - errors, - touched - )} - /> - - ); - }} - - ); -} diff --git a/public/pages/workflow_detail/component_details/input_fields/text_field.tsx b/public/pages/workflow_detail/component_details/input_fields/text_field.tsx deleted file mode 100644 index 038051f1..00000000 --- a/public/pages/workflow_detail/component_details/input_fields/text_field.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { Field, FieldProps, useFormikContext } from 'formik'; -import { EuiFieldText, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; -import { IComponentField, WorkspaceFormValues } from '../../../../../common'; -import { - getInitialValue, - isFieldInvalid, - getFieldError, -} from '../../../../utils'; - -interface TextFieldProps { - field: IComponentField; - componentId: string; - onFormChange: () => void; -} - -/** - * An input field for a component where users input plaintext - */ -export function TextField(props: TextFieldProps) { - const formField = `${props.componentId}.${props.field.id}`; - const { errors, touched } = useFormikContext(); - - return ( - - {({ field, form }: FieldProps) => { - return ( - - - Learn more - - - ) : undefined - } - helpText={props.field.helpText || undefined} - error={getFieldError(props.componentId, props.field.id, errors)} - isInvalid={isFieldInvalid( - props.componentId, - props.field.id, - errors, - touched - )} - > - { - form.setFieldValue(formField, e.target.value); - props.onFormChange(); - }} - /> - - ); - }} - - ); -} diff --git a/public/pages/workflow_detail/component_details/provisioned_component_inputs.tsx b/public/pages/workflow_detail/component_details/provisioned_component_inputs.tsx deleted file mode 100644 index dbc050dd..00000000 --- a/public/pages/workflow_detail/component_details/provisioned_component_inputs.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; - -// Simple prompt to display when the workflow is provisioned. -export function ProvisionedComponentInputs() { - return ( - The workflow has been provisioned} - titleSize="s" - body={ - <> - Please deprovision first to continue editing. - - } - /> - ); -} diff --git a/public/pages/workflow_detail/resizable_workspace.tsx b/public/pages/workflow_detail/resizable_workspace.tsx index e0412a1d..5901680e 100644 --- a/public/pages/workflow_detail/resizable_workspace.tsx +++ b/public/pages/workflow_detail/resizable_workspace.tsx @@ -35,7 +35,7 @@ import { updateWorkflow, useAppDispatch, } from '../../store'; -import { Workspace } from './workspace/workspace'; +import { Workspace } from './workspace'; // styling import './workspace/workspace-styles.scss'; diff --git a/public/pages/workflow_detail/utils/index.ts b/public/pages/workflow_detail/utils/index.ts index 94f7e2e4..e26183a6 100644 --- a/public/pages/workflow_detail/utils/index.ts +++ b/public/pages/workflow_detail/utils/index.ts @@ -3,6 +3,5 @@ * SPDX-License-Identifier: Apache-2.0 */ -export * from './utils'; export * from './workflow_to_template_utils'; export * from './data_extractor_utils'; diff --git a/public/pages/workflow_detail/utils/utils.ts b/public/pages/workflow_detail/utils/utils.ts deleted file mode 100644 index fd26a1a8..00000000 --- a/public/pages/workflow_detail/utils/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WorkspaceFlowState } from '../../../../common'; - -// TODO: implement this -/** - * Validates the UI workflow state. - * Note we don't have to validate connections since that is done via input/output handlers. - * But we need to validate there are no open connections - */ -export function validateWorkspaceFlow( - workspaceFlow: WorkspaceFlowState -): boolean { - return true; -} diff --git a/public/pages/workflow_detail/workflow_detail.tsx b/public/pages/workflow_detail/workflow_detail.tsx index 4c1c4e6a..16863cfb 100644 --- a/public/pages/workflow_detail/workflow_detail.tsx +++ b/public/pages/workflow_detail/workflow_detail.tsx @@ -17,7 +17,7 @@ import { searchModels, useAppDispatch, } from '../../store'; -import { ResizableWorkspace } from './workspace'; +import { ResizableWorkspace } from './resizable_workspace'; import { DEFAULT_NEW_WORKFLOW_NAME, FETCH_ALL_QUERY_BODY, diff --git a/public/pages/workflow_detail/workspace/index.ts b/public/pages/workflow_detail/workspace/index.ts index e772f3d3..47ade8d8 100644 --- a/public/pages/workflow_detail/workspace/index.ts +++ b/public/pages/workflow_detail/workspace/index.ts @@ -3,4 +3,4 @@ * SPDX-License-Identifier: Apache-2.0 */ -export { ResizableWorkspace } from '../resizable_workspace'; +export { Workspace } from './workspace'; diff --git a/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx b/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx index aac57ffe..3dbe008e 100644 --- a/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/input_handle.tsx @@ -6,7 +6,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { EuiText } from '@elastic/eui'; -import { IComponent, IComponentInput } from '../../../../component_types'; +import { IComponent, IComponentInput } from '../../../../../common'; import { calculateHandlePosition, isValidConnection } from './utils'; interface InputHandleProps { @@ -29,7 +29,7 @@ export function InputHandle(props: InputHandleProps) { {props.input.label} // @ts-ignore diff --git a/public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx b/public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx deleted file mode 100644 index 777e36b4..00000000 --- a/public/pages/workflow_detail/workspace/workspace_components/new_or_existing_tabs.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { EuiTab, EuiTabs } from '@elastic/eui'; - -/** - * A helper component containing the togglable 'New' vs. 'Existing' tabs. - */ - -interface NewOrExistingTabsProps { - selectedTabId: string; - setSelectedTabId(tabId: string): void; -} - -const inputTabs = [ - { - id: 'new', - name: 'New', - disabled: false, - }, - { - id: 'existing', - name: 'Existing', - disabled: true, - }, -]; - -export function NewOrExistingTabs(props: NewOrExistingTabsProps) { - return ( - - {inputTabs.map((tab, idx) => { - return ( - props.setSelectedTabId(tab.id)} - isSelected={tab.id === props.selectedTabId} - disabled={tab.disabled} - key={idx} - > - {tab.name} - - ); - })} - - ); -} diff --git a/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx b/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx index c8276b60..dc3d2bef 100644 --- a/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/output_handle.tsx @@ -6,7 +6,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { Connection, Handle, Position, useReactFlow } from 'reactflow'; import { EuiText } from '@elastic/eui'; -import { IComponent, IComponentOutput } from '../../../../component_types'; +import { IComponent, IComponentOutput } from '../../../../../common'; import { calculateHandlePosition, isValidConnection } from './utils'; interface OutputHandleProps { @@ -18,7 +18,6 @@ export function OutputHandle(props: OutputHandleProps) { const ref = useRef(null); const reactFlowInstance = useReactFlow(); const [position, setPosition] = useState(0); - const outputClasses = props.output.baseClasses.join('|'); useEffect(() => { setPosition(calculateHandlePosition(ref)); @@ -30,7 +29,7 @@ export function OutputHandle(props: OutputHandleProps) { {props.output.label} // @ts-ignore diff --git a/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx b/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx index 8f89562e..fb245a1c 100644 --- a/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx +++ b/public/pages/workflow_detail/workspace/workspace_components/workspace_component.tsx @@ -12,11 +12,9 @@ import { EuiTitle, EuiSpacer, } from '@elastic/eui'; -import { setDirty, useAppDispatch } from '../../../../store'; -import { IComponentData } from '../../../../component_types'; +import { IComponentData } from '../../../../../common'; import { InputHandle } from './input_handle'; import { OutputHandle } from './output_handle'; -import { Edge, useReactFlow } from 'reactflow'; // styling import '../../workspace/reactflow-styles.scss'; @@ -31,35 +29,11 @@ interface WorkspaceComponentProps { * As users interact with it (input data, add connections), the stored IComponent data will update. */ export function WorkspaceComponent(props: WorkspaceComponentProps) { - const dispatch = useAppDispatch(); const component = props.data; - // TODO: remove hardcoded logic that only create fields are allowed - const containsFormFields = - props.data.createFields !== undefined && props.data.createFields.length > 0; - const reactFlowInstance = useReactFlow(); - - // TODO: re-enable deletion - const deleteNode = (nodeId: string) => { - reactFlowInstance.setNodes( - reactFlowInstance.getNodes().filter((node: Node) => node.id !== nodeId) - ); - // Also delete any dangling edges attached to the component - reactFlowInstance.setEdges( - reactFlowInstance - .getEdges() - .filter( - (edge: Edge) => edge.source !== nodeId && edge.target !== nodeId - ) - ); - dispatch(setDirty()); - }; - - const backgroundColor = containsFormFields ? '#172430' : '#0A121A'; return ( @@ -68,18 +42,7 @@ export function WorkspaceComponent(props: WorkspaceComponentProps) {

{component.label}

- - { - // TODO: re-enable deletion - } - {/* { - deleteNode(component.id); - }} - aria-label="Delete" - /> */} - + } > diff --git a/public/pages/workflows/new_workflow/utils.ts b/public/pages/workflows/new_workflow/utils.ts index 2ba748cb..4e8ad4e1 100644 --- a/public/pages/workflows/new_workflow/utils.ts +++ b/public/pages/workflows/new_workflow/utils.ts @@ -12,7 +12,21 @@ import { UIState, PROCESSOR_TYPE, IModelProcessorConfig, + COMPONENT_CLASS, + COMPONENT_CATEGORY, + NODE_CATEGORY, + ReactFlowComponent, + ReactFlowEdge, + WorkspaceFlowState, } from '../../../../common'; +import { generateId, initComponentData } from '../../../utils'; +import { MarkerType } from 'reactflow'; +import { + Document, + KnnIndexer, + NeuralQuery, + TextEmbeddingTransformer, +} from '../../../component_types'; // Fn to produce the complete preset template with all necessary UI metadata. export function enrichPresetWorkflowWithUiMetadata( @@ -83,6 +97,10 @@ function fetchEmptyMetadata(): UIState { }, }, }, + workspace_flow: { + nodes: [], + edges: [], + }, }; } @@ -103,200 +121,181 @@ function fetchSemanticSearchMetadata(): UIState { }, }, ] as IModelProcessorConfig; + baseState.workspace_flow = fetchSemanticSearchWorkspaceFlow(); return baseState; } -// function fetchSemanticSearchWorkspaceFlow(): WorkspaceFlowState { -// const ingestId0 = generateId(COMPONENT_CLASS.DOCUMENT); -// const ingestId1 = generateId(COMPONENT_CLASS.TEXT_EMBEDDING_TRANSFORMER); -// const ingestId2 = generateId(COMPONENT_CLASS.KNN_INDEXER); -// const ingestGroupId = generateId(COMPONENT_CATEGORY.INGEST); -// const searchGroupId = generateId(COMPONENT_CATEGORY.SEARCH); -// const searchId0 = generateId(COMPONENT_CLASS.NEURAL_QUERY); -// const searchId1 = generateId(COMPONENT_CLASS.SPARSE_ENCODER_TRANSFORMER); -// const searchId2 = generateId(COMPONENT_CLASS.KNN_INDEXER); -// const edgeId0 = generateId('edge'); -// const edgeId1 = generateId('edge'); -// const edgeId2 = generateId('edge'); -// const edgeId3 = generateId('edge'); +function fetchSemanticSearchWorkspaceFlow(): WorkspaceFlowState { + const ingestId0 = generateId(COMPONENT_CLASS.DOCUMENT); + const ingestId1 = generateId(COMPONENT_CLASS.TEXT_EMBEDDING_TRANSFORMER); + const ingestId2 = generateId(COMPONENT_CLASS.KNN_INDEXER); + const ingestGroupId = generateId(COMPONENT_CATEGORY.INGEST); + const searchGroupId = generateId(COMPONENT_CATEGORY.SEARCH); + const searchId0 = generateId(COMPONENT_CLASS.NEURAL_QUERY); + const searchId1 = generateId(COMPONENT_CLASS.SPARSE_ENCODER_TRANSFORMER); + const searchId2 = generateId(COMPONENT_CLASS.KNN_INDEXER); + const edgeId0 = generateId('edge'); + const edgeId1 = generateId('edge'); + const edgeId2 = generateId('edge'); + const edgeId3 = generateId('edge'); -// const ingestNodes = [ -// { -// id: ingestGroupId, -// position: { x: 400, y: 400 }, -// type: NODE_CATEGORY.INGEST_GROUP, -// data: { label: COMPONENT_CATEGORY.INGEST }, -// style: { -// width: 1300, -// height: 400, -// }, -// className: 'reactflow__group-node__ingest', -// selectable: true, -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId0, -// position: { x: 100, y: 70 }, -// data: initComponentData(new Document().toObj(), ingestId0), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId1, -// position: { x: 500, y: 70 }, -// data: initComponentData( -// new TextEmbeddingTransformer().toObj(), -// ingestId1 -// ), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: ingestId2, -// position: { x: 900, y: 70 }, -// data: initComponentData(new KnnIndexer().toObj(), ingestId2), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: ingestGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// ] as ReactFlowComponent[]; -// const searchNodes = [ -// { -// id: searchGroupId, -// position: { x: 400, y: 1000 }, -// type: NODE_CATEGORY.SEARCH_GROUP, -// data: { label: COMPONENT_CATEGORY.SEARCH }, -// style: { -// width: 1300, -// height: 400, -// }, -// className: 'reactflow__group-node__search', -// selectable: true, -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId0, -// position: { x: 100, y: 70 }, -// data: initComponentData(new NeuralQuery().toObj(), searchId0), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId1, -// position: { x: 500, y: 70 }, -// data: initComponentData( -// new TextEmbeddingTransformer().toPlaceholderObj(), -// searchId1 -// ), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// { -// id: searchId2, -// position: { x: 900, y: 70 }, -// data: initComponentData(new KnnIndexer().toPlaceholderObj(), searchId2), -// type: NODE_CATEGORY.CUSTOM, -// parentNode: searchGroupId, -// extent: 'parent', -// draggable: true, -// deletable: false, -// }, -// ] as ReactFlowComponent[]; + const ingestNodes = [ + { + id: ingestGroupId, + position: { x: 400, y: 400 }, + type: NODE_CATEGORY.INGEST_GROUP, + data: { label: COMPONENT_CATEGORY.INGEST }, + style: { + width: 1300, + height: 400, + }, + className: 'reactflow__group-node__ingest', + selectable: true, + draggable: true, + deletable: false, + }, + { + id: ingestId0, + position: { x: 100, y: 70 }, + data: initComponentData(new Document().toObj(), ingestId0), + type: NODE_CATEGORY.CUSTOM, + parentNode: ingestGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + { + id: ingestId1, + position: { x: 500, y: 70 }, + data: initComponentData( + new TextEmbeddingTransformer().toObj(), + ingestId1 + ), + type: NODE_CATEGORY.CUSTOM, + parentNode: ingestGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + { + id: ingestId2, + position: { x: 900, y: 70 }, + data: initComponentData(new KnnIndexer().toObj(), ingestId2), + type: NODE_CATEGORY.CUSTOM, + parentNode: ingestGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + ] as ReactFlowComponent[]; + const searchNodes = [ + { + id: searchGroupId, + position: { x: 400, y: 1000 }, + type: NODE_CATEGORY.SEARCH_GROUP, + data: { label: COMPONENT_CATEGORY.SEARCH }, + style: { + width: 1300, + height: 400, + }, + className: 'reactflow__group-node__search', + selectable: true, + draggable: true, + deletable: false, + }, + { + id: searchId0, + position: { x: 100, y: 70 }, + data: initComponentData(new NeuralQuery().toObj(), searchId0), + type: NODE_CATEGORY.CUSTOM, + parentNode: searchGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + { + id: searchId1, + position: { x: 500, y: 70 }, + data: initComponentData( + new TextEmbeddingTransformer().toPlaceholderObj(), + searchId1 + ), + type: NODE_CATEGORY.CUSTOM, + parentNode: searchGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + { + id: searchId2, + position: { x: 900, y: 70 }, + data: initComponentData(new KnnIndexer().toPlaceholderObj(), searchId2), + type: NODE_CATEGORY.CUSTOM, + parentNode: searchGroupId, + extent: 'parent', + draggable: true, + deletable: false, + }, + ] as ReactFlowComponent[]; -// return { -// nodes: [...ingestNodes, ...searchNodes], -// edges: [ -// { -// id: edgeId0, -// key: edgeId0, -// source: ingestId0, -// target: ingestId1, -// sourceClasses: ingestNodes.find((node) => node.id === ingestId0)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId1, -// key: edgeId1, -// source: ingestId1, -// target: ingestId2, -// sourceClasses: ingestNodes.find((node) => node.id === ingestId1)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === ingestId2)?.data -// .baseClasses, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId2, -// key: edgeId2, -// source: searchId0, -// target: searchId1, -// sourceClasses: ingestNodes.find((node) => node.id === searchId0)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === searchId1)?.data -// .baseClasses, -// sourceHandle: COMPONENT_CLASS.QUERY, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// { -// id: edgeId3, -// key: edgeId3, -// source: searchId1, -// target: searchId2, -// sourceClasses: ingestNodes.find((node) => node.id === searchId1)?.data -// .baseClasses, -// targetClasses: ingestNodes.find((node) => node.id === searchId2)?.data -// .baseClasses, -// sourceHandle: COMPONENT_CLASS.QUERY, -// targetHandle: COMPONENT_CLASS.QUERY, -// markerEnd: { -// type: MarkerType.ArrowClosed, -// width: 20, -// height: 20, -// }, -// zIndex: 2, -// deletable: false, -// }, -// ] as ReactFlowEdge[], -// }; -// } + return { + nodes: [...ingestNodes, ...searchNodes], + edges: [ + { + id: edgeId0, + key: edgeId0, + source: ingestId0, + target: ingestId1, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + }, + zIndex: 2, + deletable: false, + }, + { + id: edgeId1, + key: edgeId1, + source: ingestId1, + target: ingestId2, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + }, + zIndex: 2, + deletable: false, + }, + { + id: edgeId2, + key: edgeId2, + source: searchId0, + target: searchId1, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + }, + zIndex: 2, + deletable: false, + }, + { + id: edgeId3, + key: edgeId3, + source: searchId1, + target: searchId2, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 20, + height: 20, + }, + zIndex: 2, + deletable: false, + }, + ] as ReactFlowEdge[], + }; +} // function fetchNeuralSparseSearchWorkspaceFlow(): WorkspaceFlowState { // const ingestId0 = generateId(COMPONENT_CLASS.DOCUMENT); diff --git a/public/utils/utils.ts b/public/utils/utils.ts index 877d844c..ed34a152 100644 --- a/public/utils/utils.ts +++ b/public/utils/utils.ts @@ -11,10 +11,7 @@ import { cloneDeep } from 'lodash'; import { IComponent, IComponentData, - IComponentField, - WorkspaceFormValues, WORKFLOW_STATE, - ReactFlowComponent, Workflow, WorkflowTemplate, ModelFormValue, @@ -169,7 +166,6 @@ function formikToIndexUiConfig( **************** Schema / validation utils ********************** */ -// TODO: implement this. Refer to getComponentSchema() below export function uiConfigToSchema(config: WorkflowConfig): WorkflowSchema { const schemaObj = {} as WorkflowSchemaObj; schemaObj['ingest'] = ingestConfigToSchema(config.ingest); @@ -217,35 +213,6 @@ function searchConfigToSchema( return yup.object(searchSchemaObj); } -// TODO: below, we are hardcoding to only persisting and validating create fields. -// If we support both, we will need to dynamically update. -// Converting stored values in component data to initial formik values -export function componentDataToFormik(data: IComponentData): FormikValues { - const formikValues = {} as FormikValues; - data.createFields?.forEach((field) => { - formikValues[field.id] = field.value || getInitialValue(field.type); - }); - return formikValues; -} - -// TODO: below, we are hardcoding to only persisting and validating create fields. -// If we support both, we will need to dynamically update. -// Injecting the current form values into the component data -export function formikToComponentData( - origData: IComponentData, - formValues: FormikValues -): IComponentData { - return { - ...origData, - createFields: origData.createFields?.map( - (createField: IComponentField) => ({ - ...createField, - value: formValues[createField.id], - }) - ), - } as IComponentData; -} - // Helper fn to remove state-related fields from a workflow and have a stateless template // to export and/or pass around, use when updating, etc. export function reduceToTemplate(workflow: Workflow): WorkflowTemplate { @@ -282,38 +249,10 @@ export function getInitialValue(fieldType: ConfigFieldType): ConfigFieldValue { } } -// Process the raw ReactFlow nodes. -// De-select them all, and propagate the form data to the internal node data -export function processNodes( - nodes: ReactFlowComponent[], - formValues: WorkspaceFormValues -): ReactFlowComponent[] { - return nodes.map((node: ReactFlowComponent) => { - return { - ...node, - selected: false, - data: formikToComponentData( - { ...node.data, selected: false }, - formValues[node.id] - ), - }; - }); -} - /* **************** Yup (validation) utils ********************** */ -// TODO: below, we are hardcoding to only persisting and validating create fields. -// If we support both, we will need to dynamically update. -export function getComponentSchema(data: IComponentData): ObjectSchema { - const schemaObj = {} as { [key: string]: Schema }; - data.createFields?.forEach((field) => { - schemaObj[field.id] = getFieldSchema(field); - }); - return yup.object(schemaObj); -} - function getFieldSchema(field: IConfigField): Schema { let baseSchema: Schema; switch (field.type) {