Skip to content
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

Stojanovic/fe 103 subjects fieled nr docs #105

Merged
merged 11 commits into from
Nov 16, 2023
1 change: 1 addition & 0 deletions oarepo_ui/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ def empty_record(self, resource_requestctx, **kwargs):
record = deepmerge.always_merger.merge(
record, copy.deepcopy(self.config.empty_record)
)
record['metadata']={}
self.run_components(
"empty_record", resource_requestctx=resource_requestctx, record=record
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,33 @@ const CustomMessage = ({ children, ...uiProps }) => {
};
export const FormFeedback = () => {
const { values } = useFormikContext();
const validationErrors = getIn(values, "validationErrors", {});
const beValidationErrors = getIn(values, "BEvalidationErrors", {});
const feValidationErrors = getIn(values, "FEvalidationErrors", {});
let httpError = getIn(values, "httpErrors", "");
if (httpError?.response?.data) {
httpError = httpError?.response?.data.message;
}
const successMessage = getIn(values, "successMessage", "");
if (!_isEmpty(validationErrors))
if (!_isEmpty(beValidationErrors))
return (
<CustomMessage negative color="orange">
<Message.Header>{validationErrors?.errorMessage}</Message.Header>
<Message.Header>{beValidationErrors?.errorMessage}</Message.Header>
<Message.List>
{validationErrors?.errors?.map((error, index) => (
<Message.Item key={`${error.field}-${index}`}>{`${titleCase(error.field)}:${
error.messages[0]
}`}</Message.Item>
{beValidationErrors?.errors?.map((error, index) => (
<Message.Item key={`${error.field}-${index}`}>{`${titleCase(
error.field
)}: ${error.messages[0]}`}</Message.Item>
))}
</Message.List>
</CustomMessage>
);
if (!_isEmpty(feValidationErrors))
return (
<CustomMessage negative color="orange">
<Message.Header>{feValidationErrors?.errorMessage}</Message.Header>
<Message.List>
{feValidationErrors?.errors?.map((error, index) => (
<Message.Item key={`${error.field}-${index}`}>{error}</Message.Item>
))}
</Message.List>
</CustomMessage>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ I18nRichInputField.propTypes = {
editorConfig: PropTypes.object,
languageOptions: PropTypes.array,
lngFieldWidth: PropTypes.number,
usedLanguages: PropTypes.array,
};

I18nRichInputField.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ I18nTextInputField.propTypes = {
optimized: PropTypes.bool,
languageOptions: PropTypes.array,
lngFieldWidth: PropTypes.number,
usedLanguages: PropTypes.array,
};

I18nTextInputField.defaultProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ArrayFieldItem,
useDefaultLocale,
useFormFieldValue,
useShowEmptyValue,
} from "@js/oarepo_ui";
import { i18next } from "@translations/oarepo_ui/i18next";
import { useFormikContext, getIn } from "formik";
Expand All @@ -17,31 +18,32 @@ export const MultilingualTextInput = ({
label,
labelIcon,
required,
emptyNewInput,
defaultNewValue,
rich,
editorConfig,
textFieldLabel,
textFieldIcon,
helpText,
addButtonLabel,
lngFieldWidth,
showEmptyValue,
...uiProps
}) => {
const { defaultLocale } = useDefaultLocale();
const { values } = useFormikContext();
const { usedSubValues, defaultNewValue } = useFormFieldValue({
const { usedSubValues, defaultNewValue: getNewValue } = useFormFieldValue({
defaultValue: defaultLocale,
fieldPath,
subValuesPath: "lang",
});
const value = getIn(values, fieldPath);
const usedLanguages = usedSubValues(value);
const newValue = defaultNewValue(emptyNewInput, usedLanguages);

useShowEmptyValue(fieldPath, defaultNewValue, showEmptyValue);
return (
<ArrayField
addButtonLabel={addButtonLabel}
defaultNewValue={newValue}
defaultNewValue={getNewValue(defaultNewValue, usedLanguages)}
fieldPath={fieldPath}
label={
<FieldLabel htmlFor={fieldPath} icon={labelIcon ?? ""} label={label} />
Expand Down Expand Up @@ -101,14 +103,18 @@ MultilingualTextInput.propTypes = {
helpText: PropTypes.string,
addButtonLabel: PropTypes.string,
lngFieldWidth: PropTypes.number,
rich: PropTypes.bool,
defaultNewValue: PropTypes.object,
showEmptyValue: PropTypes.bool,
};

MultilingualTextInput.defaultProps = {
emptyNewInput: {
defaultNewValue: {
lang: "",
value: "",
},
rich: false,
label: undefined,
addButtonLabel: i18next.t("Add another language"),
showEmptyValue: false,
};
89 changes: 81 additions & 8 deletions oarepo_ui/theme/assets/semantic-ui/js/oarepo_ui/forms/hooks.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,36 @@
import * as React from "react";
import { FormConfigContext } from "./contexts";
import { OARepoDepositApiClient, OARepoDepositSerializer } from "../api";
import { useFormikContext } from "formik";
import _get from "lodash/get";
import _set from "lodash/set";
import { useFormikContext, getIn } from "formik";
import _omit from "lodash/omit";
import _pick from "lodash/pick";
import _isEmpty from "lodash/isEmpty";
import _isObject from "lodash/isObject";
import { i18next } from "@translations/oarepo_ui/i18next";
import { relativeUrl } from "../util";

const extractFEErrorMessages = (obj) => {
const errorMessages = [];

const traverse = (obj) => {
if (typeof obj === "string") {
errorMessages.push(obj);
} else if (Array.isArray(obj)) {
obj.forEach((item) => traverse(item));
} else if (typeof obj === "object") {
for (const key in obj) {
traverse(obj[key]);
}
}
};

traverse(obj);
const uniqueErrorMessages = [...new Set(errorMessages)];
return uniqueErrorMessages;
};

export const useFormConfig = () => {
const context = React.useContext(FormConfigContext);
if (!context) {
Expand Down Expand Up @@ -45,7 +66,7 @@ export const useConfirmationModal = () => {
return { isModalOpen, handleCloseModal, handleOpenModal };
};

export const useFormFieldValue = ({ fieldPath, subValuesPath, defaultValue, subValuesUnique = true }) => {
export const useFormFieldValue = ({ subValuesPath, defaultValue, subValuesUnique = true }) => {
const usedSubValues = (value) =>
value && typeof Array.isArray(value)
? value.map((val) => _get(val, "lang")) || []
Expand All @@ -55,12 +76,48 @@ export const useFormFieldValue = ({ fieldPath, subValuesPath, defaultValue, subV
return { usedSubValues, defaultNewValue }
}

export const useShowEmptyValue = (
fieldPath,
defaultNewValue,
showEmptyValue
) => {
const { values, setFieldValue } = useFormikContext();
const currentFieldValue = getIn(values, fieldPath, []);
React.useEffect(() => {
if (!showEmptyValue) return;
if (!_isEmpty(currentFieldValue)) return;
if (defaultNewValue === undefined) {
console.error(
"Default value for new input must be provided. Field: ",
fieldPath
);
return;
}
if (!fieldPath) {
console.error("Fieldpath must be provided");
return;
}
// to be used with invenio array fields that always push objects and add the __key property
if (!_isEmpty(defaultNewValue) && _isObject(defaultNewValue)) {
currentFieldValue.push({
__key: currentFieldValue.length,
...defaultNewValue,
});
setFieldValue(fieldPath, currentFieldValue);
} else if (typeof defaultNewValue === "string") {
currentFieldValue.push(defaultNewValue);
setFieldValue(fieldPath, currentFieldValue);
}
}, [showEmptyValue, setFieldValue, fieldPath, defaultNewValue]);
};

export const useDepositApiClient = (
baseApiClient,
serializer,
internalFieldsArray = [
"errors",
"validationErrors",
"BEvalidationErrors",
"FEvalidationErrors",
"httpErrors",
"successMessage",
],
Expand All @@ -78,7 +135,6 @@ export const useDepositApiClient = (
setFieldError,
setFieldValue,
} = formik;

const {
formConfig: { createUrl },
} = useFormConfig();
Expand Down Expand Up @@ -127,7 +183,7 @@ export const useDepositApiClient = (
setFieldError(error.field, error.messages[0])
);
// here I am setting the state to be used by FormFeedback componene that plugs into the formik's context.
setFieldValue("validationErrors", {
setFieldValue("BEvalidationErrors", {
errors: response.errors,
errorMessage: i18next.t(
"Draft saved with validation errors. Fields listed below that failed validation were not saved to the server"
Expand All @@ -154,10 +210,27 @@ export const useDepositApiClient = (
async function publish () {
// call save and if save returns false, exit
const saveResult = await save();
if (!saveResult) return;
if (!saveResult) {
setFieldValue(
"BEvalidationErrors.errorMessage",
i18next.t(
"Draft was saved but could not be published due to following validation errors"
)
);
return;
}
// imperative form validation, if fails exit
const validationErrors = await validateForm();
if (!_isEmpty(validationErrors)) return;
const FEvalidationErrors = await validateForm();
// show also front end validation errors grouped on the top similar to BE validation errors for consistency
if (!_isEmpty(FEvalidationErrors)) {
setFieldValue("FEvalidationErrors", {
errors: extractFEErrorMessages(FEvalidationErrors.metadata),
errorMessage: i18next.t(
"Draft was saved but could not be published due to following validation errors"
),
});
return;
}
setSubmitting(true);
let response;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"Draft saved successfully.": "Pracovní záznam byl uložen.",
"Draft published successfully. Redirecting to record's detail page ...": "Pracovní záznam byl úspěšně publikován. Budete přesměrováni na stránku s detailem záznamu …",
"Draft deleted successfully. Redirecting to the main page ...": "Pracovní verze záznamu byla smazána. Za okamžik Vás přesměrujeme na hlavní stránku.",
"Draft was saved but could not be published due to following validation errors": "Koncept byl uložen, ale nemohl být publikován kvůli následujícím chybám ověření:",
"Start over": "Začít znovu",
"resultsPerPage": "Výsledků na stránku",
"Filters": "Filtry",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"Draft saved successfully.": "Draft saved successfully.",
"Draft published successfully. Redirecting to record's detail page ...": "Draft published successfully. Redirecting to record's detail page ...",
"Draft deleted successfully. Redirecting to the main page ...": "Draft deleted successfully. Redirecting to the main page ...",
"Draft was saved but could not be published due to following validation errors": "Draft was saved but could not be published due to following validation errors:",
"Start over": "Start over",
"resultsPerPage": "Results per page",
"Filters": "Filters",
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = oarepo-ui
version = 5.0.82
version = 5.0.83
description = UI module for invenio 3.5+
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ui_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


def test_ui_resource_create_new(app, record_ui_resource, record_service):
assert record_ui_resource.empty_record(None) == {"title": None}
assert record_ui_resource.empty_record(None) == {'metadata': {}, 'title': None}


def test_ui_resource_form_config(app, record_ui_resource):
Expand Down