fix exporting models

This commit is contained in:
ryanwong
2024-11-15 05:05:25 -05:00
parent a090d6ffb4
commit e636f4e181
4 changed files with 275 additions and 57 deletions
+130 -33
View File
@@ -1,12 +1,20 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { X, Plus, Trash } from 'lucide-react'; import { X, Plus, Trash } from "lucide-react";
import { useFlowStore } from '../store/flowStore'; import { useFlowStore } from "../store/flowStore";
interface Field { interface Field {
name: string; name: string;
type: string; type: string;
defaultValue: string; defaultValue: string;
validation: string; validation: string;
validationOptions?: {
pattern?: string;
enum?: string[];
min?: number;
max?: number;
minLength?: number;
maxLength?: number;
};
mapping?: string; mapping?: string;
} }
@@ -21,35 +29,53 @@ interface ModelModalProps {
} }
const fieldTypes = [ const fieldTypes = [
'string', "primary key",
'number', "string",
'boolean', "long text",
'date', "integer",
'mapping', "double",
'array', "big number",
'object', "boolean",
"date",
"datetime",
"uuid",
"json",
"mapping",
]; ];
const validationRules = [ const validationRules = [
'required', "required",
'email', "email",
'url', "url",
'min', "min",
'max', "max",
'pattern', "pattern",
'enum', "enum",
"length",
"minLength",
"maxLength",
"positive",
"negative",
"integer",
"decimal",
"alphanumeric",
"uuid",
"json",
"date",
"phone",
]; ];
const initialNewField = { const initialNewField: Field = {
name: '', name: "",
type: 'string', type: "string",
defaultValue: '', defaultValue: "",
validation: '', validation: "",
validationOptions: {},
}; };
const createInitialModelData = () => ({ const createInitialModelData = () => ({
id: `model_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, id: `model_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
name: '', name: "",
fields: [], fields: [],
}); });
@@ -108,12 +134,9 @@ export function ModelModal({ isOpen, onClose, model }: ModelModalProps) {
<div className="bg-white rounded-lg w-full max-w-2xl max-h-[80vh] overflow-hidden"> <div className="bg-white rounded-lg w-full max-w-2xl max-h-[80vh] overflow-hidden">
<div className="p-4 border-b border-gray-200 flex justify-between items-center"> <div className="p-4 border-b border-gray-200 flex justify-between items-center">
<h2 className="text-lg font-semibold"> <h2 className="text-lg font-semibold">
{model ? 'Edit Model' : 'Add New Model'} {model ? "Edit Model" : "Add New Model"}
</h2> </h2>
<button <button onClick={onClose} className="p-1 hover:bg-gray-100 rounded">
onClick={onClose}
className="p-1 hover:bg-gray-100 rounded"
>
<X className="w-5 h-5" /> <X className="w-5 h-5" />
</button> </button>
</div> </div>
@@ -124,7 +147,9 @@ export function ModelModal({ isOpen, onClose, model }: ModelModalProps) {
<input <input
type="text" type="text"
value={modelData.name} value={modelData.name}
onChange={(e) => setModelData({ ...modelData, name: e.target.value })} onChange={(e) =>
setModelData({ ...modelData, name: e.target.value })
}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
placeholder="Enter model name" placeholder="Enter model name"
/> />
@@ -211,10 +236,10 @@ export function ModelModal({ isOpen, onClose, model }: ModelModalProps) {
</select> </select>
</div> </div>
{newField.type === 'mapping' && ( {newField.type === "mapping" && (
<input <input
type="text" type="text"
value={newField.mapping || ''} value={newField.mapping || ""}
onChange={(e) => onChange={(e) =>
setNewField({ ...newField, mapping: e.target.value }) setNewField({ ...newField, mapping: e.target.value })
} }
@@ -223,6 +248,78 @@ export function ModelModal({ isOpen, onClose, model }: ModelModalProps) {
/> />
)} )}
{newField.validation &&
[
"pattern",
"enum",
"min",
"max",
"minLength",
"maxLength",
].includes(newField.validation) && (
<div className="flex gap-2">
{newField.validation === "pattern" && (
<input
type="text"
value={newField.validationOptions?.pattern || ""}
onChange={(e) =>
setNewField({
...newField,
validationOptions: {
...newField.validationOptions,
pattern: e.target.value,
},
})
}
className="flex-1 p-2 border rounded"
placeholder="Regular expression pattern"
/>
)}
{newField.validation === "enum" && (
<input
type="text"
value={
newField.validationOptions?.enum?.join(",") || ""
}
onChange={(e) =>
setNewField({
...newField,
validationOptions: {
...newField.validationOptions,
enum: e.target.value.split(","),
},
})
}
className="flex-1 p-2 border rounded"
placeholder="Comma-separated values"
/>
)}
{["min", "max", "minLength", "maxLength"].includes(
newField.validation
) && (
<input
type="number"
value={
newField.validationOptions?.[
newField.validation as keyof typeof newField.validationOptions
] || ""
}
onChange={(e) =>
setNewField({
...newField,
validationOptions: {
...newField.validationOptions,
[newField.validation]: parseFloat(e.target.value),
},
})
}
className="flex-1 p-2 border rounded"
placeholder={`Enter ${newField.validation} value`}
/>
)}
</div>
)}
<button <button
onClick={handleAddField} onClick={handleAddField}
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors"
@@ -245,10 +342,10 @@ export function ModelModal({ isOpen, onClose, model }: ModelModalProps) {
onClick={handleSave} onClick={handleSave}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
> >
{model ? 'Update' : 'Save'} Model {model ? "Update" : "Save"} Model
</button> </button>
</div> </div>
</div> </div>
</div> </div>
); );
} }
+8 -7
View File
@@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { Plus, Edit2 } from 'lucide-react'; import { Plus, Edit2 } from "lucide-react";
import { useFlowStore } from '../store/flowStore'; import { useFlowStore } from "../store/flowStore";
import { ModelModal } from './ModelModal'; import { ModelModal } from "./ModelModal";
export function ModelPanel() { export function ModelPanel() {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -36,10 +36,11 @@ export function ModelPanel() {
<div> <div>
<h3 className="font-medium">{model.name}</h3> <h3 className="font-medium">{model.name}</h3>
<p className="text-sm text-gray-500"> <p className="text-sm text-gray-500">
{model.fields.length} field{model.fields.length !== 1 ? 's' : ''} {model.fields.length} field
{model.fields.length !== 1 ? "s" : ""}
</p> </p>
</div> </div>
<button <button
className="p-1 hover:bg-gray-100 rounded" className="p-1 hover:bg-gray-100 rounded"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@@ -63,4 +64,4 @@ export function ModelPanel() {
/> />
</div> </div>
); );
} }
+52 -17
View File
@@ -1,11 +1,14 @@
import React from 'react'; import React from "react";
import { Save } from 'lucide-react'; import { Save } from "lucide-react";
import { useFlowStore } from '../store/flowStore'; import { useFlowStore } from "../store/flowStore";
import { TranslationService } from "../services/TranslationService";
export function SettingsForm() { export function SettingsForm() {
const { settings, updateSettings } = useFlowStore(); const { settings, updateSettings } = useFlowStore();
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
) => {
updateSettings({ updateSettings({
...settings, ...settings,
[e.target.name]: e.target.value, [e.target.name]: e.target.value,
@@ -16,6 +19,25 @@ export function SettingsForm() {
updateSettings(settings); updateSettings(settings);
}; };
const handleExportConfiguration = () => {
const models = useFlowStore.getState().models;
const configuration = {
models: models.map((model) => TranslationService.translateModel(model)),
};
const blob = new Blob([JSON.stringify(configuration, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "model-configuration.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
@@ -23,7 +45,7 @@ export function SettingsForm() {
<input <input
type="text" type="text"
name="globalKey" name="globalKey"
value={settings?.globalKey || ''} value={settings?.globalKey || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
placeholder="Enter global key" placeholder="Enter global key"
@@ -34,7 +56,7 @@ export function SettingsForm() {
<label className="block text-sm font-medium mb-1">Database Type</label> <label className="block text-sm font-medium mb-1">Database Type</label>
<select <select
name="databaseType" name="databaseType"
value={settings?.databaseType || ''} value={settings?.databaseType || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
> >
@@ -47,10 +69,12 @@ export function SettingsForm() {
</div> </div>
<div> <div>
<label className="block text-sm font-medium mb-1">Authentication Type</label> <label className="block text-sm font-medium mb-1">
Authentication Type
</label>
<select <select
name="authType" name="authType"
value={settings?.authType || ''} value={settings?.authType || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
> >
@@ -63,7 +87,7 @@ export function SettingsForm() {
<label className="block text-sm font-medium mb-1">Timezone</label> <label className="block text-sm font-medium mb-1">Timezone</label>
<select <select
name="timezone" name="timezone"
value={settings?.timezone || ''} value={settings?.timezone || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
> >
@@ -75,13 +99,15 @@ export function SettingsForm() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<label className="block text-sm font-medium">Database Credentials</label> <label className="block text-sm font-medium">
Database Credentials
</label>
<div> <div>
<input <input
type="text" type="text"
name="dbHost" name="dbHost"
value={settings?.dbHost || ''} value={settings?.dbHost || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded mb-2" className="w-full p-2 border rounded mb-2"
placeholder="Host" placeholder="Host"
@@ -92,7 +118,7 @@ export function SettingsForm() {
<input <input
type="text" type="text"
name="dbPort" name="dbPort"
value={settings?.dbPort || ''} value={settings?.dbPort || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded mb-2" className="w-full p-2 border rounded mb-2"
placeholder="Port" placeholder="Port"
@@ -103,7 +129,7 @@ export function SettingsForm() {
<input <input
type="text" type="text"
name="dbUser" name="dbUser"
value={settings?.dbUser || ''} value={settings?.dbUser || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded mb-2" className="w-full p-2 border rounded mb-2"
placeholder="Username" placeholder="Username"
@@ -114,7 +140,7 @@ export function SettingsForm() {
<input <input
type="password" type="password"
name="dbPassword" name="dbPassword"
value={settings?.dbPassword || ''} value={settings?.dbPassword || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded mb-2" className="w-full p-2 border rounded mb-2"
placeholder="Password" placeholder="Password"
@@ -125,7 +151,7 @@ export function SettingsForm() {
<input <input
type="text" type="text"
name="dbName" name="dbName"
value={settings?.dbName || ''} value={settings?.dbName || ""}
onChange={handleChange} onChange={handleChange}
className="w-full p-2 border rounded" className="w-full p-2 border rounded"
placeholder="Database Name" placeholder="Database Name"
@@ -140,6 +166,15 @@ export function SettingsForm() {
<Save className="w-4 h-4" /> <Save className="w-4 h-4" />
Save Settings Save Settings
</button> </button>
<div className="mt-6">
<button
onClick={handleExportConfiguration}
className="w-full px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition-colors"
>
Export Configuration
</button>
</div>
</div> </div>
); );
} }
+85
View File
@@ -0,0 +1,85 @@
interface ModelData {
id: string;
name: string;
fields: Field[];
}
interface Field {
name: string;
type: string;
defaultValue: string;
validation: string;
validationOptions?: {
pattern?: string;
enum?: string[];
min?: number;
max?: number;
minLength?: number;
maxLength?: number;
};
mapping?: string;
}
export class TranslationService {
static translateModel(model: ModelData): any {
return {
name: model.name,
fields: model.fields.map((field) => ({
name: field.name,
type: this.translateFieldType(field.type),
isPrimaryKey: field.type === "primary key",
validation: this.translateValidation(field),
defaultValue: field.defaultValue || null,
...(field.mapping ? { mapping: this.parseMapping(field.mapping) } : {}),
})),
};
}
private static translateFieldType(type: string): string {
if (type === "primary key") {
return "INTEGER";
}
const typeMap: { [key: string]: string } = {
string: "STRING",
"long text": "TEXT",
integer: "INTEGER",
double: "DOUBLE",
"big number": "BIGINT",
boolean: "BOOLEAN",
date: "DATE",
datetime: "DATETIME",
uuid: "UUID",
json: "JSON",
mapping: "MAPPING",
};
return typeMap[type] || type.toUpperCase();
}
private static translateValidation(field: Field): any {
if (!field.validation) return null;
const validation: any = {
type: field.validation,
};
if (field.validationOptions) {
Object.assign(validation, field.validationOptions);
}
return validation;
}
private static parseMapping(mapping: string): Record<string, string> {
try {
return Object.fromEntries(
mapping.split(",").map((pair) => {
const [key, value] = pair.split(":");
return [key.trim(), value.trim()];
})
);
} catch (e) {
return {};
}
}
}