expanded functionality of the form builder
This commit is contained in:
parent
5a2db8e040
commit
ea8d310299
|
@ -2549,4 +2549,4 @@
|
||||||
"clientPolicies" : {
|
"clientPolicies" : {
|
||||||
"policies" : [ ]
|
"policies" : [ ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,8 +84,10 @@ class GitService:
|
||||||
repo_path_to_use = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"]
|
repo_path_to_use = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"]
|
||||||
if repo_path_to_use is None:
|
if repo_path_to_use is None:
|
||||||
raise ConfigurationError("BPMN_SPEC_ABSOLUTE_DIR config must be set")
|
raise ConfigurationError("BPMN_SPEC_ABSOLUTE_DIR config must be set")
|
||||||
if current_app.config['GIT_SSH_PRIVATE_KEY']:
|
if current_app.config["GIT_SSH_PRIVATE_KEY"]:
|
||||||
os.environ['GIT_SSH_PRIVATE_KEY'] = current_app.config['GIT_SSH_PRIVATE_KEY']
|
os.environ["GIT_SSH_PRIVATE_KEY"] = current_app.config[
|
||||||
|
"GIT_SSH_PRIVATE_KEY"
|
||||||
|
]
|
||||||
|
|
||||||
git_username = ""
|
git_username = ""
|
||||||
git_email = ""
|
git_email = ""
|
||||||
|
@ -222,7 +224,7 @@ class GitService:
|
||||||
destination_process_root = f"/tmp/{clone_dir}" # noqa
|
destination_process_root = f"/tmp/{clone_dir}" # noqa
|
||||||
|
|
||||||
git_clone_url = current_app.config["GIT_CLONE_URL_FOR_PUBLISHING"]
|
git_clone_url = current_app.config["GIT_CLONE_URL_FOR_PUBLISHING"]
|
||||||
if git_clone_url.startswith('https://'):
|
if git_clone_url.startswith("https://"):
|
||||||
git_clone_url = git_clone_url.replace(
|
git_clone_url = git_clone_url.replace(
|
||||||
"https://",
|
"https://",
|
||||||
f"https://{current_app.config['GIT_USERNAME']}:{current_app.config['GIT_USER_PASSWORD']}@",
|
f"https://{current_app.config['GIT_USERNAME']}:{current_app.config['GIT_USER_PASSWORD']}@",
|
||||||
|
|
|
@ -228,4 +228,13 @@ export interface FormField {
|
||||||
required: boolean;
|
required: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
enum: string[];
|
enum: string[];
|
||||||
|
default: any;
|
||||||
|
pattern: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JsonSchemaForm {
|
||||||
|
file_contents: string;
|
||||||
|
name: string;
|
||||||
|
process_model_id: string;
|
||||||
|
required: string[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Button, Select, SelectItem, TextInput } from '@carbon/react';
|
import { Button, Select, SelectItem, TextInput } from '@carbon/react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { FormField } from '../interfaces';
|
import { FormField, JsonSchemaForm } from '../interfaces';
|
||||||
import {
|
import {
|
||||||
modifyProcessIdentifierForPathParam,
|
modifyProcessIdentifierForPathParam,
|
||||||
slugifyString,
|
slugifyString,
|
||||||
underscorizeString,
|
underscorizeString,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
|
import { Notification } from '../components/Notification';
|
||||||
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
|
|
||||||
export default function JsonSchemaFormBuilder() {
|
export default function JsonSchemaFormBuilder() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
const formFieldTypes = ['textbox', 'checkbox', 'select'];
|
const formFieldTypes = ['textbox', 'checkbox', 'select'];
|
||||||
|
|
||||||
const [formTitle, setFormTitle] = useState<string>('');
|
const [formTitle, setFormTitle] = useState<string>('');
|
||||||
|
@ -31,34 +36,90 @@ export default function JsonSchemaFormBuilder() {
|
||||||
const [formFieldId, setFormFieldId] = useState<string>('');
|
const [formFieldId, setFormFieldId] = useState<string>('');
|
||||||
const [formFieldTitle, setFormFieldTitle] = useState<string>('');
|
const [formFieldTitle, setFormFieldTitle] = useState<string>('');
|
||||||
const [formFieldType, setFormFieldType] = useState<string>('');
|
const [formFieldType, setFormFieldType] = useState<string>('');
|
||||||
|
const [requiredFields, setRequiredFields] = useState<string[]>([]);
|
||||||
|
const [savedJsonSchema, setSavedJsonSchema] = useState<boolean>(false);
|
||||||
|
|
||||||
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
||||||
`${params.process_model_id}`
|
`${params.process_model_id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {}, []);
|
useEffect(() => {
|
||||||
|
const processResult = (result: JsonSchemaForm) => {
|
||||||
|
const jsonForm = JSON.parse(result.file_contents);
|
||||||
|
setFormTitle(jsonForm.title);
|
||||||
|
setFormDescription(jsonForm.description);
|
||||||
|
setRequiredFields(jsonForm.required);
|
||||||
|
const newFormId = (searchParams.get('file_name') || '').replace(
|
||||||
|
'-schema.json',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
setFormId(newFormId);
|
||||||
|
const newFormFields: FormField[] = [];
|
||||||
|
Object.keys(jsonForm.properties).forEach((propertyId: string) => {
|
||||||
|
const propertyDetails = jsonForm.properties[propertyId];
|
||||||
|
newFormFields.push({
|
||||||
|
id: propertyId,
|
||||||
|
title: propertyDetails.title,
|
||||||
|
required: propertyDetails.required,
|
||||||
|
type: propertyDetails.type,
|
||||||
|
enum: propertyDetails.enum,
|
||||||
|
default: propertyDetails.default,
|
||||||
|
pattern: propertyDetails.pattern,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setFormFields(newFormFields);
|
||||||
|
};
|
||||||
|
if (searchParams.get('file_name')) {
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/process-models/${modifiedProcessModelId}/files/${searchParams.get(
|
||||||
|
'file_name'
|
||||||
|
)}`,
|
||||||
|
successCallback: processResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [modifiedProcessModelId, searchParams]);
|
||||||
|
|
||||||
|
const formSubmitResultElement = () => {
|
||||||
|
if (savedJsonSchema) {
|
||||||
|
return (
|
||||||
|
<Notification
|
||||||
|
title="Form Saved"
|
||||||
|
onClose={() => setSavedJsonSchema(false)}
|
||||||
|
>
|
||||||
|
It saved
|
||||||
|
</Notification>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const renderFormJson = () => {
|
const renderFormJson = () => {
|
||||||
const formJson = {
|
const formJson = {
|
||||||
title: formTitle,
|
title: formTitle,
|
||||||
description: formDescription,
|
description: formDescription,
|
||||||
properties: {},
|
properties: {},
|
||||||
required: [],
|
required: requiredFields,
|
||||||
};
|
};
|
||||||
|
|
||||||
formFields.forEach((formField: FormField) => {
|
formFields.forEach((formField: FormField) => {
|
||||||
let jsonSchemaFieldType = 'string';
|
let jsonSchemaFieldType = formField.type;
|
||||||
if (formField.type === 'checkbox') {
|
if (['checkbox'].includes(formField.type)) {
|
||||||
jsonSchemaFieldType = 'boolean';
|
jsonSchemaFieldType = 'boolean';
|
||||||
}
|
}
|
||||||
const formJsonObject: any = {
|
const formJsonObject: any = {
|
||||||
type: jsonSchemaFieldType,
|
type: jsonSchemaFieldType || 'string',
|
||||||
title: formField.title,
|
title: formField.title,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (formField.type === 'select') {
|
if (formField.enum) {
|
||||||
formJsonObject.enum = formField.enum;
|
formJsonObject.enum = formField.enum;
|
||||||
}
|
}
|
||||||
|
if (formField.default !== undefined) {
|
||||||
|
formJsonObject.default = formField.default;
|
||||||
|
}
|
||||||
|
if (formField.pattern) {
|
||||||
|
formJsonObject.pattern = formField.pattern;
|
||||||
|
}
|
||||||
(formJson.properties as any)[formField.id] = formJsonObject;
|
(formJson.properties as any)[formField.id] = formJsonObject;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -93,6 +154,8 @@ export default function JsonSchemaFormBuilder() {
|
||||||
required: false,
|
required: false,
|
||||||
type: formFieldType,
|
type: formFieldType,
|
||||||
enum: formFieldSelectOptions.split(','),
|
enum: formFieldSelectOptions.split(','),
|
||||||
|
pattern: '',
|
||||||
|
default: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
setFormFieldIdHasBeenUpdatedByUser(false);
|
setFormFieldIdHasBeenUpdatedByUser(false);
|
||||||
|
@ -168,8 +231,8 @@ export default function JsonSchemaFormBuilder() {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveCallback = (result: any) => {
|
const handleSaveCallback = () => {
|
||||||
console.log('result', result);
|
setSavedJsonSchema(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadFile = (file: File) => {
|
const uploadFile = (file: File) => {
|
||||||
|
@ -188,17 +251,111 @@ export default function JsonSchemaFormBuilder() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveFile = () => {
|
const saveFile = () => {
|
||||||
const formJsonFileName = `${formId}-schema.json`;
|
setSavedJsonSchema(false);
|
||||||
const formUiJsonFileName = `${formId}-uischema.json`;
|
let formJsonFileName = `${formId}-schema.json`;
|
||||||
|
let formUiJsonFileName: string | null = `${formId}-uischema.json`;
|
||||||
|
if (searchParams.get('file_name')) {
|
||||||
|
formJsonFileName = searchParams.get('file_name') as any;
|
||||||
|
if (formJsonFileName.match(/-schema\.json$/)) {
|
||||||
|
formUiJsonFileName = (searchParams.get('file_name') as any).replace(
|
||||||
|
'-schema.json',
|
||||||
|
'-uischema.json'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
formUiJsonFileName = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uploadFile(new File([renderFormJson()], formJsonFileName));
|
uploadFile(new File([renderFormJson()], formJsonFileName));
|
||||||
uploadFile(new File([renderFormUiJson()], formUiJsonFileName));
|
if (formUiJsonFileName) {
|
||||||
|
uploadFile(new File([renderFormUiJson()], formUiJsonFileName));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFile = () => {
|
||||||
|
const url = `/process-models/${modifiedProcessModelId}/files/${params.file_name}`;
|
||||||
|
const httpMethod = 'DELETE';
|
||||||
|
|
||||||
|
const navigateToProcessModelShow = (_httpResult: any) => {
|
||||||
|
navigate(`/admin/process-models/${modifiedProcessModelId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: url,
|
||||||
|
successCallback: navigateToProcessModelShow,
|
||||||
|
httpMethod,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const formIdTextField = () => {
|
||||||
|
if (searchParams.get('file_name')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
id="json-form-id"
|
||||||
|
name="id"
|
||||||
|
labelText="ID"
|
||||||
|
value={formId}
|
||||||
|
onChange={(event: any) => {
|
||||||
|
setFormIdHasBeenUpdatedByUser(true);
|
||||||
|
setFormId(event.srcElement.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonFormButton = () => {
|
||||||
|
if (!searchParams.get('file_name')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ButtonWithConfirmation
|
||||||
|
data-qa="delete-process-model-file"
|
||||||
|
description={`Delete file ${searchParams.get('file_name')}?`}
|
||||||
|
onConfirmation={deleteFile}
|
||||||
|
buttonLabel="Delete"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/admin/process-models/${
|
||||||
|
params.process_model_id
|
||||||
|
}/form/${searchParams.get('file_name')}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="danger"
|
||||||
|
data-qa="form-builder-button"
|
||||||
|
>
|
||||||
|
View Json
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsonFormArea = () => {
|
const jsonFormArea = () => {
|
||||||
|
const processModelFileName = searchParams.get('file_name') || '';
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ProcessBreadcrumb
|
||||||
|
hotCrumbs={[
|
||||||
|
['Process Groups', '/admin'],
|
||||||
|
{
|
||||||
|
entityToExplode: params.process_model_id || '',
|
||||||
|
entityType: 'process-model-id',
|
||||||
|
linkLastItem: true,
|
||||||
|
},
|
||||||
|
[processModelFileName],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<h1>
|
||||||
|
Process Model File{processModelFileName ? ': ' : ''}
|
||||||
|
{processModelFileName}
|
||||||
|
</h1>
|
||||||
|
{formSubmitResultElement()}
|
||||||
<Button onClick={saveFile}>Save</Button>
|
<Button onClick={saveFile}>Save</Button>
|
||||||
|
{jsonFormButton()}
|
||||||
<TextInput
|
<TextInput
|
||||||
id="json-form-title"
|
id="json-form-title"
|
||||||
name="title"
|
name="title"
|
||||||
|
@ -208,16 +365,7 @@ export default function JsonSchemaFormBuilder() {
|
||||||
onFormTitleChange(event.srcElement.value);
|
onFormTitleChange(event.srcElement.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
{formIdTextField()}
|
||||||
id="json-form-id"
|
|
||||||
name="id"
|
|
||||||
labelText="ID"
|
|
||||||
value={formId}
|
|
||||||
onChange={(event: any) => {
|
|
||||||
setFormIdHasBeenUpdatedByUser(true);
|
|
||||||
setFormId(event.srcElement.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
id="form-description"
|
id="form-description"
|
||||||
name="description"
|
name="description"
|
||||||
|
|
|
@ -190,6 +190,9 @@ export default function ReactFormEditor() {
|
||||||
|
|
||||||
if (processModelFile || !params.file_name) {
|
if (processModelFile || !params.file_name) {
|
||||||
const processModelFileName = processModelFile ? processModelFile.name : '';
|
const processModelFileName = processModelFile ? processModelFile.name : '';
|
||||||
|
const formBuildFileParam = params.file_name
|
||||||
|
? `?file_name=${params.file_name}`
|
||||||
|
: '';
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<ProcessBreadcrumb
|
<ProcessBreadcrumb
|
||||||
|
@ -212,19 +215,7 @@ export default function ReactFormEditor() {
|
||||||
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">
|
<Button onClick={saveFile} variant="danger" data-qa="file-save-button">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
{params.file_name ? null : (
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
`/admin/process-models/${params.process_model_id}/form-builder`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
variant="danger"
|
|
||||||
data-qa="form-builder-button"
|
|
||||||
>
|
|
||||||
Form Builder
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{params.file_name ? (
|
{params.file_name ? (
|
||||||
<ButtonWithConfirmation
|
<ButtonWithConfirmation
|
||||||
data-qa="delete-process-model-file"
|
data-qa="delete-process-model-file"
|
||||||
|
@ -233,6 +224,17 @@ export default function ReactFormEditor() {
|
||||||
buttonLabel="Delete"
|
buttonLabel="Delete"
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/admin/process-models/${params.process_model_id}/form-builder${formBuildFileParam}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant="danger"
|
||||||
|
data-qa="form-builder-button"
|
||||||
|
>
|
||||||
|
Form Builder
|
||||||
|
</Button>
|
||||||
{hasDiagram ? (
|
{hasDiagram ? (
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
Loading…
Reference in New Issue