diff --git a/src/components/metadata/templates/AstroThesaurusSearchField.js b/src/components/metadata/templates/fields/AstroThesaurusSearchField.js
similarity index 70%
rename from src/components/metadata/templates/AstroThesaurusSearchField.js
rename to src/components/metadata/templates/fields/AstroThesaurusSearchField.js
index 365c43895..95607da3f 100644
--- a/src/components/metadata/templates/AstroThesaurusSearchField.js
+++ b/src/components/metadata/templates/fields/AstroThesaurusSearchField.js
@@ -2,8 +2,11 @@
* @author psarando sriram
*/
import React from "react";
+
+import { FastField } from "formik";
import PropTypes from "prop-types";
+import { useTranslation } from "i18n";
import FormSearchField from "components/forms/FormSearchField";
import { ListItemText } from "@mui/material";
@@ -12,8 +15,9 @@ const AstroThesaurusOption = (option) => (
);
-const AstroThesaurusSearchField = (props) => {
- const { searchAstroThesaurusTerms, ...custom } = props;
+const AstroThesaurusSearchFieldComponent = (props) => {
+ const { searchAstroThesaurusTerms, attribute, writable, ...custom } = props;
+
const [options, setOptions] = React.useState([]);
const handleSearch = (event, value, reason) => {
@@ -59,11 +63,31 @@ const AstroThesaurusSearchField = (props) => {
options={options}
labelKey="label"
valueKey="label"
+ label={attribute.name}
+ required={attribute.required && writable}
+ readOnly={!writable}
{...custom}
/>
);
};
+const AstroThesaurusSearchField = ({ avu, avuFieldName, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (props.attribute?.required && !value) {
+ return t("required");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
AstroThesaurusSearchField.propTypes = {
searchAstroThesaurusTerms: PropTypes.func.isRequired,
};
diff --git a/src/components/metadata/templates/fields/CheckboxStringValueField.js b/src/components/metadata/templates/fields/CheckboxStringValueField.js
new file mode 100644
index 000000000..982dca84c
--- /dev/null
+++ b/src/components/metadata/templates/fields/CheckboxStringValueField.js
@@ -0,0 +1,28 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import FormCheckboxStringValue from "components/forms/FormCheckboxStringValue";
+
+const CheckboxStringValueField = ({
+ attribute,
+ avu,
+ avuFieldName,
+ writable,
+ ...props
+}) => {
+ return (
+
+ );
+};
+
+export default CheckboxStringValueField;
diff --git a/src/components/metadata/templates/fields/EnumField.js b/src/components/metadata/templates/fields/EnumField.js
new file mode 100644
index 000000000..75c70fc3d
--- /dev/null
+++ b/src/components/metadata/templates/fields/EnumField.js
@@ -0,0 +1,40 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormTextField from "components/forms/FormTextField";
+import { MenuItem } from "@mui/material";
+
+const EnumField = ({ attribute, avu, avuFieldName, writable, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value) {
+ return t("required");
+ }
+ }}
+ {...props}
+ >
+ {attribute.values &&
+ attribute.values.map((enumVal, index) => (
+
+ ))}
+
+ );
+};
+
+export default EnumField;
diff --git a/src/components/metadata/templates/fields/GroupingField.js b/src/components/metadata/templates/fields/GroupingField.js
new file mode 100644
index 000000000..b4077dc8b
--- /dev/null
+++ b/src/components/metadata/templates/fields/GroupingField.js
@@ -0,0 +1,20 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+const GroupingField = ({
+ attribute,
+ avu,
+ avuFieldName,
+ writable,
+ ...props
+}) => {
+ return (
+
+ );
+};
+
+export default GroupingField;
diff --git a/src/components/metadata/templates/fields/IntegerField.js b/src/components/metadata/templates/fields/IntegerField.js
new file mode 100644
index 000000000..f0f07af92
--- /dev/null
+++ b/src/components/metadata/templates/fields/IntegerField.js
@@ -0,0 +1,35 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormIntegerField from "components/forms/FormIntegerField";
+
+const IntegerField = ({ attribute, avu, avuFieldName, writable, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value && value !== 0) {
+ return t("required");
+ }
+
+ if (isNaN(Number(value))) {
+ return t("templateValidationErrMsgNumber");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
+export default IntegerField;
diff --git a/src/components/metadata/templates/LocalContextsField.js b/src/components/metadata/templates/fields/LocalContextsField.js
similarity index 86%
rename from src/components/metadata/templates/LocalContextsField.js
rename to src/components/metadata/templates/fields/LocalContextsField.js
index de3048dcc..c99d32435 100644
--- a/src/components/metadata/templates/LocalContextsField.js
+++ b/src/components/metadata/templates/fields/LocalContextsField.js
@@ -7,11 +7,12 @@
*/
import React from "react";
+import { FastField } from "formik";
import { useQuery } from "react-query";
import { useTranslation } from "i18n";
-import LocalContextsLabelDisplay from "../LocalContextsLabelDisplay";
+import LocalContextsLabelDisplay from "../../LocalContextsLabelDisplay";
import ErrorTypographyWithDialog from "components/error/ErrorTypographyWithDialog";
import getFormError from "components/forms/getFormError";
@@ -19,6 +20,7 @@ import {
LocalContextsAttrs,
parseProjectID,
} from "components/models/metadata/LocalContexts";
+import { urlField } from "components/utils/validations";
import {
LOCAL_CONTEXTS_QUERY_KEY,
@@ -29,15 +31,18 @@ import { Skeleton, TextField } from "@mui/material";
const findAVU = (avus, attr) => avus?.find((avu) => avu.attr === attr);
-const LocalContextsField = ({
+const LocalContextsFieldComponent = ({
+ attribute,
avu,
- onUpdate,
+ avuFieldName,
+ writable,
helperText,
- form: { setFieldValue, ...form },
+ form,
field: { value, onChange, ...field },
...props
}) => {
- const { t } = useTranslation("localcontexts");
+ const { t } = useTranslation(["localcontexts", "metadata"]);
+ const { i18nUtil } = useTranslation("util");
const [rightsURIAVU, setRightsURIAVU] = React.useState(
() =>
@@ -87,8 +92,7 @@ const LocalContextsField = ({
return schemeURI;
});
- const { touched, errors } = form;
- const fieldError = getFormError(field.name, touched, errors);
+ const fieldError = getFormError(avuFieldName, form.touched, form.errors);
const projectID = parseProjectID(projectHubURI);
const { data: project, isFetching } = useQuery({
@@ -99,6 +103,8 @@ const LocalContextsField = ({
}),
enabled: !!projectHubURI && !fieldError,
onSuccess: (project) => {
+ if (!writable) return;
+
let newValue = avu.value || "";
const projectLabels = [
@@ -163,7 +169,11 @@ const LocalContextsField = ({
})),
];
- onUpdate({ ...avu, value: newValue, avus: newAVUs });
+ form.setFieldValue(avuFieldName, {
+ ...avu,
+ value: newValue,
+ avus: newAVUs,
+ });
}
},
onError: (error) => {
@@ -179,6 +189,10 @@ const LocalContextsField = ({
const updateProjectHubURI = (uri) => {
setProjectHubURI(uri);
setProjectHubError(null);
+ form.setFieldError(
+ avuFieldName,
+ !uri ? t("metadata:required") : urlField(uri, i18nUtil)
+ );
let newAVUs = avu.avus || [];
@@ -204,7 +218,7 @@ const LocalContextsField = ({
rightsIDSchemeURIAVU,
];
- onUpdate({ ...avu, avus: newAVUs });
+ form.setFieldValue(avuFieldName, { ...avu, avus: newAVUs });
}
};
@@ -213,6 +227,7 @@ const LocalContextsField = ({
return (
<>
(
+
+);
+
export default LocalContextsField;
diff --git a/src/components/metadata/templates/fields/MultilineTextField.js b/src/components/metadata/templates/fields/MultilineTextField.js
new file mode 100644
index 000000000..c2ee11a54
--- /dev/null
+++ b/src/components/metadata/templates/fields/MultilineTextField.js
@@ -0,0 +1,37 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormMultilineTextField from "components/forms/FormMultilineTextField";
+
+const MultilineTextField = ({
+ attribute,
+ avu,
+ avuFieldName,
+ writable,
+ ...props
+}) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value) {
+ return t("required");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
+export default MultilineTextField;
diff --git a/src/components/metadata/templates/fields/NumberField.js b/src/components/metadata/templates/fields/NumberField.js
new file mode 100644
index 000000000..09916f9e8
--- /dev/null
+++ b/src/components/metadata/templates/fields/NumberField.js
@@ -0,0 +1,35 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormNumberField from "components/forms/FormNumberField";
+
+const NumberField = ({ attribute, avu, avuFieldName, writable, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value && value !== 0.0) {
+ return t("required");
+ }
+
+ if (isNaN(Number(value))) {
+ return t("templateValidationErrMsgNumber");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
+export default NumberField;
diff --git a/src/components/metadata/templates/OntologyLookupServiceSearchField.js b/src/components/metadata/templates/fields/OntologyLookupServiceSearchField.js
similarity index 61%
rename from src/components/metadata/templates/OntologyLookupServiceSearchField.js
rename to src/components/metadata/templates/fields/OntologyLookupServiceSearchField.js
index 185b0c2f3..7376d9107 100644
--- a/src/components/metadata/templates/OntologyLookupServiceSearchField.js
+++ b/src/components/metadata/templates/fields/OntologyLookupServiceSearchField.js
@@ -2,8 +2,11 @@
* @author psarando sriram
*/
import React from "react";
+
+import { FastField } from "formik";
import PropTypes from "prop-types";
+import { useTranslation } from "i18n";
import FormSearchField from "components/forms/FormSearchField";
import { ListItemText } from "@mui/material";
@@ -19,8 +22,9 @@ const OLSOption = (option) => (
/>
);
-const OntologyLookupServiceSearchField = (props) => {
- const { attribute, searchOLSTerms, ...custom } = props;
+const OntologyLookupServiceSearchFieldComponent = (props) => {
+ const { attribute, searchOLSTerms, writable, ...custom } = props;
+
const [options, setOptions] = React.useState([]);
const handleSearch = (event, value, reason) => {
@@ -44,11 +48,31 @@ const OntologyLookupServiceSearchField = (props) => {
options={options}
labelKey="label"
valueKey="label"
+ label={attribute.name}
+ required={attribute.required && writable}
+ readOnly={!writable}
{...custom}
/>
);
};
+const OntologyLookupServiceSearchField = ({ avu, avuFieldName, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (props.attribute?.required && !value) {
+ return t("required");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
OntologyLookupServiceSearchField.propTypes = {
searchOLSTerms: PropTypes.func.isRequired,
};
diff --git a/src/components/metadata/templates/fields/TextField.js b/src/components/metadata/templates/fields/TextField.js
new file mode 100644
index 000000000..f203662bf
--- /dev/null
+++ b/src/components/metadata/templates/fields/TextField.js
@@ -0,0 +1,32 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormTextField from "components/forms/FormTextField";
+
+const TextField = ({ attribute, avu, avuFieldName, writable, ...props }) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value) {
+ return t("required");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
+export default TextField;
diff --git a/src/components/metadata/templates/fields/TimestampField.js b/src/components/metadata/templates/fields/TimestampField.js
new file mode 100644
index 000000000..6d68b2dd3
--- /dev/null
+++ b/src/components/metadata/templates/fields/TimestampField.js
@@ -0,0 +1,41 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+import FormTimestampField from "components/forms/FormTimestampField";
+
+const TimestampField = ({
+ attribute,
+ avu,
+ avuFieldName,
+ writable,
+ ...props
+}) => {
+ const { t } = useTranslation("metadata");
+
+ return (
+ {
+ if (attribute.required && !value) {
+ return t("required");
+ }
+
+ if (!Date.parse(value)) {
+ return t("templateValidationErrMsgTimestamp");
+ }
+ }}
+ {...props}
+ />
+ );
+};
+
+export default TimestampField;
diff --git a/src/components/metadata/templates/fields/UrlField.js b/src/components/metadata/templates/fields/UrlField.js
new file mode 100644
index 000000000..1ebac0ab2
--- /dev/null
+++ b/src/components/metadata/templates/fields/UrlField.js
@@ -0,0 +1,34 @@
+/**
+ * @author psarando
+ */
+import React from "react";
+
+import { FastField } from "formik";
+
+import { useTranslation } from "i18n";
+
+import FormTextField from "components/forms/FormTextField";
+import { urlField } from "components/utils/validations";
+
+const UrlField = ({ attribute, avu, avuFieldName, writable, ...props }) => {
+ const { t } = useTranslation("metadata");
+ const { i18nUtil } = useTranslation("util");
+
+ return (
+ {
+ return attribute.required && !value
+ ? t("required")
+ : urlField(value, i18nUtil, attribute.required);
+ }}
+ {...props}
+ />
+ );
+};
+
+export default UrlField;
diff --git a/src/components/metadata/templates/index.js b/src/components/metadata/templates/index.js
index 34e8174de..731c6b214 100644
--- a/src/components/metadata/templates/index.js
+++ b/src/components/metadata/templates/index.js
@@ -3,7 +3,7 @@
*/
import React, { Fragment } from "react";
-import { FastField, FieldArray, Formik } from "formik";
+import { FieldArray, Formik } from "formik";
import PropTypes from "prop-types";
import { useQuery } from "react-query";
@@ -11,7 +11,6 @@ import { useTranslation } from "i18n";
import ids from "../ids";
import styles from "../styles";
-import { urlField } from "components/utils/validations";
import AttributeTypes from "components/models/metadata/TemplateAttributeTypes";
import ConfirmationDialog from "components/utils/ConfirmationDialog";
@@ -29,21 +28,22 @@ import {
searchUnifiedAstronomyThesaurus,
} from "serviceFacades/metadata";
-import FormMultilineTextField from "components/forms/FormMultilineTextField";
-import FormTextField from "components/forms/FormTextField";
-import FormTimestampField from "components/forms/FormTimestampField";
-
-import FormNumberField from "components/forms/FormNumberField";
-
-import FormIntegerField from "components/forms/FormIntegerField";
import getFormError from "components/forms/getFormError";
import buildID from "components/utils/DebugIDUtil";
import { formatCurrentDate } from "components/utils/DateFormatter";
-import FormCheckboxStringValue from "components/forms/FormCheckboxStringValue";
-import AstroThesaurusSearchField from "./AstroThesaurusSearchField";
-import OntologyLookupServiceSearchField from "./OntologyLookupServiceSearchField";
-import LocalContextsField from "./LocalContextsField";
+import AstroThesaurusSearchField from "./fields/AstroThesaurusSearchField";
+import CheckboxStringValueField from "./fields/CheckboxStringValueField";
+import EnumField from "./fields/EnumField";
+import GroupingField from "./fields/GroupingField";
+import IntegerField from "./fields/IntegerField";
+import LocalContextsField from "./fields/LocalContextsField";
+import MultilineTextField from "./fields/MultilineTextField";
+import NumberField from "./fields/NumberField";
+import OntologyLookupServiceSearchField from "./fields/OntologyLookupServiceSearchField";
+import TextField from "./fields/TextField";
+import TimestampField from "./fields/TimestampField";
+import UrlField from "./fields/UrlField";
import SlideUpTransition from "../SlideUpTransition";
@@ -54,7 +54,6 @@ import {
AccordionDetails,
Grid,
IconButton,
- MenuItem,
Table,
Typography,
} from "@mui/material";
@@ -189,80 +188,51 @@ const MetadataTemplateAttributeForm = (props) => {
);
let attrErrors = false;
- let canRemove = !attribute.required,
- FieldComponent,
- fieldProps = {
- label: attribute.name,
- required: attribute.required && writable,
- inputProps: { readOnly: !writable },
- };
+ let canRemove = !attribute.required;
+ let FieldComponent;
+ let customFieldProps = {};
switch (attribute.type) {
case AttributeTypes.BOOLEAN:
- FieldComponent = FormCheckboxStringValue;
- fieldProps = {
- ...fieldProps,
- disabled: !writable,
- };
+ FieldComponent = CheckboxStringValueField;
break;
case AttributeTypes.NUMBER:
- FieldComponent = FormNumberField;
+ FieldComponent = NumberField;
break;
case AttributeTypes.INTEGER:
- FieldComponent = FormIntegerField;
+ FieldComponent = IntegerField;
break;
case AttributeTypes.MULTILINE_TEXT:
- FieldComponent = FormMultilineTextField;
+ FieldComponent = MultilineTextField;
break;
case AttributeTypes.TIMESTAMP:
- FieldComponent = FormTimestampField;
+ FieldComponent = TimestampField;
break;
case AttributeTypes.ENUM:
- FieldComponent = FormTextField;
- fieldProps = {
- ...fieldProps,
- select: true,
- children:
- attribute.values &&
- attribute.values.map((enumVal, index) => (
-
- )),
- };
+ FieldComponent = EnumField;
break;
case AttributeTypes.ONTOLOGY_TERM_UAT:
FieldComponent = AstroThesaurusSearchField;
- fieldProps = {
- ...fieldProps,
- searchAstroThesaurusTerms,
- readOnly: !writable,
- };
+ customFieldProps = { searchAstroThesaurusTerms };
break;
case AttributeTypes.ONTOLOGY_TERM_OLS:
FieldComponent = OntologyLookupServiceSearchField;
- fieldProps = {
- ...fieldProps,
- searchOLSTerms,
- attribute,
- readOnly: !writable,
- };
+ customFieldProps = { searchOLSTerms };
break;
case AttributeTypes.GROUPING:
- FieldComponent = "span";
- fieldProps = {};
+ FieldComponent = GroupingField;
+ break;
+
+ case AttributeTypes.URL:
+ FieldComponent = UrlField;
break;
default:
- FieldComponent = FormTextField;
- fieldProps.multiline = true;
+ FieldComponent = TextField;
break;
}
@@ -278,10 +248,6 @@ const MetadataTemplateAttributeForm = (props) => {
LocalContextsAttrs.LOCAL_CONTEXTS;
if (isLocalContextsAttr) {
FieldComponent = LocalContextsField;
- fieldProps.avu = avu;
- fieldProps.onUpdate = (avu) => {
- arrayHelpers.replace(index, avu);
- };
}
const avuFieldName = `${field}.avus[${index}]`;
@@ -339,11 +305,13 @@ const MetadataTemplateAttributeForm = (props) => {
alignItems="center"
>
-
{!isGroupingAttr && (
@@ -488,7 +456,7 @@ const MetadataTemplateForm = (props) => {
const [showErrorsDialog, setShowErrorsDialog] = React.useState(false);
const handleSubmitWrapper = () => {
- if (errors.error) {
+ if (errors?.metadata) {
setShowErrorsDialog(true);
} else {
handleSubmit();
@@ -605,7 +573,6 @@ const MetadataTemplateView = (props) => {
const [uatSearch, searchAstroThesaurusTerms] = React.useState(null);
const { t } = useTranslation("metadata");
- const { t: i18nUtil } = useTranslation("util");
const { isFetching } = useQuery({
queryKey: [FILESYSTEM_METADATA_TEMPLATE_QUERY_KEY, templateId],
@@ -709,108 +676,6 @@ const MetadataTemplateView = (props) => {
return { template, attributeMap, metadata };
};
- const validateAVUs = (avus, attributeMap) => {
- const avuArrayErrors = [];
- avus.forEach((avu, avuIndex) => {
- const avuErrors = {};
-
- const attrTemplate = attributeMap[avu.attr];
- if (!attrTemplate) {
- return;
- }
-
- let attrType = attrTemplate.type;
- let value = avu.value;
-
- const isLocalContexts =
- avu.attr === LocalContextsAttrs.LOCAL_CONTEXTS;
- if (isLocalContexts) {
- const rightsURIAVU = avu.avus?.find(
- (childAVU) =>
- childAVU.attr === LocalContextsAttrs.RIGHTS_URI
- );
-
- attrType = AttributeTypes.URL;
- value = rightsURIAVU?.value;
- }
-
- const isRequired = isLocalContexts || attrTemplate.required;
- if (isRequired && value === "") {
- avuErrors.value = t("required");
- avuErrors.error = true;
- avuArrayErrors[avuIndex] = avuErrors;
- } else if (value) {
- switch (attrType) {
- case AttributeTypes.NUMBER:
- case AttributeTypes.INTEGER:
- const numVal = Number(value);
- if (isNaN(numVal)) {
- avuErrors.value = t(
- "templateValidationErrMsgNumber"
- );
- avuErrors.error = true;
- avuArrayErrors[avuIndex] = avuErrors;
- }
-
- break;
-
- case AttributeTypes.TIMESTAMP:
- if (!Date.parse(value)) {
- avuErrors.value = t(
- "templateValidationErrMsgTimestamp"
- );
- avuErrors.error = true;
- avuArrayErrors[avuIndex] = avuErrors;
- }
-
- break;
-
- case AttributeTypes.URL:
- const err = urlField(value, i18nUtil);
- if (err) {
- avuErrors.value = err;
- avuErrors.error = true;
- avuArrayErrors[avuIndex] = avuErrors;
- }
-
- break;
-
- default:
- break;
- }
- }
-
- if (attrTemplate.attributes && avu.avus && avu.avus.length > 0) {
- const subAttrErros = validateAVUs(
- avu.avus,
- attrTemplate.attributes
- );
- if (subAttrErros.length > 0) {
- avuErrors.avus = subAttrErros;
- avuErrors.error = true;
- avuArrayErrors[avuIndex] = avuErrors;
- }
- }
- });
-
- return avuArrayErrors;
- };
-
- const validate = (values) => {
- const errors = {};
- const { attributeMap, metadata } = values;
-
- if (metadata.avus && metadata.avus.length > 0) {
- const avuArrayErrors = validateAVUs(metadata.avus, attributeMap);
- if (avuArrayErrors.length > 0) {
- errors.metadata = { avus: avuArrayErrors };
- errors.error = true;
- }
- }
-
- return errors;
- };
-
/**
* Users do not fill in the values of Grouping attributes,
* but duplicated attributes need unique values in order for the service to save them as separate AVUs.
@@ -874,7 +739,6 @@ const MetadataTemplateView = (props) => {
{(formikProps) => {