diff --git a/spiffworkflow-backend/keycloak/realm_exports/sartography-realm.json b/spiffworkflow-backend/keycloak/realm_exports/sartography-realm.json index 5e85f830a..37704ea52 100644 --- a/spiffworkflow-backend/keycloak/realm_exports/sartography-realm.json +++ b/spiffworkflow-backend/keycloak/realm_exports/sartography-realm.json @@ -2549,4 +2549,4 @@ "clientPolicies" : { "policies" : [ ] } -} \ No newline at end of file +} diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py index 06c1bda74..3b8f518a4 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/git_service.py @@ -84,8 +84,10 @@ class GitService: repo_path_to_use = current_app.config["BPMN_SPEC_ABSOLUTE_DIR"] if repo_path_to_use is None: raise ConfigurationError("BPMN_SPEC_ABSOLUTE_DIR config must be set") - if current_app.config['GIT_SSH_PRIVATE_KEY']: - os.environ['GIT_SSH_PRIVATE_KEY'] = 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" + ] git_username = "" git_email = "" @@ -222,7 +224,7 @@ class GitService: destination_process_root = f"/tmp/{clone_dir}" # noqa 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( "https://", f"https://{current_app.config['GIT_USERNAME']}:{current_app.config['GIT_USER_PASSWORD']}@", diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index f6d3020a2..f3888f1de 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -228,4 +228,13 @@ export interface FormField { required: boolean; type: string; enum: string[]; + default: any; + pattern: string; +} + +export interface JsonSchemaForm { + file_contents: string; + name: string; + process_model_id: string; + required: string[]; } diff --git a/spiffworkflow-frontend/src/routes/JsonSchemaFormBuilder.tsx b/spiffworkflow-frontend/src/routes/JsonSchemaFormBuilder.tsx index b180ed2a0..b0f0d7c08 100644 --- a/spiffworkflow-frontend/src/routes/JsonSchemaFormBuilder.tsx +++ b/spiffworkflow-frontend/src/routes/JsonSchemaFormBuilder.tsx @@ -1,17 +1,22 @@ import { useEffect, useState } from 'react'; // @ts-ignore import { Button, Select, SelectItem, TextInput } from '@carbon/react'; -import { useParams } from 'react-router-dom'; -import { FormField } from '../interfaces'; +import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; +import { FormField, JsonSchemaForm } from '../interfaces'; import { modifyProcessIdentifierForPathParam, slugifyString, underscorizeString, } from '../helpers'; import HttpService from '../services/HttpService'; +import { Notification } from '../components/Notification'; +import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; +import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; export default function JsonSchemaFormBuilder() { const params = useParams(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); const formFieldTypes = ['textbox', 'checkbox', 'select']; const [formTitle, setFormTitle] = useState(''); @@ -31,34 +36,90 @@ export default function JsonSchemaFormBuilder() { const [formFieldId, setFormFieldId] = useState(''); const [formFieldTitle, setFormFieldTitle] = useState(''); const [formFieldType, setFormFieldType] = useState(''); + const [requiredFields, setRequiredFields] = useState([]); + const [savedJsonSchema, setSavedJsonSchema] = useState(false); const modifiedProcessModelId = modifyProcessIdentifierForPathParam( `${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 ( + setSavedJsonSchema(false)} + > + It saved + + ); + } + return null; + }; const renderFormJson = () => { const formJson = { title: formTitle, description: formDescription, properties: {}, - required: [], + required: requiredFields, }; formFields.forEach((formField: FormField) => { - let jsonSchemaFieldType = 'string'; - if (formField.type === 'checkbox') { + let jsonSchemaFieldType = formField.type; + if (['checkbox'].includes(formField.type)) { jsonSchemaFieldType = 'boolean'; } const formJsonObject: any = { - type: jsonSchemaFieldType, + type: jsonSchemaFieldType || 'string', title: formField.title, }; - if (formField.type === 'select') { + if (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; }); @@ -93,6 +154,8 @@ export default function JsonSchemaFormBuilder() { required: false, type: formFieldType, enum: formFieldSelectOptions.split(','), + pattern: '', + default: null, }; setFormFieldIdHasBeenUpdatedByUser(false); @@ -168,8 +231,8 @@ export default function JsonSchemaFormBuilder() { return null; }; - const handleSaveCallback = (result: any) => { - console.log('result', result); + const handleSaveCallback = () => { + setSavedJsonSchema(true); }; const uploadFile = (file: File) => { @@ -188,17 +251,111 @@ export default function JsonSchemaFormBuilder() { }; const saveFile = () => { - const formJsonFileName = `${formId}-schema.json`; - const formUiJsonFileName = `${formId}-uischema.json`; + setSavedJsonSchema(false); + 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([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 ( + { + setFormIdHasBeenUpdatedByUser(true); + setFormId(event.srcElement.value); + }} + /> + ); + }; + + const jsonFormButton = () => { + if (!searchParams.get('file_name')) { + return null; + } + return ( + <> + + + + ); }; const jsonFormArea = () => { + const processModelFileName = searchParams.get('file_name') || ''; return ( <> + +

+ Process Model File{processModelFileName ? ': ' : ''} + {processModelFileName} +

+ {formSubmitResultElement()} + {jsonFormButton()} - { - setFormIdHasBeenUpdatedByUser(true); - setFormId(event.srcElement.value); - }} - /> + {formIdTextField()} Save - {params.file_name ? null : ( - - )} + {params.file_name ? ( ) : null} + {hasDiagram ? (