Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial component interfaces & basic node rendering #32

Merged
merged 1 commit into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions public/component_types/base_interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { COMPONENT_CATEGORY } from '../utils';

/**
* ************ Types **************************
*/

// TODO: may change some/all of these to enums later
export type BaseClass = string;
export type UIFlow = string;
export type FieldType = 'string' | 'json' | 'select';
jackiehanyang marked this conversation as resolved.
Show resolved Hide resolved

/**
* ************ Base interfaces ****************
*/

/**
* Represents a single base class as an input handle for a component.
* It may be optional. It may also accept multiples of that class.
*/
export interface IComponentInput {
id: string;
label: string;
baseClass: string;
optional: boolean;
acceptMultiple: boolean;
}

/**
* An input field for a component. Specifies enough configuration for the
* UI node to render it properly within the component (show it as optional,
* put it in advanced settings, placeholder values, etc.)
*/
export interface IComponentField {
label: string;
type: FieldType;
placeholder?: string;
optional?: boolean;
advanced?: boolean;
}

/**
* Represents the list of base classes as a single output handle for
* a component.
*/
export interface IComponentOutput {
id: string;
label: string;
baseClasses: BaseClass[];
}

/**
* The base interface the components will implement.
*/
export interface IComponent {
id: string;
type: BaseClass;
label: string;
description: string;
// will be used for grouping together in the drag-and-drop component library
category: 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;
// determines if this is something that will be included in the use
// case template construction (query or ingest flows). provisioning flow
// is handled by the allowsCreation flag above.
isApplicationStep: boolean;
// the set of allowed flows this component can be drug into the workspace
allowedFlows: UIFlow[];
// the list of base classes that will be used in the component output
baseClasses?: BaseClass[];
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[];
// we will need some init function when the component is drug into the workspace
init?(): Promise<any>;
jackiehanyang marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 8 additions & 0 deletions public/component_types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export * from './base_interfaces';
export * from './processors';
export * from './indices';
6 changes: 6 additions & 0 deletions public/component_types/indices/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 * from './knn_index';
87 changes: 87 additions & 0 deletions public/component_types/indices/knn_index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { COMPONENT_CATEGORY } from '../../utils';
import {
IComponent,
IComponentField,
IComponentInput,
IComponentOutput,
UIFlow,
BaseClass,
} from '../base_interfaces';

/**
* A k-NN index UI component
*/
export class KnnIndex implements IComponent {
id: string;
type: BaseClass;
label: string;
description: string;
category: COMPONENT_CATEGORY;
allowsCreation: boolean;
isApplicationStep: boolean;
allowedFlows: UIFlow[];
baseClasses: BaseClass[];
inputs: IComponentInput[];
fields: IComponentField[];
createFields: IComponentField[];
outputs: IComponentOutput[];

constructor() {
this.id = 'knn_index';
this.type = 'knn_index';
this.label = 'k-NN Index';
this.description = 'A k-NN Index to be used as a vector store';
this.category = COMPONENT_CATEGORY.INDICES;
this.allowsCreation = true;
this.isApplicationStep = false;
// TODO: 'other' may not be how this is stored. the idea is 'other' allows
// for placement outside of the ingest or query flows- typically something
// that will be referenced/used as input across multiple flows
this.allowedFlows = ['Ingest', 'Query', 'Other'];
this.baseClasses = [this.type];
this.inputs = [];
this.fields = [
{
label: 'Index Name',
type: 'select',
optional: false,
advanced: false,
},
];
this.createFields = [
{
label: 'Index Name',
type: 'string',
optional: false,
advanced: false,
},
// we don't need to expose "settings" here since it will be index.knn by default
// just let users customize the mappings
// TODO: figure out how to handle defaults for all of these values. maybe toggle between
// simple form inputs vs. complex JSON editor
{
label: 'Mappings',
type: 'json',
placeholder: 'Enter an index mappings JSON blob...',
optional: false,
advanced: false,
},
];
this.outputs = [
{
id: this.id,
label: this.label,
baseClasses: this.baseClasses,
},
];
}

async init(): Promise<any> {
return new KnnIndex();
}
}
6 changes: 6 additions & 0 deletions public/component_types/processors/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 * from './text_embedding_processor';
77 changes: 77 additions & 0 deletions public/component_types/processors/text_embedding_processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { COMPONENT_CATEGORY } from '../../utils';
import {
IComponent,
IComponentField,
IComponentInput,
IComponentOutput,
UIFlow,
BaseClass,
} from '../base_interfaces';

/**
* A text embedding processor UI component
*/
export class TextEmbeddingProcessor implements IComponent {
id: string;
type: BaseClass;
label: string;
description: string;
category: COMPONENT_CATEGORY;
allowsCreation: boolean;
isApplicationStep: boolean;
allowedFlows: UIFlow[];
baseClasses: BaseClass[];
inputs: IComponentInput[];
fields: IComponentField[];
outputs: IComponentOutput[];

constructor() {
this.id = 'text_embedding_processor';
this.type = 'text_embedding_processor';
this.label = 'Text Embedding Processor';
this.description =
'A text embedding ingest processor to be used in an ingest pipeline';
this.category = COMPONENT_CATEGORY.INGEST_PROCESSORS;
this.allowsCreation = false;
this.isApplicationStep = false;
this.allowedFlows = ['Ingest'];
this.baseClasses = [this.type];
this.inputs = [];
this.fields = [
{
label: 'Model ID',
type: 'string',
optional: false,
advanced: false,
},
{
label: 'Input Field',
type: 'string',
optional: false,
advanced: false,
},
{
label: 'Output Field',
type: 'string',
optional: false,
advanced: false,
},
];
this.outputs = [
{
id: this.id,
label: this.label,
baseClasses: this.baseClasses,
},
];
}

async init(): Promise<any> {
return new TextEmbeddingProcessor();
}
}
8 changes: 8 additions & 0 deletions public/pages/workflow_builder/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export * from './input_fields';
export { NewOrExistingTabs } from './new_or_existing_tabs';
export { InputFieldList } from './input_field_list';
62 changes: 62 additions & 0 deletions public/pages/workflow_builder/components/input_field_list.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { IComponentField } from '../../../component_types';
import { TextField, JsonField, SelectField } from './input_fields';

/**
* A helper component to format all of the input fields for a component. Dynamically
* render based on the input type.
*/

interface InputFieldListProps {
inputFields?: IComponentField[];
}

export function InputFieldList(props: InputFieldListProps) {
return (
<EuiFlexItem>
{props.inputFields?.map((field, idx) => {
let el;
switch (field.type) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: instead of using a switch statement, consider refactoring it to use a mapping object for better clarity and scalability. This also makes adding new input types in the future easier

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Likely this will be moved into more complex helper fns as the number of object types will grow, and the complexity to render them will grow. Prefer to just leave as a simple switch for now.

case 'string': {
el = (
<EuiFlexItem key={idx}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using index as a key isn't always a problem. But if the list can change over time, will it cause issues and even potentially introduce bugs? Do we have other unique identifier in IComponentField that we can use and the key here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the index as a key for items in a list is a common JS pattern since it is calculated on-the-fly for each render. This will likely change a lot as we iterate so prefer to leave for now.

<TextField
label={field.label}
placeholder={field.placeholder || ''}
/>
<EuiSpacer size="s" />
</EuiFlexItem>
);
break;
}
case 'json': {
el = (
<EuiFlexItem key={idx}>
<JsonField
label={field.label}
placeholder={field.placeholder || ''}
/>
</EuiFlexItem>
);
break;
}
case 'select': {
el = (
<EuiFlexItem key={idx}>
<SelectField />
</EuiFlexItem>
);
break;
}
}
return el;
})}
</EuiFlexItem>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { TextField } from './text_field';
export { JsonField } from './json_field';
export { SelectField } from './select_field';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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
*/
export function JsonField(props: JsonFieldProps) {
return (
<>
<EuiText size="s" className="eui-textLeft">
{props.label}
</EuiText>
<EuiTextArea placeholder={props.placeholder} />
</>
);
}
Loading
Loading