Mirror of https://github.com/roostorg/osprey
github.com/roostorg/osprey
1import React, { useState } from 'react';
2import { Modal, Form, Input, Select, Upload, Button } from 'antd';
3import { UploadOutlined } from '@ant-design/icons';
4import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
5import type { FormInstance } from 'antd/es/form';
6import styles from './BulkActionStartModal.module.css';
7import { max, set } from 'lodash';
8import useBulkActionStore from '../../stores/BulkActionStore';
9
10const { TextArea } = Input;
11const { Option } = Select;
12
13interface WorkflowOption {
14 value: string;
15 label: string;
16}
17
18interface JobFormValues {
19 jobName: string;
20 jobDescription: string;
21 workflow: string;
22 customWorkflowInput?: string;
23 file: File;
24 entityType: string;
25}
26
27interface JobUploadModalProps {
28 visible: boolean;
29 onCancel: () => void;
30 onSubmit: (values: JobFormValues) => void;
31 initialValues?: Partial<JobFormValues>;
32}
33
34const workflowOptions: WorkflowOption[] = [
35 { value: 'workflow1', label: 'Test Workflow' },
36 { value: 'custom', label: 'Custom Workflow' },
37];
38
39const BulkActionStartModal: React.FC<JobUploadModalProps> = ({ visible, onCancel, onSubmit, initialValues }) => {
40 const [form] = Form.useForm<JobFormValues>();
41 const [customWorkflow, setCustomWorkflow] = useState<boolean>(false);
42 const [fileSelected, setFileSelected] = useState<File | null>(null);
43
44 const handleWorkflowChange = (value: string): void => {
45 setCustomWorkflow(value === 'custom');
46 };
47
48 const handleSubmit = async (): Promise<void> => {
49 try {
50 const values: JobFormValues = (await form.validateFields()) as JobFormValues;
51 if (fileSelected != null) {
52 values.file = fileSelected;
53 }
54 onSubmit(values as JobFormValues);
55
56 form.resetFields();
57 setFileSelected(null);
58 setCustomWorkflow(false);
59 } catch (error) {
60 console.error('Validation failed:', error);
61 }
62 };
63
64 const normFile = (e: any): UploadFile[] => {
65 if (Array.isArray(e)) {
66 return e;
67 }
68 return e?.fileList || [];
69 };
70
71 // Custom validation rules
72 const validationRules = {
73 jobName: [
74 { required: true, message: 'Please enter job name' },
75 { max: 100, message: 'Job name cannot exceed 100 characters' },
76 ],
77 jobDescription: [
78 { required: true, message: 'Please enter job description' },
79 { max: 500, message: 'Job description cannot exceed 500 characters' },
80 ],
81 workflow: [{ required: true, message: 'Please select or enter workflow' }],
82 customWorkflowInput: [
83 { required: true, message: 'Please enter custom workflow' },
84 { max: 100, message: 'Custom workflow cannot exceed 100 characters' },
85 ],
86 file: [],
87 entityType: [{ required: true, message: 'Please select entity type' }],
88 };
89
90 // File upload configuration
91 const uploadProps: UploadProps = {
92 beforeUpload: (file) => {
93 // ~10 million rows of bigint entity-ids in a CSV file would be around 250MB
94 const isValidSize = file.size ? file.size / 1024 / 1024 / 1024 < 250 : false; // 250MB limit
95 if (!isValidSize) {
96 Modal.error({
97 title: 'File too large',
98 content: 'File size must be less than 250MB',
99 });
100 }
101 setFileSelected(file);
102 return false; // Prevent automatic upload
103 },
104 accept: '.csv', // Restrict file types,
105 multiple: false,
106 showUploadList: false,
107 type: 'drag',
108 };
109
110 return (
111 <Modal
112 title="Create New Job"
113 visible={visible}
114 onCancel={() => {
115 form.resetFields();
116 setFileSelected(null);
117 setCustomWorkflow(false);
118 onCancel();
119 }}
120 onOk={handleSubmit}
121 width={600}
122 className={styles.modal}
123 >
124 <Form<JobFormValues> form={form} layout="vertical" requiredMark="optional" initialValues={initialValues}>
125 <Form.Item name="jobName" label="Job Name" rules={validationRules.jobName}>
126 <Input placeholder="Enter job name" />
127 </Form.Item>
128
129 <Form.Item name="jobDescription" label="Job Description" rules={validationRules.jobDescription}>
130 <TextArea placeholder="Enter job description" rows={4} />
131 </Form.Item>
132
133 <Form.Item name="entityType" label="Entity Type" rules={validationRules.entityType}>
134 <Select placeholder="Select entity type">
135 <Option value="user">User</Option>
136 <Option value="guild">Guild</Option>
137 </Select>
138 </Form.Item>
139
140 <Form.Item name="workflow" label="Workflow" rules={validationRules.workflow}>
141 <Select onChange={handleWorkflowChange} placeholder="Select workflow">
142 {workflowOptions.map((option) => (
143 <Option key={option.value} value={option.value}>
144 {option.label}
145 </Option>
146 ))}
147 </Select>
148 </Form.Item>
149
150 {customWorkflow && (
151 <Form.Item name="customWorkflowInput" label="Custom Workflow" rules={validationRules.customWorkflowInput}>
152 <Input placeholder="Enter custom workflow" />
153 </Form.Item>
154 )}
155
156 <Form.Item name="file" label="Upload File" getValueFromEvent={normFile} rules={validationRules.file}>
157 <Upload {...uploadProps}>
158 <Button icon={<UploadOutlined />}>Click to Upload</Button>
159 </Upload>
160 <div>{fileSelected?.name}</div>
161 </Form.Item>
162 </Form>
163 </Modal>
164 );
165};
166
167const BulkActionStartModalContainer = () => {
168 const [visible, setVisible] = useState(false);
169 const bulkActions = useBulkActionStore();
170
171 const handleOpenModal = () => {
172 setVisible(true);
173 };
174
175 const handleCloseModal = () => {
176 setVisible(false);
177 };
178
179 const handleSubmit = async (values: JobFormValues) => {
180 const workflow = values.workflow === 'custom' ? values.customWorkflowInput : values.workflow;
181 if (!workflow) {
182 Modal.error({
183 title: 'Workflow is required',
184 content: 'Please select or enter a workflow',
185 });
186 return;
187 }
188 await bulkActions.startBulkAction({
189 job_name: values.jobName,
190 job_description: values.jobDescription,
191 workflow_name: workflow,
192 file_name: values.file.name,
193 file: values.file,
194 entity_type: values.entityType,
195 });
196
197 setVisible(false);
198
199 await bulkActions.getJobs();
200 };
201
202 return (
203 <>
204 <Button type="primary" onClick={handleOpenModal}>
205 Create New Job
206 </Button>
207 <BulkActionStartModal visible={visible} onCancel={handleCloseModal} onSubmit={handleSubmit} />
208 </>
209 );
210};
211
212export default BulkActionStartModalContainer;