-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cred-server): generate custom credential schemas #751
base: develop
Are you sure you want to change the base?
Changes from all commits
2745ed8
2a0e873
e119087
793ba26
e75da81
8de4540
92ddba1
716989c
aff8afb
bd60ed4
15204dd
5c3f0de
191d4c4
dd1eb71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,323 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { useForm, Controller } from "react-hook-form"; | ||
import axios from "axios"; | ||
import { | ||
Alert, | ||
Autocomplete, | ||
Box, | ||
Button, | ||
FormControl, | ||
Grid, | ||
InputLabel, | ||
MenuItem, | ||
Select, | ||
TextField, | ||
} from "@mui/material"; | ||
import RefreshIcon from "@mui/icons-material/Refresh"; | ||
import { config } from "../config"; | ||
import { Contact } from "../types"; | ||
import { CredentialType, UUID_REGEX } from "../constants"; | ||
import { | ||
IAttributeObj, | ||
IAttributes, | ||
SchemaShortDetails, | ||
} from "../constants/type"; | ||
|
||
interface CredentialFormProps { | ||
onSubmit: (values: any) => Promise<void>; | ||
submitButtonText: string; | ||
successMessage: string; | ||
apiPath: string; | ||
} | ||
|
||
const CredentialForm: React.FC<CredentialFormProps> = ({ | ||
onSubmit, | ||
submitButtonText, | ||
successMessage, | ||
apiPath, | ||
}) => { | ||
const { | ||
control, | ||
register, | ||
handleSubmit, | ||
setValue, | ||
watch, | ||
formState: { errors }, | ||
} = useForm(); | ||
|
||
const [contacts, setContacts] = useState<Contact[]>([]); | ||
const [attributes, setAttributes] = useState<IAttributeObj[]>([]); | ||
const [isSuccess, setIsSuccess] = useState(false); | ||
const [schemaList, setSchemaList] = useState<SchemaShortDetails[]>([]); | ||
const [defaultCredentialType, setDefaultCredentialType] = | ||
useState<string>(""); | ||
const [selectedSchemaId, setSelectedSchemaId] = useState<string | null>(null); | ||
const [selectedContact, setSelectedContact] = useState<Contact | null>(null); | ||
|
||
useEffect(() => { | ||
handleGetContacts(); | ||
handleGetSchemaList(); | ||
}, []); | ||
|
||
useEffect(() => { | ||
const type = watch("credential_type") as CredentialType; | ||
if (!type) return; | ||
|
||
handleGetSchemaDetails(type); | ||
}, [watch("credential_type")]); | ||
|
||
const handleGetContacts = async () => { | ||
try { | ||
setContacts( | ||
(await axios.get(`${config.endpoint}${config.path.contacts}`)).data.data | ||
); | ||
} catch (e) { | ||
console.log(e); | ||
} | ||
}; | ||
|
||
const handleGetSchemaList = async () => { | ||
try { | ||
const response = await axios.get( | ||
`${config.endpoint}${config.path.schemaList}` | ||
); | ||
const schemas: SchemaShortDetails[] = response.data; | ||
setSchemaList(schemas); | ||
if (schemas.length > 0) { | ||
setDefaultCredentialType(schemas[0].title); | ||
setValue("credential_type", schemas[0].title); | ||
setSelectedSchemaId(schemas[0].$id); | ||
} else { | ||
setDefaultCredentialType(""); | ||
} | ||
} catch (error) { | ||
console.log(`Error fetching schema list: ${error}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see all errors are thrown in the console. It would be nice if we followed what we do in the app and show |
||
} | ||
}; | ||
|
||
const handleGetSchemaDetails = async (schemaTitle: string) => { | ||
const schema = schemaList.find((s) => s.title === schemaTitle); | ||
if (!schema) return; | ||
|
||
try { | ||
const response = await axios.get( | ||
`${config.endpoint}${config.path.schemaCustomFields}`, | ||
{ | ||
params: { id: schema.$id }, | ||
} | ||
); | ||
const { customizableKeys } = response.data; | ||
|
||
const newAttributes = Object.keys(customizableKeys).map((key: string) => { | ||
const value = customizableKeys[key]; | ||
return { | ||
key, | ||
label: `${value.name} - ${value.description}`, | ||
type: value.type || "string", // Default to 'string' if type is not provided | ||
defaultValue: value.default || "", // Add default value if provided | ||
}; | ||
}); | ||
|
||
newAttributes.forEach((att: IAttributeObj, index: number) => { | ||
setValue(`attributes.${index}.key`, att.key); | ||
setValue(`attributes.${index}.label`, att.label); | ||
setValue(`attributes.${index}.value`, att.defaultValue); | ||
}); | ||
|
||
setAttributes(newAttributes); | ||
setSelectedSchemaId(schema.$id); | ||
} catch (error) { | ||
console.log(`Error fetching schema details: ${error}`); | ||
} | ||
}; | ||
|
||
const handleFormSubmit = async (values: any) => { | ||
setIsSuccess(false); | ||
let objAttributes = {}; | ||
let attribute: IAttributes = {}; | ||
|
||
values.attributes.forEach((att: IAttributeObj) => { | ||
if (att.key && att.value) attribute[att.key] = att.value; | ||
}); | ||
|
||
if (Object.keys(attribute).length) { | ||
objAttributes = { | ||
attribute, | ||
}; | ||
} | ||
|
||
const data = { | ||
schemaSaid: selectedSchemaId, | ||
aid: values.selectedContact, | ||
...objAttributes, | ||
}; | ||
|
||
try { | ||
await onSubmit(data); | ||
setIsSuccess(true); | ||
} catch (error) { | ||
console.log(error); | ||
} | ||
}; | ||
|
||
return ( | ||
<Box> | ||
<form onSubmit={handleSubmit(handleFormSubmit)}> | ||
<Grid | ||
container | ||
spacing={2} | ||
justifyContent="center" | ||
> | ||
<Grid | ||
item | ||
xs={10} | ||
> | ||
<FormControl fullWidth> | ||
<Controller | ||
name="selectedContact" | ||
control={control} | ||
render={({ field }) => ( | ||
<Autocomplete | ||
{...field} | ||
{...register(`selectedContact`, { required: true })} | ||
getOptionLabel={(option) => | ||
UUID_REGEX.test(option.alias) | ||
? option.id | ||
: `${option.alias} (${option.id})` | ||
} | ||
options={contacts || []} | ||
value={selectedContact} | ||
onChange={(_event, data) => { | ||
field.onChange(data?.id || null); | ||
setSelectedContact(data); | ||
}} | ||
renderInput={(params) => ( | ||
<TextField | ||
{...params} | ||
label="Search by connection name or ID" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice if we had a i18n file with all text in it. |
||
/> | ||
)} | ||
/> | ||
)} | ||
/> | ||
</FormControl> | ||
</Grid> | ||
<Grid | ||
item | ||
xs={1} | ||
> | ||
<Button | ||
startIcon={<RefreshIcon />} | ||
onClick={handleGetContacts} | ||
style={{ height: "100%" }} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline style is old school and usually never recommended. |
||
></Button> | ||
</Grid> | ||
<Grid | ||
item | ||
xs={11} | ||
> | ||
<FormControl fullWidth> | ||
<InputLabel id="credential_type">Credential Type</InputLabel> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is generally recommended to use lowercase and hyphens for classes, camelCase for IDs. |
||
<Controller | ||
name="credential_type" | ||
control={control} | ||
defaultValue={defaultCredentialType} | ||
render={({ field }) => ( | ||
<Select | ||
label="Credential Type" | ||
labelId="credential_type" | ||
{...register(`credential_type`, { required: true })} | ||
value={field.value || ""} | ||
onChange={(event) => { | ||
field.onChange(event); | ||
setSelectedSchemaId( | ||
schemaList.find((s) => s.title === event.target.value) | ||
?.$id || null | ||
); | ||
}} | ||
> | ||
{schemaList.map((schema, index) => ( | ||
<MenuItem | ||
key={index} | ||
value={schema.title} | ||
> | ||
{schema.title} | ||
</MenuItem> | ||
))} | ||
</Select> | ||
)} | ||
/> | ||
</FormControl> | ||
</Grid> | ||
<Grid | ||
item | ||
xs={11} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why we are using something different (mui?) than what we already have in the app for handling styles. I would suggest to keep it consistent, instead of adopting a whole different system, however I understand this entire tool is already built so this will just be a thought. |
||
> | ||
{attributes.map((att, index) => ( | ||
<FormControl | ||
key={index} | ||
sx={{ width: "100%", mb: 2 }} | ||
> | ||
<Controller | ||
control={control} | ||
name={`attributes.${index}.value`} | ||
render={({ field }) => { | ||
switch (att.type) { | ||
case "integer": | ||
return ( | ||
<TextField | ||
{...field} | ||
label={att.label} | ||
type="number" | ||
variant="outlined" | ||
onChange={(e) => | ||
field.onChange(Number(e.target.value)) | ||
} | ||
/> | ||
); | ||
case "string": | ||
default: | ||
return ( | ||
<TextField | ||
{...field} | ||
label={att.label} | ||
type="text" | ||
variant="outlined" | ||
/> | ||
); | ||
} | ||
}} | ||
/> | ||
</FormControl> | ||
))} | ||
</Grid> | ||
<Grid | ||
item | ||
xs={12} | ||
> | ||
<Box sx={{ display: "flex", justifyContent: "right" }}> | ||
{errors.selectedContact && ( | ||
<Alert severity="error"> | ||
Please, select a contact from the list of connections | ||
</Alert> | ||
)} | ||
{errors.credential_type && ( | ||
<Alert severity="error">Please, select a credential type</Alert> | ||
)} | ||
{isSuccess && <Alert severity="success">{successMessage}</Alert>} | ||
<Button | ||
variant="contained" | ||
color="primary" | ||
type="submit" | ||
> | ||
{submitButtonText} | ||
</Button> | ||
</Box> | ||
</Grid> | ||
</Grid> | ||
</form> | ||
</Box> | ||
); | ||
}; | ||
|
||
export { CredentialForm }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can @sdisalvo-crd review the UI folder?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean just the new changes or the entire folder? I've never looked into it before.