Fixed persisting translations for attribute groups and improved errors for empty translations on attribute/attribute groups save (#33943)

* added fix for attribute groups

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* Improved translations for attributes

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improvement

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improvement

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improvement

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improvement

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improved fetching translations in NewAttributeSettings

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* improved fetching translations in NewAttributeSettings

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* cleanup

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

* cleanup

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>

---------

Signed-off-by: Agnieszka Gancarczyk <agagancarczyk@gmail.com>
This commit is contained in:
Agnieszka Gancarczyk 2024-10-31 07:01:26 +00:00 committed by GitHub
parent abb7c414ab
commit c64e0ad583
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 133 additions and 152 deletions

View file

@ -3280,4 +3280,5 @@ noGroupMemberships=No memberships
termsAndConditionsDeclined=You need to accept the Terms and Conditions to continue termsAndConditionsDeclined=You need to accept the Terms and Conditions to continue
somethingWentWrong=Something went wrong somethingWentWrong=Something went wrong
somethingWentWrongDescription=Sorry, an unexpected error has occurred. somethingWentWrongDescription=Sorry, an unexpected error has occurred.
tryAgain=Try again tryAgain=Try again
errorSavingTranslations=Error saving translations\: '{{error}}'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { import type {
UserProfileAttribute, UserProfileAttribute,
UserProfileConfig, UserProfileConfig,
@ -61,6 +62,7 @@ type UserProfileAttributeFormFields = Omit<
annotations: IndexedAnnotations[]; annotations: IndexedAnnotations[];
hasSelector: boolean; hasSelector: boolean;
hasRequiredScopes: boolean; hasRequiredScopes: boolean;
translations?: TranslationForm[];
}; };
type Attribute = { type Attribute = {
@ -172,7 +174,8 @@ export default function NewAttributeSettings() {
useFetch( useFetch(
async () => { async () => {
const translationsToSave: any[] = []; const translationsToSave: Translations[] = [];
await Promise.all( await Promise.all(
combinedLocales.map(async (selectedLocale) => { combinedLocales.map(async (selectedLocale) => {
try { try {
@ -183,55 +186,50 @@ export default function NewAttributeSettings() {
}); });
const formData = form.getValues(); const formData = form.getValues();
const formattedKey = formData.displayName?.substring( const formattedKey =
2, formData.displayName?.substring(
formData.displayName.length - 1, 2,
); formData.displayName.length - 1,
const filteredTranslations: Array<{ ) || "";
locale: string;
value: string; const filteredTranslations: TranslationForm[] = Object.entries(
}> = []; translations,
const allTranslations = Object.entries(translations).map( )
([key, value]) => ({ .filter(([key]) => key === formattedKey)
key, .map(([_, value]) => ({
locale: selectedLocale,
value, value,
}), }));
);
allTranslations.forEach((translation) => { if (filteredTranslations.length > 0) {
if (translation.key === formattedKey) { translationsToSave.push({
filteredTranslations.push({ key: formattedKey,
locale: selectedLocale, translations: filteredTranslations,
value: translation.value, });
}); }
}
});
const translationToSave: any = {
key: formattedKey,
translations: filteredTranslations,
};
translationsToSave.push(translationToSave);
} catch (error) { } catch (error) {
console.error( addError("errorSavingTranslations", error);
`Error fetching translations for ${selectedLocale}:`,
error,
);
} }
}), }),
); );
return translationsToSave; return translationsToSave;
}, },
(translationsToSaveData) => { (translationsToSave) => {
setTranslationsData(() => ({ if (translationsToSave && translationsToSave.length > 0) {
key: translationsToSaveData[0].key, const allTranslations = translationsToSave.flatMap(
translations: translationsToSaveData.flatMap( (translation) => translation.translations,
(translationData) => translationData.translations, );
),
})); setTranslationsData({
key: translationsToSave[0].key,
translations: allTranslations,
});
form.setValue("translations", allTranslations);
}
}, },
[combinedLocales], [combinedLocales, realmName, form],
); );
useFetch( useFetch(
@ -282,8 +280,9 @@ export default function NewAttributeSettings() {
const saveTranslations = async () => { const saveTranslations = async () => {
try { try {
const nonEmptyTranslations = translationsData.translations.map( const nonEmptyTranslations = translationsData.translations
async (translation) => { .filter((translation) => translation.value.trim() !== "")
.map(async (translation) => {
try { try {
await adminClient.realms.addLocalization( await adminClient.realms.addLocalization(
{ {
@ -293,11 +292,11 @@ export default function NewAttributeSettings() {
}, },
translation.value, translation.value,
); );
} catch { } catch (error) {
console.error(`Error saving translation for ${translation.locale}`); addError(t("errorSavingTranslations"), error);
} }
}, });
);
await Promise.all(nonEmptyTranslations); await Promise.all(nonEmptyTranslations);
} catch (error) { } catch (error) {
console.error(`Error saving translations: ${error}`); console.error(`Error saving translations: ${error}`);
@ -377,7 +376,7 @@ export default function NewAttributeSettings() {
(translation) => translation.value.trim() !== "", (translation) => translation.value.trim() !== "",
); );
if (!hasNonEmptyTranslations && !formFields.displayName) { if (!hasNonEmptyTranslations) {
addError("createAttributeError", t("translationError")); addError("createAttributeError", t("translationError"));
return; return;
} }

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata"; import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import { import {
HelpItem, HelpItem,
@ -17,7 +18,6 @@ import {
TextContent, TextContent,
TextInput, TextInput,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { GlobeRouteIcon } from "@patternfly/react-icons";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { import {
FormProvider, FormProvider,
@ -27,7 +27,6 @@ import {
} from "react-hook-form"; } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import { useAdminClient } from "../../admin-client";
import { FormAccess } from "../../components/form/FormAccess"; import { FormAccess } from "../../components/form/FormAccess";
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput"; import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
import type { KeyValueType } from "../../components/key-value-form/key-value-convert"; import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
@ -44,6 +43,8 @@ import {
AddTranslationsDialog, AddTranslationsDialog,
TranslationsType, TranslationsType,
} from "./attribute/AddTranslationsDialog"; } from "./attribute/AddTranslationsDialog";
import { GlobeRouteIcon } from "@patternfly/react-icons";
import { useAdminClient } from "../../admin-client";
function parseAnnotations(input: Record<string, unknown>): KeyValueType[] { function parseAnnotations(input: Record<string, unknown>): KeyValueType[] {
return Object.entries(input).reduce((p, [key, value]) => { return Object.entries(input).reduce((p, [key, value]) => {
@ -77,11 +78,6 @@ type Translations = {
translations: TranslationForm[]; translations: TranslationForm[];
}; };
type TranslationsSets = {
displayHeader: Translations;
displayDescription: Translations;
};
const defaultValues: FormFields = { const defaultValues: FormFields = {
annotations: [], annotations: [],
displayDescription: "", displayDescription: "",
@ -112,20 +108,21 @@ export default function AttributesGroupForm() {
const [addTranslationsModalOpen, toggleModal] = useToggle(); const [addTranslationsModalOpen, toggleModal] = useToggle();
const regexPattern = /\$\{([^}]+)\}/; const regexPattern = /\$\{([^}]+)\}/;
const [type, setType] = useState<TranslationsType>(); const [type, setType] = useState<TranslationsType>();
const [translationsData, setTranslationsData] = useState<TranslationsSets>({
const [translationsData, setTranslationsData] = useState({
displayHeader: { displayHeader: {
key: "", key: "",
translations: [], translations: [] as TranslationForm[],
}, },
displayDescription: { displayDescription: {
key: "", key: "",
translations: [], translations: [] as TranslationForm[],
}, },
}); });
const matchingGroup = useMemo( const matchingGroup = useMemo(
() => config?.groups?.find(({ name }) => name === params.name), () => config?.groups?.find(({ name }) => name === params.name),
[config?.groups], [config?.groups, params.name],
); );
useEffect(() => { useEffect(() => {
@ -138,120 +135,98 @@ export default function AttributesGroupForm() {
: []; : [];
form.reset({ ...defaultValues, ...matchingGroup, annotations }); form.reset({ ...defaultValues, ...matchingGroup, annotations });
}, [matchingGroup]); }, [matchingGroup, form]);
useEffect(() => { useEffect(() => {
form.setValue( form.setValue(
"displayHeader", "displayHeader",
matchingGroup matchingGroup?.displayHeader || generatedAttributesGroupDisplayName || "",
? matchingGroup.displayHeader!
: generatedAttributesGroupDisplayName,
); );
form.setValue( form.setValue(
"displayDescription", "displayDescription",
matchingGroup matchingGroup?.displayDescription ||
? matchingGroup.displayDescription! generatedAttributesGroupDisplayDescription ||
: generatedAttributesGroupDisplayDescription, "",
); );
}, [ }, [
generatedAttributesGroupDisplayName, generatedAttributesGroupDisplayName,
generatedAttributesGroupDisplayDescription, generatedAttributesGroupDisplayDescription,
matchingGroup,
form,
]); ]);
useFetch( useFetch(
async () => { async () => {
const translationsToSaveDisplayHeader: Translations[] = []; const translationsToSaveDisplayHeader: Translations[] = [];
const translationsToSaveDisplayDescription: Translations[] = []; const translationsToSaveDisplayDescription: Translations[] = [];
const formData = form.getValues();
const translationsResults = await Promise.all( await Promise.all(
combinedLocales.map(async (selectedLocale) => { combinedLocales.map(async (locale: string) => {
try { try {
const translations = const translations =
await adminClient.realms.getRealmLocalizationTexts({ await adminClient.realms.getRealmLocalizationTexts({
realm: realmName, realm: realmName,
selectedLocale, selectedLocale: locale,
}); });
const formattedDisplayHeaderKey = formData.displayHeader?.substring( const formData = form.getValues();
2, const extractKey = (value: string | undefined) => {
formData.displayHeader.length - 1, const match = value?.match(/\$\{(.*?)\}/);
); return match ? match[1] : "";
const formattedDisplayDescriptionKey =
formData.displayDescription?.substring(
2,
formData.displayDescription.length - 1,
);
return {
locale: selectedLocale,
headerTranslation: translations[formattedDisplayHeaderKey] ?? "",
descriptionTranslation:
translations[formattedDisplayDescriptionKey] ?? "",
}; };
const displayHeaderKey = extractKey(formData.displayHeader) || "";
const displayDescriptionKey =
extractKey(formData.displayDescription) || "";
const headerTranslation = translations[displayHeaderKey] || "";
const descriptionTranslation =
translations[displayDescriptionKey] || "";
if (headerTranslation) {
translationsToSaveDisplayHeader.push({
key: displayHeaderKey,
translations: [{ locale, value: headerTranslation }],
});
}
if (descriptionTranslation) {
translationsToSaveDisplayDescription.push({
key: displayDescriptionKey,
translations: [{ locale, value: descriptionTranslation }],
});
}
} catch (error) { } catch (error) {
console.error( console.error(`Error fetching translations for ${locale}:`, error);
`Error fetching translations for ${selectedLocale}:`,
error,
);
return null;
} }
}), }),
); );
translationsResults.forEach((translationsResult) => { const translationsDataNew = {
if (translationsResult) {
const { locale, headerTranslation, descriptionTranslation } =
translationsResult;
translationsToSaveDisplayHeader.push({
key: formData.displayHeader?.substring(
2,
formData.displayHeader.length - 1,
),
translations: [
{
locale,
value: headerTranslation,
},
],
});
translationsToSaveDisplayDescription.push({
key: formData.displayDescription?.substring(
2,
formData.displayDescription.length - 1,
),
translations: [
{
locale,
value: descriptionTranslation,
},
],
});
}
});
return {
translationsToSaveDisplayHeader,
translationsToSaveDisplayDescription,
};
},
(data) => {
setTranslationsData({
displayHeader: { displayHeader: {
key: data.translationsToSaveDisplayHeader[0].key, key:
translations: data.translationsToSaveDisplayHeader.flatMap( translationsToSaveDisplayHeader.length > 0
(translationData) => translationData.translations, ? translationsToSaveDisplayHeader[0].key
: "",
translations: translationsToSaveDisplayHeader.flatMap(
(data) => data.translations,
), ),
}, },
displayDescription: { displayDescription: {
key: data.translationsToSaveDisplayDescription[0].key, key:
translations: data.translationsToSaveDisplayDescription.flatMap( translationsToSaveDisplayDescription.length > 0
(translationData) => translationData.translations, ? translationsToSaveDisplayDescription[0].key
: "",
translations: translationsToSaveDisplayDescription.flatMap(
(data) => data.translations,
), ),
}, },
}); };
setTranslationsData(translationsDataNew);
}, },
[combinedLocales], () => {},
[combinedLocales, realmName, form],
); );
const saveTranslations = async () => { const saveTranslations = async () => {
@ -278,29 +253,33 @@ export default function AttributesGroupForm() {
try { try {
if ( if (
translationsData.displayHeader && translationsData &&
translationsData.displayHeader.translations.length > 0 translationsData.displayHeader.translations.length > 0
) { ) {
for (const translation of translationsData.displayHeader.translations) { for (const translation of translationsData.displayHeader.translations) {
await addLocalization( if (translation.locale && translation.value) {
translationsData.displayHeader.key, await addLocalization(
translation.locale, translationsData.displayHeader.key,
translation.value, translation.locale,
); translation.value,
);
}
} }
} }
if ( if (
translationsData.displayDescription && translationsData &&
translationsData.displayDescription.translations.length > 0 translationsData.displayDescription.translations.length > 0
) { ) {
for (const translation of translationsData.displayDescription for (const translation of translationsData.displayDescription
.translations) { .translations) {
await addLocalization( if (translation.locale && translation.value) {
translationsData.displayDescription.key, await addLocalization(
translation.locale, translationsData.displayDescription.key,
translation.value, translation.locale,
); translation.value,
);
}
} }
} }
} catch (error) { } catch (error) {
@ -331,7 +310,6 @@ export default function AttributesGroupForm() {
translationsData.displayHeader.translations.some( translationsData.displayHeader.translations.some(
(translation) => translation.value.trim() !== "", (translation) => translation.value.trim() !== "",
); );
const hasNonEmptyDisplayDescriptionTranslations = const hasNonEmptyDisplayDescriptionTranslations =
translationsData.displayDescription.translations.some( translationsData.displayDescription.translations.some(
(translation) => translation.value.trim() !== "", (translation) => translation.value.trim() !== "",

View file

@ -142,10 +142,13 @@ export const AddTranslationsDialog = ({
useEffect(() => { useEffect(() => {
combinedLocales.forEach((locale, rowIndex) => { combinedLocales.forEach((locale, rowIndex) => {
setValue(`translations.${rowIndex}.locale`, locale); setValue(`translations.${rowIndex}.locale`, locale);
const translationExists =
translations.translations[rowIndex] !== undefined;
setValue( setValue(
`translations.${rowIndex}.value`, `translations.${rowIndex}.value`,
translations.translations.length > 0 translationExists
? translations.translations[rowIndex].value ? translations.translations[rowIndex]?.value
: defaultTranslations[locale] || "", : defaultTranslations[locale] || "",
); );
}); });