diff --git a/gui/rpk-gui/src/lib/crud/components/base-form.tsx b/gui/rpk-gui/src/lib/crud/components/base-form.tsx index ffd36b8..c54ba9f 100644 --- a/gui/rpk-gui/src/lib/crud/components/base-form.tsx +++ b/gui/rpk-gui/src/lib/crud/components/base-form.tsx @@ -7,6 +7,8 @@ import ArrayFieldTemplate from "./templates/ArrayFieldTemplate" import ArrayFieldItemTemplate from "./templates/ArrayFieldItemTemplate"; import { ResourceContext } from "../contexts/ResourceContext"; import { ReactNode } from "react"; +import { ParametersContextProvider } from "../contexts/parameters-context"; +import CrudArrayField from "./fields/crud-array-field"; type BaseFormProps = { schema: RJSFSchema, @@ -19,11 +21,12 @@ type BaseFormProps = { } export const customWidgets: RegistryWidgetsType = { - TextWidget: CrudTextWidget + TextWidget: CrudTextWidget, }; export const customFields: RegistryFieldsType = { - AnyOfField: UnionEnumField + AnyOfField: UnionEnumField, + ArrayField: CrudArrayField } const customTemplates = { @@ -36,19 +39,21 @@ export const BaseForm: React.FC = (props) => { return ( -
onSubmit != undefined && onSubmit(e.formData)} - validator={validator} - omitExtraData={true} - widgets={customWidgets} - fields={customFields} - templates={customTemplates} - onChange={(e, id) => onChange != undefined && onChange(e.formData)} - children={children} - /> + + onSubmit != undefined && onSubmit(e.formData)} + validator={validator} + omitExtraData={true} + widgets={customWidgets} + fields={customFields} + templates={customTemplates} + onChange={(e, id) => onChange != undefined && onChange(e.formData)} + children={children} + /> + ) } diff --git a/gui/rpk-gui/src/lib/crud/components/fields/crud-array-field.tsx b/gui/rpk-gui/src/lib/crud/components/fields/crud-array-field.tsx new file mode 100644 index 0000000..45adfae --- /dev/null +++ b/gui/rpk-gui/src/lib/crud/components/fields/crud-array-field.tsx @@ -0,0 +1,146 @@ +import React, { useContext } from "react"; +import { JSONSchema7Definition } from "json-schema"; +import { + FieldProps, + FormContextType, + getTemplate, getUiOptions, + RJSFSchema, +} from "@rjsf/utils"; +import ArrayField from "@rjsf/core/lib/components/fields/ArrayField"; +import validator from "@rjsf/validator-ajv8"; +import Form from "@rjsf/mui"; +import Typography from "@mui/material/Typography"; +import { Box, Paper } from "@mui/material"; +import { CrudTextRJSFSchema } from "../widgets/crud-text-widget"; +import { ParametersContext } from "../../contexts/parameters-context"; + +export type CrudArrayFieldSchema = RJSFSchema & { + props? : any +} + + +const CrudArrayField = (props: FieldProps)=> { +const { schema } = props + let isDictionary = false; + if (schema.props) { + if (schema.props.hasOwnProperty("display")) { + if (schema.props.display == "dictionary") { + isDictionary = true; + } + } + } + if (isDictionary) { + return + } + return {...props}/> +} + +export default CrudArrayField; + +type DictionaryEntry = { + key: string + value: string +}; + +const Dictionary = < + T = any, + S extends CrudTextRJSFSchema = CrudTextRJSFSchema, + F extends FormContextType = any +>(props: FieldProps)=> { + const { required, formData, onChange, registry, uiSchema, idSchema, schema } = props; + const uiOptions = getUiOptions(uiSchema); + const { parameters } = useContext(ParametersContext); + + const ArrayFieldDescriptionTemplate = getTemplate<'ArrayFieldDescriptionTemplate', T[], S, F>( + 'ArrayFieldDescriptionTemplate', + registry, + uiOptions + ); + const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate', T[], S, F>( + 'ArrayFieldTitleTemplate', + registry, + uiOptions + ); + + let properties = new Set() + for (const field in parameters) { + for (const param of parameters[field]) { + properties.add(param) + } + } + + let data: {[key:string]: string} = {} + if (formData !== undefined) { + for (const param of formData) { + // @ts-ignore + data[param.key] = param.value; + } + } + + const emptyDict = Object.values(data).length == 0; + + return ( + + + + + { emptyDict && No variables found} + { !emptyDict && ( + { + console.log(e) + let value: T[] = [] + for (const prop of properties) { + value.push({ + key: prop, + value: e.formData.hasOwnProperty(prop) ? e.formData[prop] : undefined + } as T) + } + onChange(value) + }} + > +   + + )} + + + ); +} + +function getFormSchema(properties: string[], isRequired: boolean) { + const schema: JSONSchema7Definition = { + type: "object", + properties: {}, + }; + let required: string[] = [] + for (const pname of properties) { + schema.properties![pname] = { + type: "string", + title: pname + } + if (isRequired) { + required.push(pname) + } + } + schema.required = required + + return schema +} diff --git a/gui/rpk-gui/src/lib/crud/components/templates/ArrayFieldTemplate.tsx b/gui/rpk-gui/src/lib/crud/components/templates/ArrayFieldTemplate.tsx index 70bdcc8..568ddbd 100644 --- a/gui/rpk-gui/src/lib/crud/components/templates/ArrayFieldTemplate.tsx +++ b/gui/rpk-gui/src/lib/crud/components/templates/ArrayFieldTemplate.tsx @@ -9,7 +9,6 @@ import { FormContextType, } from '@rjsf/utils'; import { CrudTextRJSFSchema } from "../widgets/crud-text-widget"; -import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; /** The `ArrayFieldTemplate` component is the template used to render all items in an array. @@ -45,7 +44,7 @@ export default function ArrayFieldTemplate< } = registry.templates; let gridSize = 12; - let numbered = false + let numbered = false; if (schema.props) { if (schema.props.hasOwnProperty("items_per_row")) { gridSize = gridSize / schema.props.items_per_row; @@ -74,7 +73,7 @@ export default function ArrayFieldTemplate< registry={registry} /> - {items && + { items && items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType, index) => ( @@ -82,9 +81,10 @@ export default function ArrayFieldTemplate< - ))} + )) + } - {canAdd && ( + { canAdd && ( diff --git a/gui/rpk-gui/src/lib/crud/components/widgets/crud-text-widget.tsx b/gui/rpk-gui/src/lib/crud/components/widgets/crud-text-widget.tsx index fe085f9..3510177 100644 --- a/gui/rpk-gui/src/lib/crud/components/widgets/crud-text-widget.tsx +++ b/gui/rpk-gui/src/lib/crud/components/widgets/crud-text-widget.tsx @@ -1,6 +1,6 @@ import React from "react"; import { getDefaultRegistry } from "@rjsf/core"; -import { FormContextType, getTemplate, RJSFSchema, WidgetProps } from "@rjsf/utils"; +import { FormContextType, RJSFSchema, WidgetProps } from "@rjsf/utils"; import Typography from "@mui/material/Typography"; import ForeignKeyWidget from "./foreign-key"; diff --git a/gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx b/gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx index fe01dfa..c58b09c 100644 --- a/gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx +++ b/gui/rpk-gui/src/lib/crud/components/widgets/foreign-key.tsx @@ -9,6 +9,7 @@ import React, { useState, useEffect, useContext, Fragment } from "react"; import { useForm, useList, useOne } from "@refinedev/core"; import { ResourceContext } from "../../contexts/ResourceContext"; import { CrudForm } from "../crud-form"; +import { ParametersContext } from "../../contexts/parameters-context"; export type ForeignKeyReference = { resource: string, @@ -21,6 +22,7 @@ export type ForeignKeySchema = RJSFSchema & { foreignKey?: { reference: ForeignKeyReference } + props? : any } export default function ForeignKeyWidget( @@ -42,10 +44,10 @@ export default function ForeignKeyWidget( props: WidgetProps ) => { - if (props.schema.foreignKey === undefined) { + const { onChange, label, fieldId, schema } = props; + if (schema.foreignKey === undefined) { return; } - const { onChange, label } = props const [openFormModal, setOpenFormModal] = useState(false); const [searchString, setSearchString] = useState(""); @@ -55,7 +57,14 @@ const RealAutocomplete = clearTimeout(handler); }, [searchString]); - const { resource, schema, label: labelField = "label" } = props.schema.foreignKey.reference + const { setFieldParameters } = useContext(ParametersContext) + useEffect(() => { + if (schema.hasOwnProperty("props") && schema.props.hasOwnProperty("parametrized") && schema.props.parametrized) { + setFieldParameters(fieldId, []) + } + }, []) + + const { resource, schema: fkSchema, label: labelField = "label" } = schema.foreignKey.reference const { basePath } = useContext(ResourceContext) const { data, isLoading } = useList({ resource: `${basePath}/${resource}`, @@ -104,7 +113,7 @@ const RealAutocomplete = ( props: WidgetProps & { onClear: () => void } ) => { - const { onClear, value } = props; + const { onClear, value, schema, id: fieldId } = props; const [openFormModal, setOpenFormModal] = React.useState(false); if (props.schema.foreignKey === undefined) { return; } - const { resource, schema, label: labelField = "label", displayedFields } = props.schema.foreignKey.reference + const { resource, schema: fkSchema, label: labelField = "label", displayedFields } = props.schema.foreignKey.reference const { basePath } = useContext(ResourceContext) - const { data, isLoading } = useOne({ + const { data, isLoading, isSuccess } = useOne({ resource: `${basePath}/${resource}`, id: value }); + const { setFieldParameters } = useContext(ParametersContext) + useEffect(() => { + if (isSuccess && schema.hasOwnProperty("props") && schema.props.hasOwnProperty("parametrized") && schema.props.parametrized) { + const record = data.data; + setFieldParameters(fieldId, extractParameters(record)) + } + }, [isSuccess]) + if (isLoading || data === undefined) { return } + const record = data.data; return ( <> - - { displayedFields && } + { displayedFields && } setOpenFormModal(false)} @@ -167,7 +185,7 @@ const ChosenValue = { ) } -const Preview = (props: {id: string, resource: string, basePath: string, displayedFields: [string]}) => { - const { basePath, resource, id, displayedFields } = props - - const { data, isLoading } = useOne({ - resource: `${basePath}/${resource}`, - id - }); - - if (isLoading || data === undefined) { - return - } +const Preview = (props: {record: any, displayedFields: [string]}) => { + const { record, displayedFields } = props return ( @@ -267,10 +276,29 @@ const Preview = (props: {id: string, resource: string, basePath: string, display return ( {field} - + ) })} ); } + +const extractParameters = (obj: any)=> { + let result: string[] = []; + for (const [k, v] of Object.entries(obj)) { + if (typeof(obj[k]) == "string") { + const matches = obj[k].match(/%[^\s.]+%/g); + if (matches) { + const filtered = matches.map((p: string | any[]) => p.slice(1,-1)) as string[] + result = result.concat(filtered); + } + } else if (typeof(obj[k]) == "object") { + if (obj[k]) { + result = result.concat(extractParameters(obj[k])); + } + } + } + + return result +} diff --git a/gui/rpk-gui/src/lib/crud/contexts/parameters-context.tsx b/gui/rpk-gui/src/lib/crud/contexts/parameters-context.tsx new file mode 100644 index 0000000..30c8a49 --- /dev/null +++ b/gui/rpk-gui/src/lib/crud/contexts/parameters-context.tsx @@ -0,0 +1,30 @@ +import React, { createContext, PropsWithChildren, useState } from 'react'; + +type Parameters = { + [field: string]: string[] +} + +type ParametersContextType = { + parameters: Parameters, + setFieldParameters: (fieldName: string, parameterList: string[]) => void +} + +export const ParametersContext = createContext( + {} as ParametersContextType +); + +export const ParametersContextProvider: React.FC = ({ children }: PropsWithChildren) => { + const [parameters, setParameters] = useState({}); + + function setFieldParameters(fieldName: string, parameterList: string[]) { + let params = structuredClone(parameters) + params[fieldName] = parameterList + setParameters(params); + } + + return ( + + {children} + + ); +}