-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add dynamic routing for BuildPage * WIP * Code cleanup * Code refactoring * Update deployment instructions * WIP: Refactoring * WIP * Add tests * packages upgraded --------- Co-authored-by: Davor Runje <[email protected]>
- Loading branch information
1 parent
1f703cf
commit 1a5a48b
Showing
11 changed files
with
1,094 additions
and
314 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import React from 'react'; | ||
import _ from 'lodash'; | ||
import { TextInput } from './TextInput'; | ||
import { SelectInput } from './SelectInput'; | ||
import { TextArea } from './TextArea'; | ||
import { NumericStepperWithClearButton } from './NumericStepperWithClearButton'; | ||
import { SECRETS_TO_MASK } from '../../utils/constants'; | ||
import { JsonSchema } from '../../interfaces/BuildPageInterfaces'; | ||
import { FormData } from '../../hooks/useForm'; | ||
import AgentConversationHistory from '../AgentConversationHistory'; | ||
|
||
interface DynamicFormProps { | ||
jsonSchema: JsonSchema; | ||
formData: FormData; | ||
handleChange: (key: string, value: any) => void; | ||
formErrors: Record<string, string>; | ||
refValues: Record<string, any>; | ||
isLoading: boolean; | ||
onMissingDependencyClick: (e: any, type: string) => void; | ||
updateExistingModel: any; | ||
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void; | ||
instructionForDeployment: Record<string, string> | null; | ||
onCancelCallback: (event: React.FormEvent) => void; | ||
cancelButtonRef: React.RefObject<HTMLButtonElement>; | ||
onDeleteCallback: (data: any) => void; | ||
} | ||
|
||
const DynamicForm: React.FC<DynamicFormProps> = ({ | ||
jsonSchema, | ||
formData, | ||
handleChange, | ||
formErrors, | ||
refValues, | ||
isLoading, | ||
onMissingDependencyClick, | ||
updateExistingModel, | ||
handleSubmit, | ||
instructionForDeployment, | ||
onCancelCallback, | ||
cancelButtonRef, | ||
onDeleteCallback, | ||
}) => { | ||
return ( | ||
<form onSubmit={handleSubmit} className='px-6.5 py-2'> | ||
{Object.entries(jsonSchema.properties).map(([key, property]) => { | ||
if (key === 'uuid') { | ||
return null; | ||
} | ||
const inputValue = formData[key] || ''; | ||
let missingDependencyForKey = null; | ||
let formElementsObject = property; | ||
if (_.has(property, '$ref') || _.has(property, 'anyOf') || _.has(property, 'allOf')) { | ||
if (refValues[key]) { | ||
formElementsObject = refValues[key].htmlSchema; | ||
missingDependencyForKey = refValues[key].missingDependency; | ||
missingDependencyForKey.label = formElementsObject.title; | ||
} | ||
} | ||
// return formElementsObject?.enum?.length === 1 ? null : ( | ||
return ( | ||
<div key={key} className='w-full mt-2'> | ||
<label htmlFor={key}>{formElementsObject.title}</label> | ||
{formElementsObject.enum ? ( | ||
formElementsObject.type === 'numericStepperWithClearButton' ? ( | ||
<div> | ||
<NumericStepperWithClearButton | ||
id={key} | ||
value={inputValue} | ||
formElementObject={formElementsObject} | ||
onChange={(value) => handleChange(key, value)} | ||
/> | ||
</div> | ||
) : ( | ||
<SelectInput | ||
id={key} | ||
value={inputValue} | ||
options={formElementsObject.enum} | ||
onChange={(value) => handleChange(key, value)} | ||
missingDependency={missingDependencyForKey} | ||
onMissingDependencyClick={onMissingDependencyClick} | ||
/> | ||
) | ||
) : key === 'system_message' ? ( | ||
<TextArea | ||
id={key} | ||
value={inputValue} | ||
placeholder={formElementsObject.description || ''} | ||
onChange={(value) => handleChange(key, value)} | ||
/> | ||
) : ( | ||
<TextInput | ||
id={key} | ||
type={_.includes(SECRETS_TO_MASK, key) && typeof inputValue === 'string' ? 'password' : 'text'} | ||
value={inputValue} | ||
placeholder={formElementsObject.description || ''} | ||
onChange={(value) => handleChange(key, value)} | ||
/> | ||
)} | ||
{formErrors[key] && <div style={{ color: 'red' }}>{formErrors[key]}</div>} | ||
</div> | ||
); | ||
})} | ||
{instructionForDeployment && instructionForDeployment.instruction && ( | ||
<div className='w-full mt-8'> | ||
<AgentConversationHistory | ||
agentConversationHistory={instructionForDeployment.instruction} | ||
isDeploymentInstructions={true} | ||
containerTitle='Deployment Details and Next Steps' | ||
/> | ||
</div> | ||
)} | ||
<div className='col-span-full mt-7'> | ||
<div className='float-right'> | ||
<button | ||
className='rounded-md px-3.5 py-2.5 text-sm border border-airt-error text-airt-primary hover:bg-opacity-10 hover:bg-airt-error shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600' | ||
disabled={isLoading} | ||
data-testid='form-cancel-button' | ||
onClick={onCancelCallback} | ||
ref={cancelButtonRef} | ||
> | ||
Cancel | ||
</button> | ||
<button | ||
type='submit' | ||
className='ml-3 rounded-md px-3.5 py-2.5 text-sm bg-airt-primary text-airt-font-base hover:bg-opacity-85 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600' | ||
disabled={isLoading} | ||
data-testid='form-submit-button' | ||
> | ||
Save | ||
</button> | ||
</div> | ||
|
||
{updateExistingModel && ( | ||
<button | ||
type='button' | ||
className='float-left rounded-md px-3.5 py-2.5 text-sm border bg-airt-error text-airt-font-base hover:bg-opacity-80 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600' | ||
disabled={isLoading} | ||
data-testid='form-cancel-button' | ||
onClick={onDeleteCallback} | ||
> | ||
Delete | ||
</button> | ||
)} | ||
</div> | ||
</form> | ||
); | ||
}; | ||
|
||
export default DynamicForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { useEffect } from 'react'; | ||
import { DEPLOYMENT_INSTRUCTIONS } from '../utils/constants'; | ||
|
||
export const useDeploymentInstructions = ( | ||
updateExistingModel: any, | ||
type_name: string, | ||
setInstructionForDeployment: (value: any) => void | ||
) => { | ||
useEffect(() => { | ||
if (updateExistingModel && type_name === 'deployment') { | ||
const msg = DEPLOYMENT_INSTRUCTIONS; | ||
|
||
setInstructionForDeployment((prevState: any) => ({ | ||
...prevState, | ||
gh_repo_url: updateExistingModel.gh_repo_url, | ||
flyio_app_url: updateExistingModel.flyio_app_url, | ||
instruction: msg | ||
.replaceAll('<gh_repo_url>', updateExistingModel.gh_repo_url) | ||
.replaceAll('<flyio_app_url>', updateExistingModel.flyio_app_url), | ||
})); | ||
} | ||
}, [updateExistingModel, type_name, setInstructionForDeployment]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { useEffect, RefObject } from 'react'; | ||
|
||
export const useEscapeKeyHandler = (cancelButtonRef: RefObject<HTMLButtonElement>) => { | ||
useEffect(() => { | ||
const keyHandler = (event: KeyboardEvent) => { | ||
if (event.key !== 'Escape') return; | ||
cancelButtonRef.current?.click(); | ||
}; | ||
document.addEventListener('keydown', keyHandler); | ||
return () => document.removeEventListener('keydown', keyHandler); | ||
}, [cancelButtonRef]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { useState } from 'react'; | ||
import { useHistory } from 'react-router-dom'; | ||
import { validateForm } from '../services/commonService'; | ||
import { | ||
getFormSubmitValues, | ||
getSecretUpdateFormSubmitValues, | ||
getSecretUpdateValidationURL, | ||
} from '../utils/buildPageUtils'; | ||
import { DEPLOYMENT_INSTRUCTIONS } from '../utils/constants'; | ||
import { SelectedModelSchema } from '../interfaces/BuildPageInterfaces'; | ||
import { parseValidationErrors } from '../app/utils/formHelpers'; | ||
|
||
interface UseFormSubmissionProps { | ||
type_name: string; | ||
validationURL: string; | ||
updateExistingModel: SelectedModelSchema | null; | ||
onSuccessCallback: (data: any) => void; | ||
setFormErrors: (errors: any) => void; | ||
} | ||
|
||
export const useFormSubmission = ({ | ||
type_name, | ||
validationURL, | ||
updateExistingModel, | ||
onSuccessCallback, | ||
setFormErrors, | ||
}: UseFormSubmissionProps) => { | ||
const [isLoading, setIsLoading] = useState(false); | ||
const [notification, setNotification] = useState({ | ||
message: 'Oops. Something went wrong. Please try again later.', | ||
show: false, | ||
}); | ||
const [instructionForDeployment, setInstructionForDeployment] = useState<Record<string, string> | null>(null); | ||
const history = useHistory(); | ||
const isDeployment = type_name === 'deployment'; | ||
|
||
const handleSubmit = async (event: React.FormEvent, formData: any, refValues: Record<string, any>) => { | ||
event.preventDefault(); | ||
if (instructionForDeployment && !updateExistingModel) { | ||
return; | ||
} | ||
setIsLoading(true); | ||
const isSecretUpdate = type_name === 'secret' && !!updateExistingModel; | ||
let formDataToSubmit: any = {}; | ||
let updatedValidationURL = validationURL; | ||
|
||
if (isSecretUpdate) { | ||
formDataToSubmit = getSecretUpdateFormSubmitValues(formData, updateExistingModel); | ||
updatedValidationURL = getSecretUpdateValidationURL(validationURL, updateExistingModel); | ||
} else { | ||
formDataToSubmit = getFormSubmitValues(refValues, formData, false); | ||
} | ||
|
||
try { | ||
const response = await validateForm(formDataToSubmit, updatedValidationURL, isSecretUpdate); | ||
const onSuccessCallbackResponse: any = await onSuccessCallback(response); | ||
|
||
if (isDeployment && !updateExistingModel) { | ||
setInstructionForDeployment((prevState) => ({ | ||
...prevState, | ||
gh_repo_url: response.gh_repo_url, | ||
instruction: DEPLOYMENT_INSTRUCTIONS.replaceAll('<gh_repo_url>', onSuccessCallbackResponse.gh_repo_url), | ||
})); | ||
} | ||
} catch (error: any) { | ||
try { | ||
const errorMsgObj = JSON.parse(error.message); | ||
const errors = parseValidationErrors(errorMsgObj); | ||
setFormErrors(errors); | ||
} catch (e: any) { | ||
setNotification({ message: error.message || notification.message, show: true }); | ||
} | ||
} finally { | ||
setIsLoading(false); | ||
} | ||
}; | ||
|
||
const notificationOnClick = () => { | ||
setNotification({ ...notification, show: false }); | ||
}; | ||
|
||
const onMissingDependencyClick = (e: any, type: string) => { | ||
history.push(`/build/${type}`); | ||
}; | ||
|
||
return { | ||
isLoading, | ||
notification, | ||
instructionForDeployment, | ||
handleSubmit, | ||
notificationOnClick, | ||
onMissingDependencyClick, | ||
setInstructionForDeployment, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { useState, useEffect } from 'react'; | ||
import _ from 'lodash'; | ||
import { | ||
getMatchedUserProperties, | ||
constructHTMLSchema, | ||
getAllRefs, | ||
checkForDependency, | ||
getMissingDependencyType, | ||
} from '../utils/buildPageUtils'; | ||
import { JsonSchema, SelectedModelSchema } from '../interfaces/BuildPageInterfaces'; | ||
|
||
interface UsePropertyReferenceValuesProps { | ||
jsonSchema: JsonSchema | null; | ||
allUserProperties: any; | ||
updateExistingModel: SelectedModelSchema | null; | ||
} | ||
|
||
export const usePropertyReferenceValues = ({ | ||
jsonSchema, | ||
allUserProperties, | ||
updateExistingModel, | ||
}: UsePropertyReferenceValuesProps) => { | ||
const [refValues, setRefValues] = useState<Record<string, any>>({}); | ||
|
||
useEffect(() => { | ||
async function fetchPropertyReferenceValues() { | ||
if (jsonSchema) { | ||
for (const [key, property] of Object.entries(jsonSchema.properties)) { | ||
const propertyHasRef = _.has(property, '$ref') && property['$ref']; | ||
const propertyHasAnyOf = (_.has(property, 'anyOf') || _.has(property, 'allOf')) && _.has(jsonSchema, '$defs'); | ||
if (propertyHasRef || propertyHasAnyOf) { | ||
const allRefList = propertyHasRef ? [property['$ref']] : getAllRefs(property); | ||
const refUserProperties = getMatchedUserProperties(allUserProperties, allRefList); | ||
const missingDependencyList = checkForDependency(refUserProperties, allRefList); | ||
const title: string = property.hasOwnProperty('title') ? property.title || '' : key; | ||
const selectedModelRefValues = _.get(updateExistingModel, key, null); | ||
const htmlSchema = constructHTMLSchema(refUserProperties, title, property, selectedModelRefValues); | ||
let missingDependencyType: null | string = null; | ||
if (missingDependencyList.length > 0) { | ||
missingDependencyType = getMissingDependencyType(jsonSchema.$defs, allRefList); | ||
} | ||
setRefValues((prev) => ({ | ||
...prev, | ||
[key]: { | ||
htmlSchema: htmlSchema, | ||
refUserProperties: refUserProperties, | ||
missingDependency: { | ||
type: missingDependencyType, | ||
label: key, | ||
}, | ||
}, | ||
})); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fetchPropertyReferenceValues(); | ||
}, [jsonSchema, allUserProperties, updateExistingModel]); | ||
|
||
return refValues; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { JsonSchema, SelectedModelSchema } from './BuildPageInterfaces'; | ||
|
||
export interface DynamicFormBuilderProps { | ||
allUserProperties: any; | ||
type_name: string; | ||
jsonSchema: JsonSchema; | ||
validationURL: string; | ||
updateExistingModel: SelectedModelSchema | null; | ||
onSuccessCallback: (data: any) => void; | ||
onCancelCallback: (event: React.FormEvent) => void; | ||
onDeleteCallback: (data: any) => void; | ||
} |
Oops, something went wrong.