-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Modular node configuration system with UI improvements #27
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Node Configuration Panel - Modular Architecture | ||
|
|
||
| This directory contains a fully modularized node configuration system that replaces the original monolithic `node-config-panel.tsx` file (1229 lines). | ||
|
|
||
| ## Architecture Overview | ||
|
|
||
| ``` | ||
| node-config/ | ||
| ├── components/ | ||
| │ ├── parameter-handlers/ # Individual parameter type handlers | ||
| │ │ ├── SelectParameterHandler.tsx | ||
| │ │ ├── TextParameterHandler.tsx | ||
| │ │ ├── TextareaParameterHandler.tsx | ||
| │ │ ├── BooleanParameterHandler.tsx | ||
| │ │ ├── JsonParameterHandler.tsx | ||
| │ │ ├── NumberParameterHandler.tsx | ||
| │ │ ├── EmailParameterHandler.tsx | ||
| │ │ ├── PasswordParameterHandler.tsx | ||
| │ │ ├── UrlParameterHandler.tsx | ||
| │ │ ├── CredentialParameterHandler.tsx | ||
| │ │ └── StringListParameterHandler.tsx | ||
| │ ├── node-configurations/ # Node-specific configurations | ||
| │ │ ├── HttpNodeConfiguration.tsx | ||
| │ │ ├── EmailActionNodeConfiguration.tsx | ||
| │ │ ├── ScheduleNodeConfiguration.tsx | ||
| │ │ ├── WebhookNodeConfiguration.tsx | ||
| │ │ └── ManualNodeConfiguration.tsx | ||
| │ └── shared/ # Shared components | ||
| │ ├── FieldLabel.tsx | ||
| │ ├── SecurityWarning.tsx | ||
| │ └── ParameterRenderer.tsx | ||
| ├── utils/ # Utility functions | ||
| │ ├── parameter-utils.ts | ||
| │ └── config-utils.ts | ||
| ├── hooks/ # Custom React hooks | ||
| │ ├── useParameterState.ts | ||
| │ └── useNodeConfig.ts | ||
| └── index.ts # Main exports | ||
| ``` | ||
|
|
||
| ## Benefits | ||
|
|
||
| - **Maintainability**: Each component has a single responsibility | ||
| - **Reusability**: Parameter handlers can be reused across different node types | ||
| - **Testability**: Each module can be tested independently | ||
| - **Scalability**: Easy to add new parameter types or node configurations | ||
| - **Developer Experience**: Much easier to navigate and understand | ||
|
|
||
| ## How to Add New Parameter Types | ||
|
|
||
| 1. **Create a new parameter handler** in `components/parameter-handlers/`: | ||
| ```typescript | ||
| // MyNewParameterHandler.tsx | ||
| import { FieldLabel } from '../shared/FieldLabel' | ||
| import { ExtendedParameterDefinition, getParamPath } from '../../utils/parameter-utils' | ||
| import { getParamValue, getParameterDescription } from '../../utils/config-utils' | ||
|
|
||
| export function MyNewParameterHandler({ param, config, onConfigChange }: ParameterHandlerProps) { | ||
| // Implementation... | ||
| } | ||
| ``` | ||
|
|
||
| 2. **Add it to ParameterRenderer** in `components/shared/ParameterRenderer.tsx`: | ||
| ```typescript | ||
| import { MyNewParameterHandler } from '../parameter-handlers/MyNewParameterHandler' | ||
|
|
||
| // In the switch statement: | ||
| case 'myNewType': | ||
| return ( | ||
| <MyNewParameterHandler | ||
| key={param.path || param.name} | ||
| param={param} | ||
| config={config} | ||
| onConfigChange={onConfigChange} | ||
| /> | ||
| ) | ||
| ``` | ||
|
|
||
| 3. **Export it** in `index.ts`: | ||
| ```typescript | ||
| export { MyNewParameterHandler } from './components/parameter-handlers/MyNewParameterHandler' | ||
| ``` | ||
|
|
||
| ## How to Add New Node Configurations | ||
|
|
||
| 1. **Create a new configuration component** in `components/node-configurations/`: | ||
| ```typescript | ||
| // MyNodeConfiguration.tsx | ||
| export function MyNodeConfiguration({ config, onConfigChange }: NodeConfigProps) { | ||
| // Implementation... | ||
| } | ||
| ``` | ||
|
|
||
| 2. **Add it to the main panel** in `node-config-panel.tsx`: | ||
| ```typescript | ||
| import { MyNodeConfiguration } from './node-config' | ||
|
|
||
| // In renderConfig(): | ||
| if (data.nodeType === NodeType.ACTION && data.actionType === ActionType.MY_TYPE) { | ||
| return <MyNodeConfiguration config={config} onConfigChange={handleConfigChange} /> | ||
| } | ||
| ``` | ||
|
|
||
| ## Migration Summary | ||
|
|
||
| **Before**: 1 monolithic file with 1229 lines | ||
| - Hard to maintain and navigate | ||
| - Complex parameter handling logic mixed with UI | ||
| - Difficult to test individual components | ||
| - No code reusability | ||
|
|
||
| **After**: 25+ modular files with clear separation of concerns | ||
| - Each component has a single responsibility | ||
| - Easy to maintain and extend | ||
| - Fully testable components | ||
| - High code reusability | ||
| - Clear architecture patterns | ||
|
|
||
| ## Testing | ||
|
|
||
| The modular system has been tested and verified to: | ||
| - Compile without TypeScript errors | ||
| - Pass all linter checks | ||
| - Maintain all original functionality | ||
| - Support all parameter types and node configurations | ||
| - Provide proper type safety throughout | ||
|
|
||
| ## Future Enhancements | ||
|
|
||
| - Add unit tests for each parameter handler | ||
| - Implement advanced validation rules | ||
| - Add support for custom parameter validation | ||
| - Create a plugin system for third-party parameter types | ||
| - Add internationalization support | ||
| - Implement accessibility improvements |
55 changes: 55 additions & 0 deletions
55
...ents/workflow/node-config/components/node-configurations/EmailActionNodeConfiguration.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { Label } from '@/components/ui/label' | ||
| import { Input } from '@/components/ui/input' | ||
| import { EmailNodeConfig } from '@/nodes/EmailNode' | ||
|
|
||
| interface EmailActionNodeConfigurationProps { | ||
| config: EmailNodeConfig | ||
| onConfigChange: (path: string, value: unknown) => void | ||
| } | ||
|
|
||
| export function EmailActionNodeConfiguration({ config, onConfigChange }: EmailActionNodeConfigurationProps) { | ||
| return ( | ||
| <> | ||
| <div className="space-y-2"> | ||
| <Label>To (comma separated)</Label> | ||
| <Input | ||
| value={config.to?.join(', ') || ''} | ||
| onChange={(e) => onConfigChange('to', e.target.value.split(',').map(s => s.trim()).filter(s => s.length > 0))} | ||
| placeholder="user@example.com, another@example.com" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>Subject</Label> | ||
| <Input | ||
| value={config.subject || ''} | ||
| onChange={(e) => onConfigChange('subject', e.target.value)} | ||
| placeholder="Email subject" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>Body</Label> | ||
| <textarea | ||
| className="w-full p-2 border rounded-md bg-white text-gray-900 border-gray-300" | ||
| rows={6} | ||
| value={config.body || ''} | ||
| onChange={(e) => onConfigChange('body', e.target.value)} | ||
| placeholder="Email body content..." | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>From (optional)</Label> | ||
| <Input | ||
| value={config.from || ''} | ||
| onChange={(e) => onConfigChange('from', e.target.value)} | ||
| placeholder="sender@example.com" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| </div> | ||
| </> | ||
| ) | ||
| } | ||
128 changes: 128 additions & 0 deletions
128
components/workflow/node-config/components/node-configurations/HttpNodeConfiguration.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| import { Label } from '@/components/ui/label' | ||
| import { Input } from '@/components/ui/input' | ||
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' | ||
| import { HttpNodeConfig } from '@/types/workflow' | ||
|
|
||
| interface HttpNodeConfigurationProps { | ||
| config: HttpNodeConfig | ||
| onConfigChange: (path: string, value: unknown) => void | ||
| } | ||
|
|
||
| export function HttpNodeConfiguration({ config, onConfigChange }: HttpNodeConfigurationProps) { | ||
| return ( | ||
| <> | ||
| <div className="space-y-2"> | ||
| <Label>Method</Label> | ||
| <Select | ||
| value={config.method || 'GET'} | ||
| onValueChange={(value) => onConfigChange('method', value)} | ||
| > | ||
|
Justin322322 marked this conversation as resolved.
|
||
| <SelectTrigger className="bg-white text-gray-900 border-gray-300"> | ||
| <SelectValue /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value="GET">GET</SelectItem> | ||
| <SelectItem value="POST">POST</SelectItem> | ||
| <SelectItem value="PUT">PUT</SelectItem> | ||
| <SelectItem value="DELETE">DELETE</SelectItem> | ||
| <SelectItem value="PATCH">PATCH</SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>URL</Label> | ||
| <Input | ||
| value={config.url || ''} | ||
| onChange={(e) => onConfigChange('url', e.target.value)} | ||
| placeholder="https://api.example.com/endpoint" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>Authentication</Label> | ||
| <Select | ||
| value={config.authentication?.type || 'none'} | ||
| onValueChange={(value) => | ||
| onConfigChange('authentication', { | ||
| type: value as NonNullable<HttpNodeConfig['authentication']>['type'], | ||
| value: config.authentication?.value, | ||
| }) | ||
| } | ||
| > | ||
| <SelectTrigger className="bg-white text-gray-900 border-gray-300"> | ||
| <SelectValue /> | ||
| </SelectTrigger> | ||
| <SelectContent> | ||
| <SelectItem value="none">None</SelectItem> | ||
| <SelectItem value="bearer">Bearer Token</SelectItem> | ||
| <SelectItem value="basic">Basic (Base64 user:pass)</SelectItem> | ||
| <SelectItem value="apiKey">API Key (Header)</SelectItem> | ||
| </SelectContent> | ||
| </Select> | ||
| </div> | ||
|
Justin322322 marked this conversation as resolved.
|
||
|
|
||
| {config.authentication?.type && config.authentication.type !== 'none' && ( | ||
| <div className="space-y-2"> | ||
| <Label>Auth Value</Label> | ||
| <Input | ||
| value={config.authentication?.value || ''} | ||
| onChange={(e) => | ||
| onConfigChange('authentication', { | ||
| type: config.authentication?.type, | ||
| value: e.target.value, | ||
| }) | ||
| } | ||
| placeholder={ | ||
| config.authentication.type === 'bearer' | ||
| ? 'Bearer token' | ||
| : config.authentication.type === 'basic' | ||
| ? 'Base64 encoded user:pass' | ||
| : 'API Key' | ||
| } | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| </div> | ||
| )} | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>Headers (JSON)</Label> | ||
| <textarea | ||
| className="w-full p-2 border rounded-md text-sm font-mono bg-white text-gray-900 border-gray-300" | ||
| rows={4} | ||
| value={JSON.stringify(config.headers || {}, null, 2)} | ||
| onChange={(e) => { | ||
| try { | ||
| const headers = JSON.parse(e.target.value) as Record<string, string> | ||
| onConfigChange('headers', headers) | ||
| } catch { | ||
| // no-op | ||
| } | ||
| }} | ||
| placeholder='{"Content-Type": "application/json"}' | ||
| /> | ||
| </div> | ||
|
|
||
| {config.method !== 'GET' && ( | ||
| <div className="space-y-2"> | ||
| <Label>Body (JSON)</Label> | ||
| <textarea | ||
| className="w-full p-2 border rounded-md text-sm font-mono bg-white text-gray-900 border-gray-300" | ||
| rows={6} | ||
| value={JSON.stringify(config.body || {}, null, 2)} | ||
| onChange={(e) => { | ||
| try { | ||
| const body = JSON.parse(e.target.value) as unknown | ||
| onConfigChange('body', body) | ||
| } catch { | ||
| // no-op | ||
| } | ||
| }} | ||
| placeholder='{}' | ||
| /> | ||
| </div> | ||
| )} | ||
| </> | ||
| ) | ||
| } | ||
7 changes: 7 additions & 0 deletions
7
components/workflow/node-config/components/node-configurations/ManualNodeConfiguration.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export function ManualNodeConfiguration() { | ||
| return ( | ||
| <div className="text-sm text-gray-600"> | ||
| This trigger has no configuration. Use the Run button in the toolbar to start the workflow manually. | ||
| </div> | ||
| ) | ||
| } |
37 changes: 37 additions & 0 deletions
37
components/workflow/node-config/components/node-configurations/ScheduleNodeConfiguration.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { Label } from '@/components/ui/label' | ||
| import { Input } from '@/components/ui/input' | ||
| import { ScheduleNodeConfig } from '@/types/workflow' | ||
|
|
||
| interface ScheduleNodeConfigurationProps { | ||
| config: ScheduleNodeConfig | ||
| onConfigChange: (path: string, value: unknown) => void | ||
| } | ||
|
|
||
| export function ScheduleNodeConfiguration({ config, onConfigChange }: ScheduleNodeConfigurationProps) { | ||
| return ( | ||
| <> | ||
| <div className="space-y-2"> | ||
| <Label>Cron Expression</Label> | ||
| <Input | ||
| value={config.cron || ''} | ||
| onChange={(e) => onConfigChange('cron', e.target.value)} | ||
| placeholder="0 0 * * *" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
| <p className="text-xs text-gray-500"> | ||
| {"Examples: \"0 0 * * *\" (daily at midnight), \"*/5 * * * *\" (every 5 minutes)"} | ||
| </p> | ||
|
Justin322322 marked this conversation as resolved.
|
||
| </div> | ||
|
|
||
| <div className="space-y-2"> | ||
| <Label>Timezone</Label> | ||
| <Input | ||
| value={config.timezone || 'UTC'} | ||
| onChange={(e) => onConfigChange('timezone', e.target.value)} | ||
| placeholder="UTC" | ||
| className="bg-white text-gray-900 placeholder:text-gray-400 border-gray-300" | ||
| /> | ||
|
Justin322322 marked this conversation as resolved.
|
||
| </div> | ||
| </> | ||
| ) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.