Localization: Realm Overrides Fixes (#26169)

* resolved conflict

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* improvements

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* improved tests

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* feedback

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* test fix

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* test fix

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* resolved conflict

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

* fixed test

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>

---------

Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com>
Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com>
This commit is contained in:
agagancarczyk 2024-01-16 12:24:26 +00:00 committed by GitHub
parent e83cc12664
commit 33651c396c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 43 additions and 675 deletions

View file

@ -206,7 +206,8 @@ describe("Realm settings tabs tests", () => {
.contains("td", "123")
.should("be.visible");
cy.findByTestId("realmOverrides-deleteKebabToggle").click();
cy.get('td.pf-c-table__action button[aria-label="Actions"]').click();
cy.contains("button", "Delete").click();
cy.findByTestId("confirm").click();
masthead.checkNotificationMessage(
"Successfully removed message(s) from the bundle.",
@ -253,7 +254,8 @@ describe("Realm settings tabs tests", () => {
.contains("td", "def")
.should("be.visible");
cy.findByTestId("realmOverrides-deleteKebabToggle").click();
cy.get('td.pf-c-table__action button[aria-label="Actions"]').click();
cy.contains("button", "Delete").click();
cy.findByTestId("confirm").click();
masthead.checkNotificationMessage(
@ -343,7 +345,7 @@ describe("Realm settings tabs tests", () => {
it("Check a11y violations on localization realm overrides sub tab/ adding message bundle", () => {
realmSettingsPage.goToLocalizationTab();
realmSettingsPage.goToLocalizationRealmOverridesSubTab();
cy.findByTestId("add-bundle-button").click();
cy.findByTestId("add-translationBtn").click();
cy.checkA11y();
modalUtils.cancelModal();
});

View file

@ -95,7 +95,7 @@ export default class RealmSettingsPage extends CommonPage {
testConnectionButton = "test-connection-button";
modalTestConnectionButton = "modal-test-connection-button";
emailAddressInput = "email-address-input";
addBundleButton = "add-bundle-button";
addBundleButton = "add-translationBtn";
confirmAddBundle = "add-bundle-confirm-button";
keyInput = "key-input";
valueInput = "value-input";

View file

@ -964,7 +964,7 @@ deleteMappingConfirm=Are you sure you want to delete this mapping?
createClientProfileSuccess=New client profile created
eventTypes.CLIENT_LOGIN_ERROR.description=Client login error
explainBearerOnly=This is a special OIDC type. This client only allows bearer token requests and cannot participate in browser logins.
noMessageBundlesInstructions=Add a message bundle to get started.
noTranslationsInstructions=Add a translation to get started.
clearFile=Clear this file
allowCreate=Allow create
providerUpdatedError=Could not update client policy due to {{error}}
@ -2632,7 +2632,7 @@ minus=Minus
groupsHelp=Groups where the user has membership. To leave a group, select it and click Leave.
includeGroupsAndRoles=Include groups and roles
groupsPermissionsHint=Determines if fine grained permissions are enabled for managing this role. Disabling will delete all current permissions that have been set up.
searchForMessageBundle=Search for message bundle
searchForTranslation=Search for translation
offlineSessionMaxHelp=Max time before an offline session is expired regardless of activity.
resourceSaveError=Could not persist resource due to {{error}}
clientsClientScopesHelp=The scopes associated with this resource.
@ -2673,7 +2673,7 @@ policyType.totp=Time based
addAttribute=Add {{label}}
clientScopeSearch.protocol=Protocol
initialAccessTokenDetails=Initial access token details
noMessageBundles=No message bundles
noTranslations=No translations
deleteProvider=Delete provider?
inputTypeSize=Input size
createAttributeSubTitle=Create a new attribute
@ -2984,3 +2984,5 @@ joinCommunity=Join community
readBlog=Read blog
customValue=Custom value
termsAndConditionsUserAttribute=Terms and conditions accepted timestamp
realmOverridesDescription= Realm overrides allow you to specify translations that will take effect for the entire realm. These translations will override any translation specified by a theme.
addTranslation=Add translation

View file

@ -951,7 +951,7 @@ deleteMappingConfirm=Czy na pewno chcesz usunąć to odwzorowanie?
createClientProfileSuccess=Utworzono nowy profil klienta
eventTypes.CLIENT_LOGIN_ERROR.description=Błąd logowania klienta
explainBearerOnly=To jest specjalny typ OIDC. Ten klient pozwala tylko na żądania tokenów dostępu i nie może uczestniczyć w logowaniach przeglądarki.
noMessageBundlesInstructions=Dodaj pakiet wiadomości, aby rozpocząć.
noTranslationsInstructions=Dodaj tłumaczenie, aby rozpocząć.
clearFile=Wyczyść ten plik
allowCreate=Zezwól na tworzenie
providerUpdatedError=Nie można zaktualizować polityki klienta z powodu błędu: {{error}}
@ -2644,7 +2644,7 @@ policyType.totp=Oparte na czasie
addAttribute=Dodaj atrybut
clientScopeSearch.protocol=Protokół
initialAccessTokenDetails=Informacje o tokenie początkowym dostępu
noMessageBundles=Brak pakietów wiadomości
noTranslations=Brak tłuamczeń
deleteProvider=Usunąć dostawcę?
inputTypeSize=Rozmiar pola wejściowego
createAttributeSubTitle=Utwórz nowy atrybut
@ -2916,3 +2916,4 @@ invalidEmailMessage='{{0}}': Nieprawidłowy adres e-mail.
missingLastNameMessage='{{0}}': Proszę podać nazwisko.
missingEmailMessage='{{0}}': Proszę podać adres e-mail.
missingPasswordMessage='{{0}}': Proszę podać hasło.
addTranslation=Dodaj tłumaczenie

View file

@ -1,625 +0,0 @@
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
ActionGroup,
AlertVariant,
Button,
Divider,
FormGroup,
PageSection,
Select,
SelectGroup,
SelectOption,
SelectVariant,
Switch,
TextContent,
ToolbarItem,
} from "@patternfly/react-core";
import { SearchIcon } from "@patternfly/react-icons";
import {
EditableTextCell,
IEditableTextCell,
IRow,
IRowCell,
RowEditType,
RowErrors,
Table,
TableBody,
TableHeader,
TableVariant,
applyCellEdits,
cancelCellEdits,
validateCellEdits,
} from "@patternfly/react-table";
import { cloneDeep, isEqual, uniqWith } from "lodash-es";
import { useEffect, useMemo, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormPanel, HelpItem } from "ui-shared";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form/FormAccess";
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
import { useRealm } from "../context/realm-context/RealmContext";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { useWhoAmI } from "../context/whoami/WhoAmI";
import { DEFAULT_LOCALE } from "../i18n/i18n";
import { convertToFormValues, localeToDisplayName } from "../util";
import { useFetch } from "../utils/useFetch";
import useLocaleSort, { mapByKey } from "../utils/useLocaleSort";
import { AddMessageBundleModal } from "./AddMessageBundleModal";
type LocalizationTabProps = {
save: (realm: RealmRepresentation) => void;
refresh: () => void;
realm: RealmRepresentation;
};
type LocaleSpecificEntry = {
key: string;
value: string;
};
export enum RowEditAction {
Save = "save",
Cancel = "cancel",
Edit = "edit",
Delete = "delete",
}
export type BundleForm = {
key: string;
value: string;
messageBundle: KeyValueType;
};
export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
const { t } = useTranslation();
const [addMessageBundleModalOpen, setAddMessageBundleModalOpen] =
useState(false);
const [supportedLocalesOpen, setSupportedLocalesOpen] = useState(false);
const [defaultLocaleOpen, setDefaultLocaleOpen] = useState(false);
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
const [selectMenuLocale, setSelectMenuLocale] = useState(DEFAULT_LOCALE);
const { setValue, getValues, control, handleSubmit, formState } = useForm();
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
const [messageBundles, setMessageBundles] = useState<LocaleSpecificEntry[]>(
[],
);
const [tableRows, setTableRows] = useState<IRow[]>([]);
const themeTypes = useServerInfo().themes!;
const allLocales = useMemo(() => {
const locales = Object.values(themeTypes).flatMap((theme) =>
theme.flatMap(({ locales }) => (locales ? locales : [])),
);
return Array.from(new Set(locales));
}, [themeTypes]);
const bundleForm = useForm<BundleForm>({ mode: "onChange" });
const { addAlert, addError } = useAlerts();
const { realm: currentRealm } = useRealm();
const { whoAmI } = useWhoAmI();
const localeSort = useLocaleSort();
const defaultSupportedLocales = realm.supportedLocales?.length
? realm.supportedLocales
: [DEFAULT_LOCALE];
const setupForm = () => {
convertToFormValues(realm, setValue);
setValue("supportedLocales", defaultSupportedLocales);
};
useEffect(setupForm, []);
const watchSupportedLocales: string[] = useWatch({
control,
name: "supportedLocales",
defaultValue: defaultSupportedLocales,
});
const internationalizationEnabled = useWatch({
control,
name: "internationalizationEnabled",
defaultValue: realm.internationalizationEnabled,
});
const [tableKey, setTableKey] = useState(0);
const [max, setMax] = useState(10);
const [first, setFirst] = useState(0);
const [filter, setFilter] = useState("");
const refreshTable = () => {
setTableKey(tableKey + 1);
};
useFetch(
async () => {
let result = await adminClient.realms
.getRealmLocalizationTexts({
first,
max,
realm: realm.realm!,
selectedLocale:
selectMenuLocale ||
getValues("defaultLocale") ||
whoAmI.getLocale(),
})
// prevents server error in dev mode due to snowpack
.catch(() => []);
const searchInBundles = (idx: number) => {
return Object.entries(result).filter((i) => i[idx].includes(filter));
};
if (filter) {
const filtered = uniqWith(
searchInBundles(0).concat(searchInBundles(1)),
isEqual,
);
result = Object.fromEntries(filtered);
}
return { result };
},
({ result }) => {
const bundles = localeSort(
Object.entries(result).map<LocaleSpecificEntry>(([key, value]) => ({
key,
value,
})),
mapByKey("key"),
).slice(first, first + max + 1);
setMessageBundles(bundles);
const updatedRows = bundles.map<IRow>((messageBundle) => ({
rowEditBtnAriaLabel: () =>
t("rowEditBtnAriaLabel", {
messageBundle: messageBundle.value,
}),
rowSaveBtnAriaLabel: () =>
t("rowSaveBtnAriaLabel", {
messageBundle: messageBundle.value,
}),
rowCancelBtnAriaLabel: () =>
t("rowCancelBtnAriaLabel", {
messageBundle: messageBundle.value,
}),
cells: [
{
title: (value, rowIndex, cellIndex, props) => (
<EditableTextCell
value={value!}
rowIndex={rowIndex!}
cellIndex={cellIndex!}
props={props}
isDisabled
handleTextInputChange={handleTextInputChange}
inputAriaLabel={messageBundle.key}
/>
),
props: {
value: messageBundle.key,
},
},
{
title: (value, rowIndex, cellIndex, props) => (
<EditableTextCell
value={value!}
rowIndex={rowIndex!}
cellIndex={cellIndex!}
props={props}
handleTextInputChange={handleTextInputChange}
inputAriaLabel={messageBundle.value}
/>
),
props: {
value: messageBundle.value,
},
},
],
}));
setTableRows(updatedRows);
return bundles;
},
[tableKey, filter, first, max],
);
const handleTextInputChange = (
newValue: string,
evt: any,
rowIndex: number,
cellIndex: number,
) => {
setTableRows((prev) => {
const newRows = cloneDeep(prev);
const textCell = newRows[rowIndex]?.cells?.[
cellIndex
] as IEditableTextCell;
textCell.props.editableValue = newValue;
return newRows;
});
};
const updateEditableRows = async (
type: RowEditType,
rowIndex?: number,
validationErrors?: RowErrors,
) => {
if (rowIndex === undefined) {
return;
}
const newRows = cloneDeep(tableRows);
let newRow: IRow;
const invalid =
!!validationErrors && Object.keys(validationErrors).length > 0;
if (invalid) {
newRow = validateCellEdits(newRows[rowIndex], type, validationErrors);
} else if (type === RowEditAction.Cancel) {
newRow = cancelCellEdits(newRows[rowIndex]);
} else {
newRow = applyCellEdits(newRows[rowIndex], type);
}
newRows[rowIndex] = newRow;
// Update the copy of the retrieved data set so we can save it when the user saves changes
if (!invalid && type === RowEditAction.Save) {
const key = (newRow.cells?.[0] as IRowCell).props.value;
const value = (newRow.cells?.[1] as IRowCell).props.value;
// We only have one editable value, otherwise we'd need to save each
try {
await adminClient.realms.addLocalization(
{
realm: realm.realm!,
selectedLocale:
selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE,
key,
},
value,
);
addAlert(t("updateMessageBundleSuccess"), AlertVariant.success);
} catch (error) {
addAlert(t("updateMessageBundleError"), AlertVariant.danger);
}
}
setTableRows(newRows);
};
const handleModalToggle = () => {
setAddMessageBundleModalOpen(!addMessageBundleModalOpen);
};
const options = [
<SelectGroup label={t("defaultLocale")} key="group1">
<SelectOption key={DEFAULT_LOCALE} value={DEFAULT_LOCALE}>
{localeToDisplayName(DEFAULT_LOCALE, whoAmI.getLocale())}
</SelectOption>
</SelectGroup>,
<Divider key="divider" />,
<SelectGroup label={t("supportedLocales")} key="group2">
{watchSupportedLocales.map((locale) => (
<SelectOption key={locale} value={locale}>
{localeToDisplayName(locale, whoAmI.getLocale())}
</SelectOption>
))}
</SelectGroup>,
];
const addKeyValue = async (pair: KeyValueType): Promise<void> => {
try {
await adminClient.realms.addLocalization(
{
realm: currentRealm!,
selectedLocale:
selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE,
key: pair.key,
},
pair.value,
);
adminClient.setConfig({
realmName: currentRealm!,
});
refreshTable();
addAlert(t("addMessageBundleSuccess"), AlertVariant.success);
} catch (error) {
addError(t("addMessageBundleError"), error);
}
};
const deleteKey = async (key: string) => {
try {
await adminClient.realms.deleteRealmLocalizationTexts({
realm: currentRealm!,
selectedLocale: selectMenuLocale,
key,
});
refreshTable();
addAlert(t("deleteMessageBundleSuccess"));
} catch (error) {
addError("deleteMessageBundleError", error);
}
};
return (
<>
{addMessageBundleModalOpen && (
<AddMessageBundleModal
handleModalToggle={handleModalToggle}
save={(pair: any) => {
addKeyValue(pair);
handleModalToggle();
}}
form={bundleForm}
/>
)}
<PageSection variant="light">
<FormAccess
isHorizontal
role="manage-realm"
className="pf-u-mt-lg"
onSubmit={handleSubmit(save)}
>
<FormGroup
label={t("internationalization")}
fieldId="kc-internationalization"
labelIcon={
<HelpItem
helpText={t("internationalizationHelp")}
fieldLabelId="internationalization"
/>
}
>
<Controller
name="internationalizationEnabled"
control={control}
defaultValue={realm.internationalizationEnabled}
render={({ field }) => (
<Switch
id="kc-l-internationalization"
label={t("enabled")}
labelOff={t("disabled")}
isChecked={field.value}
data-testid={
field.value
? "internationalization-enabled"
: "internationalization-disabled"
}
onChange={field.onChange}
aria-label={t("internationalization")}
/>
)}
/>
</FormGroup>
{internationalizationEnabled && (
<>
<FormGroup
label={t("supportedLocales")}
fieldId="kc-l-supported-locales"
>
<Controller
name="supportedLocales"
control={control}
defaultValue={defaultSupportedLocales}
render={({ field }) => (
<Select
toggleId="kc-l-supported-locales"
onToggle={(open) => {
setSupportedLocalesOpen(open);
}}
onSelect={(_, v) => {
const option = v as string;
if (field.value.includes(option)) {
field.onChange(
field.value.filter(
(item: string) => item !== option,
),
);
} else {
field.onChange([...field.value, option]);
}
}}
onClear={() => {
field.onChange([]);
}}
selections={field.value}
variant={SelectVariant.typeaheadMulti}
aria-label={t("supportedLocales")}
isOpen={supportedLocalesOpen}
placeholderText={t("selectLocales")}
>
{allLocales.map((locale) => (
<SelectOption
selected={field.value.includes(locale)}
key={locale}
value={locale}
>
{localeToDisplayName(locale, whoAmI.getLocale())}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
<FormGroup
label={t("defaultLocale")}
fieldId="kc-l-default-locale"
>
<Controller
name="defaultLocale"
control={control}
defaultValue={DEFAULT_LOCALE}
render={({ field }) => (
<Select
toggleId="kc-default-locale"
onToggle={() => setDefaultLocaleOpen(!defaultLocaleOpen)}
onSelect={(_, value) => {
field.onChange(value as string);
setDefaultLocaleOpen(false);
}}
selections={
field.value
? localeToDisplayName(field.value, whoAmI.getLocale())
: realm.defaultLocale !== ""
? localeToDisplayName(
realm.defaultLocale || DEFAULT_LOCALE,
whoAmI.getLocale(),
)
: t("placeholderText")
}
variant={SelectVariant.single}
aria-label={t("defaultLocale")}
isOpen={defaultLocaleOpen}
placeholderText={t("placeholderText")}
data-testid="select-default-locale"
>
{watchSupportedLocales.map((locale, idx) => (
<SelectOption
key={`default-locale-${idx}`}
value={locale}
>
{localeToDisplayName(locale, whoAmI.getLocale())}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
</>
)}
<ActionGroup>
<Button
variant="primary"
isDisabled={!formState.isDirty}
type="submit"
data-testid="localization-tab-save"
>
{t("save")}
</Button>
<Button variant="link" onClick={setupForm}>
{t("revert")}
</Button>
</ActionGroup>
</FormAccess>
<FormPanel className="kc-message-bundles" title="Edit message bundles">
<TextContent className="messageBundleDescription">
{t("messageBundleDescription")}
</TextContent>
<div className="tableBorder">
<PaginatingTableToolbar
count={messageBundles.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(first, max) => {
setFirst(first);
setMax(max);
}}
inputGroupName={"search"}
inputGroupOnEnter={(search) => {
setFilter(search);
setFirst(0);
setMax(10);
}}
inputGroupPlaceholder={t("searchForMessageBundle")}
toolbarItem={
<Button
data-testid="add-bundle-button"
onClick={() => setAddMessageBundleModalOpen(true)}
>
{t("addMessageBundle")}
</Button>
}
searchTypeComponent={
<ToolbarItem>
<Select
width={180}
data-testid="filter-by-locale-select"
isOpen={filterDropdownOpen}
className="kc-filter-by-locale-select"
variant={SelectVariant.single}
isDisabled={!internationalizationEnabled}
onToggle={(isExpanded) => setFilterDropdownOpen(isExpanded)}
onSelect={(_, value) => {
setSelectMenuLocale(value.toString());
setSelectMenuValueSelected(true);
refreshTable();
setFilterDropdownOpen(false);
}}
selections={
selectMenuValueSelected
? localeToDisplayName(
selectMenuLocale,
whoAmI.getLocale(),
)
: realm.defaultLocale !== ""
? localeToDisplayName(
DEFAULT_LOCALE,
whoAmI.getLocale(),
)
: t("placeholderText")
}
>
{options}
</Select>
</ToolbarItem>
}
>
{messageBundles.length === 0 && !filter && (
<ListEmptyState
hasIcon
message={t("noMessageBundles")}
instructions={t("noMessageBundlesInstructions")}
onPrimaryAction={handleModalToggle}
/>
)}
{messageBundles.length === 0 && filter && (
<ListEmptyState
hasIcon
icon={SearchIcon}
isSearchVariant
message={t("noSearchResults")}
instructions={t("noSearchResultsInstructions")}
/>
)}
{messageBundles.length !== 0 && (
<Table
aria-label={t("editableRowsTable")}
data-testid="editable-rows-table"
variant={TableVariant.compact}
cells={[t("key"), t("value")]}
rows={tableRows}
onRowEdit={(_, type, _b, rowIndex, validation) =>
updateEditableRows(type, rowIndex, validation)
}
actions={[
{
title: t("delete"),
onClick: (_, row) =>
deleteKey(
(tableRows[row].cells?.[0] as IRowCell).props.value,
),
},
]}
>
<TableHeader />
<TableBody />
</Table>
)}
</PaginatingTableToolbar>
</div>
</FormPanel>
</PageSection>
</>
);
};

View file

@ -231,17 +231,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
<Tab
id="realm-overrides"
eventKey={1}
title={
<TabTitleText>
{t("realmOverrides")}{" "}
<HelpItem
fieldLabelId="realm-overrides"
helpText={t("realmOverridesHelp")}
noVerticalAlign={false}
unWrap
/>
</TabTitleText>
}
title={<TabTitleText>{t("realmOverrides")} </TabTitleText>}
data-testid="rs-localization-realm-overrides-tab"
>
<RealmOverrides
@ -253,17 +243,7 @@ export const LocalizationTab = ({ save, realm }: LocalizationTabProps) => {
<Tab
id="effective-message-bundles"
eventKey={2}
title={
<TabTitleText>
{t("effectiveMessageBundles")}
<HelpItem
fieldLabelId="effective-message-bundles"
helpText={t("effectiveMessageBundlesHelp")}
noVerticalAlign={false}
unWrap
/>
</TabTitleText>
}
title={<TabTitleText>{t("effectiveMessageBundles")}</TabTitleText>}
data-testid="rs-localization-effective-message-bundles-tab"
>
<EffectiveMessageBundles

View file

@ -12,7 +12,10 @@ import {
SelectGroup,
SelectOption,
SelectVariant,
TextContent,
Text,
ToolbarItem,
TextVariants,
} from "@patternfly/react-core";
import {
CheckIcon,
@ -21,6 +24,7 @@ import {
TimesIcon,
} from "@patternfly/react-icons";
import {
ActionsColumn,
IRow,
IRowCell,
Table,
@ -323,6 +327,11 @@ export const RealmOverrides = ({
form={bundleForm}
/>
)}
<TextContent>
<Text className="pf-u-mt-lg pf-u-ml-md" component={TextVariants.p}>
{t("realmOverridesDescription")}
</Text>
</TextContent>
<PaginatingTableToolbar
count={messageBundles.length}
first={first}
@ -339,14 +348,14 @@ export const RealmOverrides = ({
setFirst(0);
setMax(10);
}}
inputGroupPlaceholder={t("searchForMessageBundle")}
inputGroupPlaceholder={t("searchForTranslation")}
toolbarItem={
<>
<Button
data-testid="add-bundle-button"
data-testid="add-translationBtn"
onClick={() => setAddMessageBundleModalOpen(true)}
>
{t("addMessageBundle")}
{t("addTranslation")}
</Button>
<ToolbarItem>
<Dropdown
@ -408,8 +417,8 @@ export const RealmOverrides = ({
{messageBundles.length === 0 && !filter && (
<ListEmptyState
hasIcon
message={t("noMessageBundles")}
instructions={t("noMessageBundlesInstructions")}
message={t("noTranslations")}
instructions={t("noTranslationsInstructions")}
onPrimaryAction={handleModalToggle}
/>
)}
@ -544,20 +553,19 @@ export const RealmOverrides = ({
</Form>
</Td>
<Td isActionCell>
<Dropdown
toggle={
<KebabToggle
className="pf-m-plain"
data-testid="realmOverrides-deleteKebabToggle"
/>
}
onClick={() => {
<ActionsColumn
items={[
{
title: t("delete"),
onClick: () => {
setSelectedRowKeys([
(row.cells?.[0] as IRowCell).props.value,
]);
toggleDeleteDialog();
setKebabOpen(false);
}}
},
},
]}
/>
</Td>
</Tr>