From 4dcc77372db57d1e45df17699fba3da2fb932444 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Wed, 26 Mar 2025 09:03:03 +0000 Subject: [PATCH] chore(ui): Refactor action and connector form fields into single component --- jsconfig.json | 15 +++ webui/react-ui/src/components/ActionForm.jsx | 42 +----- .../src/components/actions/BaseAction.jsx | 44 +++++++ .../actions/GenerateImageAction.jsx | 49 +++++++ .../actions/GithubIssueCloserAction.jsx | 98 +++++++------- .../actions/GithubIssueCommenterAction.jsx | 98 +++++++------- .../actions/GithubIssueLabelerAction.jsx | 120 ++++++++--------- .../actions/GithubIssueOpenerAction.jsx | 98 +++++++------- .../actions/GithubRepositoryAction.jsx | 98 +++++++------- .../src/components/actions/SendMailAction.jsx | 120 ++++++++--------- .../components/actions/TwitterPostAction.jsx | 58 ++++---- .../src/components/common/FormField.jsx | 107 +++++++++++++++ .../components/common/FormFieldDefinition.jsx | 42 ++++++ .../components/connectors/BaseConnector.jsx | 46 +++++++ .../connectors/DiscordConnector.jsx | 57 ++++---- .../connectors/GithubIssuesConnector.jsx | 124 +++++++++--------- .../connectors/GithubPRsConnector.jsx | 124 +++++++++--------- .../components/connectors/IRCConnector.jsx | 121 ++++++++--------- .../components/connectors/SlackConnector.jsx | 103 +++++++-------- .../connectors/TelegramConnector.jsx | 35 +++-- .../connectors/TwitterConnector.jsx | 118 ++++++++--------- 21 files changed, 964 insertions(+), 753 deletions(-) create mode 100644 jsconfig.json create mode 100644 webui/react-ui/src/components/actions/BaseAction.jsx create mode 100644 webui/react-ui/src/components/actions/GenerateImageAction.jsx create mode 100644 webui/react-ui/src/components/common/FormField.jsx create mode 100644 webui/react-ui/src/components/common/FormFieldDefinition.jsx create mode 100644 webui/react-ui/src/components/connectors/BaseConnector.jsx diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..80636cc --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "target": "ES2022", + "jsx": "react", + "allowImportingTsExtensions": true, + "strictNullChecks": true, + "strictFunctionTypes": true + }, + "exclude": [ + "node_modules", + "**/node_modules/*" + ] +} \ No newline at end of file diff --git a/webui/react-ui/src/components/ActionForm.jsx b/webui/react-ui/src/components/ActionForm.jsx index 4a1b937..721d12f 100644 --- a/webui/react-ui/src/components/ActionForm.jsx +++ b/webui/react-ui/src/components/ActionForm.jsx @@ -7,6 +7,7 @@ import GithubIssueCommenterAction from './actions/GithubIssueCommenterAction'; import GithubRepositoryAction from './actions/GithubRepositoryAction'; import TwitterPostAction from './actions/TwitterPostAction'; import SendMailAction from './actions/SendMailAction'; +import GenerateImageAction from './actions/GenerateImageAction'; /** * ActionForm component for configuring an action @@ -103,46 +104,7 @@ const ActionForm = ({ actions = [], onChange, onRemove, onAdd }) => { case 'send-mail': return ; case 'generate_image': - return ( -
-
- - onActionConfigChange(index, 'apiKey', e.target.value)} - className="form-control" - placeholder="sk-..." - /> -
- -
- - onActionConfigChange(index, 'apiURL', e.target.value)} - className="form-control" - placeholder="https://api.openai.com/v1" - /> -
- -
- - onActionConfigChange(index, 'model', e.target.value)} - className="form-control" - placeholder="dall-e-3" - /> - Image generation model (e.g., dall-e-3) -
-
- ); + return ; default: return ; } diff --git a/webui/react-ui/src/components/actions/BaseAction.jsx b/webui/react-ui/src/components/actions/BaseAction.jsx new file mode 100644 index 0000000..10ba5f5 --- /dev/null +++ b/webui/react-ui/src/components/actions/BaseAction.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import FormFieldDefinition from '../common/FormFieldDefinition'; + +/** + * Base action component that renders form fields based on field definitions + * + * @param {Object} props Component props + * @param {number} props.index Action index + * @param {Function} props.onActionConfigChange Handler for config changes + * @param {Function} props.getConfigValue Helper to get config values + * @param {Array} props.fields Field definitions for this action + */ +const BaseAction = ({ + index, + onActionConfigChange, + getConfigValue, + fields = [] +}) => { + // Create an object with all the current values + const currentValues = {}; + + // Pre-populate with current values or defaults + fields.forEach(field => { + currentValues[field.name] = getConfigValue(field.name, field.defaultValue); + }); + + // Handle field value changes + const handleFieldChange = (name, value) => { + onActionConfigChange(name, value); + }; + + return ( +
+ +
+ ); +}; + +export default BaseAction; diff --git a/webui/react-ui/src/components/actions/GenerateImageAction.jsx b/webui/react-ui/src/components/actions/GenerateImageAction.jsx new file mode 100644 index 0000000..5f7a91f --- /dev/null +++ b/webui/react-ui/src/components/actions/GenerateImageAction.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import BaseAction from './BaseAction'; + +/** + * Generate Image action component + */ +const GenerateImageAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for Generate Image action + const fields = [ + { + name: 'apiKey', + label: 'OpenAI API Key', + type: 'text', + defaultValue: '', + placeholder: 'sk-...', + helpText: 'Your OpenAI API key for image generation', + required: false, + }, + { + name: 'apiURL', + label: 'API URL', + type: 'text', + defaultValue: '', + placeholder: 'http://localai:8081', + helpText: 'OpenAI compatible API endpoint URL', + required: false, + }, + { + name: 'model', + label: 'Model', + type: 'text', + defaultValue: '', + placeholder: 'dall-e-3', + helpText: 'Image generation model', + required: false, + } + ]; + + return ( + + ); +}; + +export default GenerateImageAction; diff --git a/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx b/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx index fab6ab0..7823ace 100644 --- a/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx +++ b/webui/react-ui/src/components/actions/GithubIssueCloserAction.jsx @@ -1,61 +1,57 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * GitHub Issue Closer action component */ const GithubIssueCloserAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for GitHub Issue Closer action + const fields = [ + { + name: 'token', + label: 'GitHub Token', + type: 'text', + defaultValue: '', + placeholder: 'ghp_...', + helpText: 'Personal access token with repo scope', + required: true, + }, + { + name: 'owner', + label: 'Repository Owner', + type: 'text', + defaultValue: '', + placeholder: 'username or organization', + helpText: '', + required: true, + }, + { + name: 'repository', + label: 'Repository Name', + type: 'text', + defaultValue: '', + placeholder: 'repository-name', + helpText: '', + required: true, + }, + { + name: 'customActionName', + label: 'Custom Action Name (Optional)', + type: 'text', + defaultValue: '', + placeholder: 'close_github_issue', + helpText: 'Custom name for this action (optional)', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="ghp_..." - /> - Personal access token with repo scope -
- -
- - onActionConfigChange('owner', e.target.value)} - className="form-control" - placeholder="username or organization" - /> -
- -
- - onActionConfigChange('repository', e.target.value)} - className="form-control" - placeholder="repository-name" - /> -
- -
- - onActionConfigChange('customActionName', e.target.value)} - className="form-control" - placeholder="close_github_issue" - /> - Custom name for this action (optional) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx b/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx index e179bc2..4470fa7 100644 --- a/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx +++ b/webui/react-ui/src/components/actions/GithubIssueCommenterAction.jsx @@ -1,61 +1,57 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * GitHub Issue Commenter action component */ const GithubIssueCommenterAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for GitHub Issue Commenter action + const fields = [ + { + name: 'token', + label: 'GitHub Token', + type: 'text', + defaultValue: '', + placeholder: 'ghp_...', + helpText: 'Personal access token with repo scope', + required: true, + }, + { + name: 'owner', + label: 'Repository Owner', + type: 'text', + defaultValue: '', + placeholder: 'username or organization', + helpText: 'Owner of the repository', + required: true, + }, + { + name: 'repository', + label: 'Repository Name', + type: 'text', + defaultValue: '', + placeholder: 'repository-name', + helpText: 'Name of the repository', + required: true, + }, + { + name: 'customActionName', + label: 'Custom Action Name (Optional)', + type: 'text', + defaultValue: '', + placeholder: 'comment_on_github_issue', + helpText: 'Custom name for this action (optional)', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="ghp_..." - /> - Personal access token with repo scope -
- -
- - onActionConfigChange('owner', e.target.value)} - className="form-control" - placeholder="username or organization" - /> -
- -
- - onActionConfigChange('repository', e.target.value)} - className="form-control" - placeholder="repository-name" - /> -
- -
- - onActionConfigChange('customActionName', e.target.value)} - className="form-control" - placeholder="comment_on_github_issue" - /> - Custom name for this action (optional) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx b/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx index aea6dfd..aec3cbe 100644 --- a/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx +++ b/webui/react-ui/src/components/actions/GithubIssueLabelerAction.jsx @@ -1,74 +1,66 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * GitHub Issue Labeler action component */ const GithubIssueLabelerAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for GitHub Issue Labeler action + const fields = [ + { + name: 'token', + label: 'GitHub Token', + type: 'text', + defaultValue: '', + placeholder: 'ghp_...', + helpText: 'Personal access token with repo scope', + required: true, + }, + { + name: 'owner', + label: 'Repository Owner', + type: 'text', + defaultValue: '', + placeholder: 'username or organization', + helpText: '', + required: true, + }, + { + name: 'repository', + label: 'Repository Name', + type: 'text', + defaultValue: '', + placeholder: 'repository-name', + helpText: '', + required: true, + }, + { + name: 'availableLabels', + label: 'Available Labels', + type: 'text', + defaultValue: 'bug,enhancement', + placeholder: 'bug,enhancement,documentation', + helpText: 'Comma-separated list of available labels', + required: true, + }, + { + name: 'customActionName', + label: 'Custom Action Name (Optional)', + type: 'text', + defaultValue: '', + placeholder: 'add_label_to_issue', + helpText: 'Custom name for this action (optional)', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="ghp_..." - /> - Personal access token with repo scope -
- -
- - onActionConfigChange('owner', e.target.value)} - className="form-control" - placeholder="username or organization" - /> -
- -
- - onActionConfigChange('repository', e.target.value)} - className="form-control" - placeholder="repository-name" - /> -
- -
- - onActionConfigChange('availableLabels', e.target.value)} - className="form-control" - placeholder="bug,enhancement,documentation" - /> - Comma-separated list of available labels -
- -
- - onActionConfigChange('customActionName', e.target.value)} - className="form-control" - placeholder="add_label_to_issue" - /> - Custom name for this action (optional) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx b/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx index ccd73b5..57c93ab 100644 --- a/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx +++ b/webui/react-ui/src/components/actions/GithubIssueOpenerAction.jsx @@ -1,61 +1,57 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * GitHub Issue Opener action component */ const GithubIssueOpenerAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for GitHub Issue Opener action + const fields = [ + { + name: 'token', + label: 'GitHub Token', + type: 'text', + defaultValue: '', + placeholder: 'ghp_...', + helpText: 'Personal access token with repo scope', + required: true, + }, + { + name: 'owner', + label: 'Repository Owner', + type: 'text', + defaultValue: '', + placeholder: 'username or organization', + helpText: '', + required: true, + }, + { + name: 'repository', + label: 'Repository Name', + type: 'text', + defaultValue: '', + placeholder: 'repository-name', + helpText: '', + required: true, + }, + { + name: 'customActionName', + label: 'Custom Action Name (Optional)', + type: 'text', + defaultValue: '', + placeholder: 'open_github_issue', + helpText: 'Custom name for this action (optional)', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="ghp_..." - /> - Personal access token with repo scope -
- -
- - onActionConfigChange('owner', e.target.value)} - className="form-control" - placeholder="username or organization" - /> -
- -
- - onActionConfigChange('repository', e.target.value)} - className="form-control" - placeholder="repository-name" - /> -
- -
- - onActionConfigChange('customActionName', e.target.value)} - className="form-control" - placeholder="open_github_issue" - /> - Custom name for this action (optional) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx b/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx index f1f0a8a..bf94d76 100644 --- a/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx +++ b/webui/react-ui/src/components/actions/GithubRepositoryAction.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * GitHub Repository action component for repository-related actions @@ -8,58 +9,53 @@ import React from 'react'; * - github-readme */ const GithubRepositoryAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for GitHub Repository action + const fields = [ + { + name: 'token', + label: 'GitHub Token', + type: 'text', + defaultValue: '', + placeholder: 'ghp_...', + helpText: 'Personal access token with repo scope', + required: true, + }, + { + name: 'owner', + label: 'Repository Owner', + type: 'text', + defaultValue: '', + placeholder: 'username or organization', + helpText: '', + required: true, + }, + { + name: 'repository', + label: 'Repository Name', + type: 'text', + defaultValue: '', + placeholder: 'repository-name', + helpText: '', + required: true, + }, + { + name: 'customActionName', + label: 'Custom Action Name (Optional)', + type: 'text', + defaultValue: '', + placeholder: 'github_repo_action', + helpText: 'Custom name for this action (optional)', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="ghp_..." - /> - Personal access token with repo scope -
- -
- - onActionConfigChange('owner', e.target.value)} - className="form-control" - placeholder="username or organization" - /> -
- -
- - onActionConfigChange('repository', e.target.value)} - className="form-control" - placeholder="repository-name" - /> -
- -
- - onActionConfigChange('customActionName', e.target.value)} - className="form-control" - placeholder="github_repo_action" - /> - Custom name for this action (optional) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/SendMailAction.jsx b/webui/react-ui/src/components/actions/SendMailAction.jsx index 2a9f461..0f6b192 100644 --- a/webui/react-ui/src/components/actions/SendMailAction.jsx +++ b/webui/react-ui/src/components/actions/SendMailAction.jsx @@ -1,74 +1,66 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * SendMail action component */ const SendMailAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for SendMail action + const fields = [ + { + name: 'email', + label: 'Email', + type: 'email', + defaultValue: '', + placeholder: 'your-email@example.com', + helpText: 'Email address to send from', + required: true, + }, + { + name: 'username', + label: 'Username', + type: 'text', + defaultValue: '', + placeholder: 'SMTP username (often same as email)', + helpText: '', + required: true, + }, + { + name: 'password', + label: 'Password', + type: 'password', + defaultValue: '', + placeholder: 'SMTP password or app password', + helpText: 'For Gmail, use an app password', + required: true, + }, + { + name: 'smtpHost', + label: 'SMTP Host', + type: 'text', + defaultValue: '', + placeholder: 'smtp.gmail.com', + helpText: '', + required: true, + }, + { + name: 'smtpPort', + label: 'SMTP Port', + type: 'text', + defaultValue: '587', + placeholder: '587', + helpText: 'Common ports: 587 (TLS), 465 (SSL)', + required: true, + }, + ]; + return ( -
-
- - onActionConfigChange('email', e.target.value)} - className="form-control" - placeholder="your-email@example.com" - /> - Email address to send from -
- -
- - onActionConfigChange('username', e.target.value)} - className="form-control" - placeholder="SMTP username (often same as email)" - /> -
- -
- - onActionConfigChange('password', e.target.value)} - className="form-control" - placeholder="SMTP password or app password" - /> - For Gmail, use an app password -
- -
- - onActionConfigChange('smtpHost', e.target.value)} - className="form-control" - placeholder="smtp.gmail.com" - /> -
- -
- - onActionConfigChange('smtpPort', e.target.value)} - className="form-control" - placeholder="587" - /> - Common ports: 587 (TLS), 465 (SSL) -
-
+ ); }; diff --git a/webui/react-ui/src/components/actions/TwitterPostAction.jsx b/webui/react-ui/src/components/actions/TwitterPostAction.jsx index 74bd733..9bfa061 100644 --- a/webui/react-ui/src/components/actions/TwitterPostAction.jsx +++ b/webui/react-ui/src/components/actions/TwitterPostAction.jsx @@ -1,40 +1,38 @@ import React from 'react'; +import BaseAction from './BaseAction'; /** * Twitter Post action component */ const TwitterPostAction = ({ index, onActionConfigChange, getConfigValue }) => { + // Field definitions for Twitter Post action + const fields = [ + { + name: 'token', + label: 'Twitter API Token', + type: 'text', + defaultValue: '', + placeholder: 'Twitter API token', + helpText: 'Twitter API token with posting permissions', + required: true, + }, + { + name: 'noCharacterLimits', + label: 'Disable character limit (280 characters)', + type: 'checkbox', + defaultValue: 'false', + helpText: 'Enable to bypass the 280 character limit check', + required: false, + }, + ]; + return ( -
-
- - onActionConfigChange('token', e.target.value)} - className="form-control" - placeholder="Twitter API token" - /> - Twitter API token with posting permissions -
- -
-
- onActionConfigChange('noCharacterLimits', e.target.checked ? 'true' : 'false')} - className="form-check-input" - /> - - Enable to bypass the 280 character limit check -
-
-
+ ); }; diff --git a/webui/react-ui/src/components/common/FormField.jsx b/webui/react-ui/src/components/common/FormField.jsx new file mode 100644 index 0000000..f5736d1 --- /dev/null +++ b/webui/react-ui/src/components/common/FormField.jsx @@ -0,0 +1,107 @@ +import React from 'react'; + +/** + * Reusable form field component that handles different input types + * + * @param {Object} props Component props + * @param {string} props.id Unique identifier for the input + * @param {string} props.label Label text for the field + * @param {string} props.type Input type (text, checkbox, select, textarea) + * @param {string|boolean} props.value Current value of the field + * @param {Function} props.onChange Handler for value changes + * @param {string} props.placeholder Placeholder text + * @param {string} props.helpText Help text to display below the field + * @param {Array} props.options Options for select inputs + * @param {boolean} props.required Whether the field is required + */ +const FormField = ({ + id, + label, + type = 'text', + value, + onChange, + placeholder = '', + helpText = '', + options = [], + required = false, +}) => { + // Render different input types + const renderInput = () => { + switch (type) { + case 'checkbox': + return ( +
+ + {helpText && {helpText}} +
+ ); + case 'select': + return ( + <> + + + {helpText && {helpText}} + + ); + case 'textarea': + return ( + <> + +