Edit message bundle values (#1663)

* message bundles are now editable

* update alert text in tests

* delete realm settings test

* remove updatedMessageBundles

* remove unused usefetch

* update lodash import

* fix add locale test

* fix add bundle

* cypress tests

* fix data test id on button

* remove unused isAgtive prop

* fix lint errors

* unskip tests

* refactor KDT to Table with PaginatedToolbar

* unskip tests

* uncomment

* revert KDT

* remove unused onSelect

* Apply suggestions from code review

Co-authored-by: Jon Koops <jonkoops@gmail.com>

* PR feedback

* PR feedback

* remove useEffect

* fix localization test

* rewrite localization test

* switch test

* unskip

* force

* PR feedback

* package lock

* remove true

Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
Jenny 2021-12-21 01:34:44 -05:00 committed by GitHub
parent 2276311334
commit 05af84f20f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 279 additions and 554 deletions

View file

@ -79,16 +79,11 @@ describe("Realm settings events tab tests", () => {
};
const addBundle = () => {
const localizationUrl = `/auth/admin/realms/${realmName}/localization/en`;
cy.intercept(localizationUrl).as("localizationFetch");
realmSettingsPage.addKeyValuePair(
"key_" + (Math.random() + 1).toString(36).substring(7),
"value_" + (Math.random() + 1).toString(36).substring(7)
);
cy.wait(["@localizationFetch"]);
return this;
};
@ -177,10 +172,24 @@ describe("Realm settings events tab tests", () => {
cy.findByTestId("rs-localization-tab").click();
cy.findByTestId("internationalization-disabled").click({ force: true });
cy.get(realmSettingsPage.supportedLocalesTypeahead)
.click()
.get(".pf-c-select__menu-item")
.contains("Dansk")
.click();
cy.get("#kc-l-supported-locales").click();
cy.findByTestId("localization-tab-save").click();
cy.findByTestId("add-bundle-button").click({ force: true });
addBundle();
masthead.checkNotificationMessage(
"Success! The localization text has been created."
"Success! The message bundle has been added."
);
});

View file

@ -48,6 +48,9 @@ export default class RealmSettingsPage {
selectDefaultLocale = "select-default-locale";
defaultLocaleList = "select-default-locale > div > ul";
supportedLocalesTypeahead =
"#kc-l-supported-locales-select-multi-typeahead-typeahead";
supportedLocalesToggle = "#kc-l-supported-locales";
emailSaveBtn = "email-tab-save";
managedAccessSwitch = "user-managed-access-switch";
userRegSwitch = "user-reg-switch";
@ -75,7 +78,7 @@ export default class RealmSettingsPage {
testConnectionButton = "test-connection-button";
modalTestConnectionButton = "modal-test-connection-button";
emailAddressInput = "email-address-input";
addBundleButton = "no-message-bundles-empty-action";
addBundleButton = "add-bundle-button";
confirmAddBundle = "add-bundle-confirm-button";
keyInput = "key-input";
valueInput = "value-input";

489
package-lock.json generated
View file

@ -7,7 +7,7 @@
"name": "keycloak-admin-ui",
"license": "Apache",
"dependencies": {
"@keycloak/keycloak-admin-client": "^16.0.0-dev.61",
"@keycloak/keycloak-admin-client": "^16.0.0-dev.66",
"@patternfly/patternfly": "^4.164.2",
"@patternfly/react-code-editor": "^4.16.4",
"@patternfly/react-core": "^4.175.4",
@ -3407,9 +3407,9 @@
}
},
"node_modules/@keycloak/keycloak-admin-client": {
"version": "16.0.0-dev.61",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.61.tgz",
"integrity": "sha512-xeJTOQevOeHe8bQfu3/6Y9Lsort3Ep/VgzieUKmBHdR25kkMyQEUtZGX/7nQKYofjONG2m/KWivNwf0OZp+rhg==",
"version": "16.0.0-dev.66",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.66.tgz",
"integrity": "sha512-3roWy7XVltOHRq7vDihJDf5LFOgA1Hk1IhiokjWPEHD/Igmo8xn0b1ITgiNn4kHEnmP+7XDJNxhWEL/yFPIBHA==",
"dependencies": {
"axios": "^0.24.0",
"camelize-ts": "^1.0.8",
@ -6739,16 +6739,6 @@
"node": ">=8"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@ -9243,90 +9233,6 @@
"esbuild-windows-arm64": "0.14.3"
}
},
"node_modules/esbuild-android-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.3.tgz",
"integrity": "sha512-v/vdnGJiSGWOAXzg422T9qb4S+P3tOaYtc5n3FDR27Bh3/xQDS7PdYz/yY7HhOlVp0eGwWNbPHEi8FcEhXjsuw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"peer": true
},
"node_modules/esbuild-darwin-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.3.tgz",
"integrity": "sha512-swY5OtEg6cfWdgc/XEjkBP7wXSyXXeZHEsWMdh1bDiN1D6GmRphk9SgKFKTj+P3ZHhOGIcC1+UdIwHk5bUcOig==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"peer": true
},
"node_modules/esbuild-darwin-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.3.tgz",
"integrity": "sha512-6i9dXPk8oT87wF6VHmwzSad76eMRU2Rt+GXrwF3Y4DCJgnPssJbabNQ9gurkuEX8M0YnEyJF0d1cR7rpTzcEiA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"peer": true
},
"node_modules/esbuild-freebsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.3.tgz",
"integrity": "sha512-WDY5ENsmyceeE+95U3eI+FM8yARY5akWkf21M/x/+v2P5OVsYqCYELglSeAI5Y7bhteCVV3g4i2fRqtkmprdSA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"peer": true
},
"node_modules/esbuild-freebsd-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.3.tgz",
"integrity": "sha512-4BEEGcP0wBzg04pCCWXlgaPuksQHHfwHvYgCIsi+7IsuB17ykt6MHhTkHR5b5pjI/jNtRhPfMsDODUyftQJgvw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"peer": true
},
"node_modules/esbuild-linux-32": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.3.tgz",
"integrity": "sha512-8yhsnjLG/GwCA1RAIndjmCHWViRB2Ol0XeOh2fCXS9qF8tlVrJB7qAiHZpm2vXx+yjOA/bFLTxzU+5pMKqkn5A==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/esbuild-linux-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.3.tgz",
@ -9341,146 +9247,6 @@
],
"peer": true
},
"node_modules/esbuild-linux-arm": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.3.tgz",
"integrity": "sha512-YcMvJHAQnWrWKb+eLxN9e/iWUC/3w01UF/RXuMknqOW3prX8UQ63QknWz9/RI8BY/sdrdgPEbSmsTU2jy2cayQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/esbuild-linux-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.3.tgz",
"integrity": "sha512-wPLyRoqoV/tEMQ7M24DpAmCMyKqBmtgZY35w2tXM8X5O5b2Ohi7fkPSmd6ZgLIxZIApWt88toA8RT0S7qoxcOA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/esbuild-linux-mips64le": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.3.tgz",
"integrity": "sha512-DdmfM5rcuoqjQL3px5MbquAjZWnySB5LdTrg52SSapp0gXMnGcsM6GY2WVta02CMKn5qi7WPVG4WbqTWE++tJw==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/esbuild-linux-ppc64le": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.3.tgz",
"integrity": "sha512-ujdqryj0m135Ms9yaNDVFAcLeRtyftM/v2v7Osji5zElf2TivSMdFxdrYnYICuHfkm8c8gHg1ncwqitL0r+nnA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"peer": true
},
"node_modules/esbuild-netbsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.3.tgz",
"integrity": "sha512-Z/UB9OUdwo1KDJCSGnVueDuKowRZRkduLvRMegHtDBHC3lS5LfZ3RdM1i+4MMN9iafyk8Q9FNcqIXI178ZujvA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"peer": true
},
"node_modules/esbuild-openbsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.3.tgz",
"integrity": "sha512-9I1uoMDeogq3zQuTe3qygmXYjImnvc6rBn51LLbLniQDlfvqHPBMnAZ/5KshwtXXIIMkCwByytDZdiuzRRlTvQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"peer": true
},
"node_modules/esbuild-sunos-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.3.tgz",
"integrity": "sha512-pldqx/Adxl4V4ymiyKxOOyJmHn6nUIo3wqk2xBx07iDgmL2XTcDDQd7N4U4QGu9LnYN4ZF+8IdOYa3oRRpbjtg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"peer": true
},
"node_modules/esbuild-windows-32": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.3.tgz",
"integrity": "sha512-AqzvA/KbkC2m3kTXGpljLin3EttRbtoPTfBn6w6n2m9MWkTEbhQbE1ONoOBxhO5tExmyJdL/6B87TJJD5jEFBQ==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"peer": true
},
"node_modules/esbuild-windows-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.3.tgz",
"integrity": "sha512-HGg3C6113zLGB5hN41PROTnBuoh/arG2lQdOird6xFl9giff1cAfMQOUJUfODKD57dDqHjQ1YGW8gOkg0/IrWw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"peer": true
},
"node_modules/esbuild-windows-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.3.tgz",
"integrity": "sha512-qB2izYu4VpigGnOrAN2Yv7ICYLZWY/AojZtwFfteViDnHgW4jXPYkHQIXTISJbRz25H2cYiv+MfRQYK31RNjlw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"peer": true
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -10605,13 +10371,6 @@
"node": ">=4"
}
},
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -10979,20 +10738,6 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -15561,13 +15306,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"dev": true,
"optional": true
},
"node_modules/nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
@ -17949,21 +17687,6 @@
"@rollup/plugin-inject": "^4.0.0"
}
},
"node_modules/rollup/node_modules/fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"deprecated": "\"Please update to latest v2.3 or v2.2\"",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
@ -21063,25 +20786,6 @@
"node": ">=0.10.0"
}
},
"node_modules/watchpack-chokidar2/node_modules/fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"dependencies": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
},
"engines": {
"node": ">= 4.0"
}
},
"node_modules/watchpack-chokidar2/node_modules/glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@ -24141,9 +23845,9 @@
}
},
"@keycloak/keycloak-admin-client": {
"version": "16.0.0-dev.61",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.61.tgz",
"integrity": "sha512-xeJTOQevOeHe8bQfu3/6Y9Lsort3Ep/VgzieUKmBHdR25kkMyQEUtZGX/7nQKYofjONG2m/KWivNwf0OZp+rhg==",
"version": "16.0.0-dev.66",
"resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-16.0.0-dev.66.tgz",
"integrity": "sha512-3roWy7XVltOHRq7vDihJDf5LFOgA1Hk1IhiokjWPEHD/Igmo8xn0b1ITgiNn4kHEnmP+7XDJNxhWEL/yFPIBHA==",
"requires": {
"axios": "^0.24.0",
"camelize-ts": "^1.0.8",
@ -26915,16 +26619,6 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
@ -28916,54 +28610,6 @@
"esbuild-windows-arm64": "0.14.3"
}
},
"esbuild-android-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.3.tgz",
"integrity": "sha512-v/vdnGJiSGWOAXzg422T9qb4S+P3tOaYtc5n3FDR27Bh3/xQDS7PdYz/yY7HhOlVp0eGwWNbPHEi8FcEhXjsuw==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-darwin-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.3.tgz",
"integrity": "sha512-swY5OtEg6cfWdgc/XEjkBP7wXSyXXeZHEsWMdh1bDiN1D6GmRphk9SgKFKTj+P3ZHhOGIcC1+UdIwHk5bUcOig==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-darwin-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.3.tgz",
"integrity": "sha512-6i9dXPk8oT87wF6VHmwzSad76eMRU2Rt+GXrwF3Y4DCJgnPssJbabNQ9gurkuEX8M0YnEyJF0d1cR7rpTzcEiA==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-freebsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.3.tgz",
"integrity": "sha512-WDY5ENsmyceeE+95U3eI+FM8yARY5akWkf21M/x/+v2P5OVsYqCYELglSeAI5Y7bhteCVV3g4i2fRqtkmprdSA==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-freebsd-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.3.tgz",
"integrity": "sha512-4BEEGcP0wBzg04pCCWXlgaPuksQHHfwHvYgCIsi+7IsuB17ykt6MHhTkHR5b5pjI/jNtRhPfMsDODUyftQJgvw==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-linux-32": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.3.tgz",
"integrity": "sha512-8yhsnjLG/GwCA1RAIndjmCHWViRB2Ol0XeOh2fCXS9qF8tlVrJB7qAiHZpm2vXx+yjOA/bFLTxzU+5pMKqkn5A==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-linux-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.3.tgz",
@ -28972,86 +28618,6 @@
"optional": true,
"peer": true
},
"esbuild-linux-arm": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.3.tgz",
"integrity": "sha512-YcMvJHAQnWrWKb+eLxN9e/iWUC/3w01UF/RXuMknqOW3prX8UQ63QknWz9/RI8BY/sdrdgPEbSmsTU2jy2cayQ==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-linux-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.3.tgz",
"integrity": "sha512-wPLyRoqoV/tEMQ7M24DpAmCMyKqBmtgZY35w2tXM8X5O5b2Ohi7fkPSmd6ZgLIxZIApWt88toA8RT0S7qoxcOA==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-linux-mips64le": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.3.tgz",
"integrity": "sha512-DdmfM5rcuoqjQL3px5MbquAjZWnySB5LdTrg52SSapp0gXMnGcsM6GY2WVta02CMKn5qi7WPVG4WbqTWE++tJw==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-linux-ppc64le": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.3.tgz",
"integrity": "sha512-ujdqryj0m135Ms9yaNDVFAcLeRtyftM/v2v7Osji5zElf2TivSMdFxdrYnYICuHfkm8c8gHg1ncwqitL0r+nnA==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-netbsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.3.tgz",
"integrity": "sha512-Z/UB9OUdwo1KDJCSGnVueDuKowRZRkduLvRMegHtDBHC3lS5LfZ3RdM1i+4MMN9iafyk8Q9FNcqIXI178ZujvA==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-openbsd-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.3.tgz",
"integrity": "sha512-9I1uoMDeogq3zQuTe3qygmXYjImnvc6rBn51LLbLniQDlfvqHPBMnAZ/5KshwtXXIIMkCwByytDZdiuzRRlTvQ==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-sunos-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.3.tgz",
"integrity": "sha512-pldqx/Adxl4V4ymiyKxOOyJmHn6nUIo3wqk2xBx07iDgmL2XTcDDQd7N4U4QGu9LnYN4ZF+8IdOYa3oRRpbjtg==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-windows-32": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.3.tgz",
"integrity": "sha512-AqzvA/KbkC2m3kTXGpljLin3EttRbtoPTfBn6w6n2m9MWkTEbhQbE1ONoOBxhO5tExmyJdL/6B87TJJD5jEFBQ==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-windows-64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.3.tgz",
"integrity": "sha512-HGg3C6113zLGB5hN41PROTnBuoh/arG2lQdOird6xFl9giff1cAfMQOUJUfODKD57dDqHjQ1YGW8gOkg0/IrWw==",
"dev": true,
"optional": true,
"peer": true
},
"esbuild-windows-arm64": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.3.tgz",
"integrity": "sha512-qB2izYu4VpigGnOrAN2Yv7ICYLZWY/AojZtwFfteViDnHgW4jXPYkHQIXTISJbRz25H2cYiv+MfRQYK31RNjlw==",
"dev": true,
"optional": true,
"peer": true
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -29911,13 +29477,6 @@
"integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=",
"dev": true
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@ -30182,13 +29741,6 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -33639,13 +33191,6 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"dev": true,
"optional": true
},
"nanoid": {
"version": "3.1.30",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz",
@ -35484,15 +35029,6 @@
"dev": true,
"requires": {
"fsevents": "~2.1.2"
},
"dependencies": {
"fsevents": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
"dev": true,
"optional": true
}
}
},
"rollup-plugin-polyfill-node": {
@ -37942,17 +37478,6 @@
}
}
},
"fsevents": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true,
"requires": {
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",

View file

@ -23,7 +23,7 @@
"prepare": "husky install"
},
"dependencies": {
"@keycloak/keycloak-admin-client": "^16.0.0-dev.61",
"@keycloak/keycloak-admin-client": "^16.0.0-dev.66",
"@patternfly/patternfly": "^4.164.2",
"@patternfly/react-code-editor": "^4.16.4",
"@patternfly/react-core": "^4.175.4",

View file

@ -1,5 +1,6 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { cloneDeep, isEqual, uniqWith } from "lodash";
import { Controller, useForm, useFormContext, useWatch } from "react-hook-form";
import {
ActionGroup,
@ -21,14 +22,32 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/r
import { FormAccess } from "../components/form-access/FormAccess";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
import { FormPanel } from "../components/scroll-form/FormPanel";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
import { useAdminClient } from "../context/auth/AdminClient";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { AddMessageBundleModal } from "./AddMessageBundleModal";
import { useAlerts } from "../components/alert/Alerts";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { useRealm } from "../context/realm-context/RealmContext";
import { DEFAULT_LOCALE } from "../i18n";
import {
EditableTextCell,
validateCellEdits,
cancelCellEdits,
applyCellEdits,
RowErrors,
RowEditType,
IRowCell,
TableBody,
TableHeader,
Table,
TableVariant,
IRow,
IEditableTextCell,
} from "@patternfly/react-table";
import type { EditableTextCellProps } from "@patternfly/react-table/dist/esm/components/Table/base";
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
import { SearchIcon } from "@patternfly/react-icons";
import { useWhoAmI } from "../context/whoami/WhoAmI";
type LocalizationTabProps = {
save: (realm: RealmRepresentation) => void;
@ -39,6 +58,12 @@ type LocalizationTabProps = {
export type KeyValueType = { key: string; value: string };
export enum RowEditAction {
Save = "save",
Cancel = "cancel",
Edit = "edit",
}
export type BundleForm = {
messageBundle: KeyValueType;
};
@ -60,11 +85,14 @@ export const LocalizationTab = ({
const { getValues, control, handleSubmit, formState } = useFormContext();
const [selectMenuValueSelected, setSelectMenuValueSelected] = useState(false);
const [messageBundles, setMessageBundles] = useState<[string, string][]>([]);
const [tableRows, setTableRows] = useState<IRow[]>([]);
const themeTypes = useServerInfo().themes!;
const bundleForm = useForm<BundleForm>({ mode: "onChange" });
const { addAlert, addError } = useAlerts();
const { realm: currentRealm } = useRealm();
const { whoAmI } = useWhoAmI();
const watchSupportedLocales = useWatch<string[]>({
control,
@ -76,31 +104,170 @@ export const LocalizationTab = ({
defaultValue: false,
});
const loader = async () => {
try {
const result = await adminClient.realms.getRealmLocalizationTexts({
realm: realm.realm!,
selectedLocale:
selectMenuLocale || getValues("defaultLocale") || DEFAULT_LOCALE,
});
const [tableKey, setTableKey] = useState(0);
const [max, setMax] = useState(10);
const [first, setFirst] = useState(0);
const [filter, setFilter] = useState("");
return Object.entries(result);
} catch (error) {
return [];
}
const refreshTable = () => {
setTableKey(tableKey + 1);
};
const tableLoader = async () => {
try {
const result = await adminClient.realms.getRealmLocalizationTexts({
realm: currentRealm,
selectedLocale: selectMenuLocale,
useFetch(
async () => {
let result = await adminClient.realms.getRealmLocalizationTexts({
first,
max,
realm: realm.realm!,
selectedLocale:
selectMenuLocale || getValues("defaultLocale") || whoAmI.getLocale(),
});
return Object.entries(result);
} catch (error) {
return [];
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 = Object.entries(result).slice(first, first + max + 1);
setMessageBundles(bundles);
const updatedRows = bundles.map<IRow>((messageBundle) => ({
rowEditBtnAriaLabel: () =>
t("rowEditBtnAriaLabel", {
messageBundle: messageBundle[1],
}),
rowSaveBtnAriaLabel: () =>
t("rowSaveBtnAriaLabel", {
messageBundle: messageBundle[1],
}),
rowCancelBtnAriaLabel: () =>
t("rowCancelBtnAriaLabel", {
messageBundle: messageBundle[1],
}),
cells: [
{
title: (
value: string,
rowIndex: number,
cellIndex: number,
props
) => (
<EditableTextCell
value={value}
rowIndex={rowIndex}
cellIndex={cellIndex}
props={props}
isDisabled
handleTextInputChange={handleTextInputChange}
inputAriaLabel={messageBundle[0]}
/>
),
props: {
value: messageBundle[0],
},
},
{
title: (
value: string,
rowIndex: number,
cellIndex: number,
props: EditableTextCellProps
) => (
<EditableTextCell
value={value}
rowIndex={rowIndex}
cellIndex={cellIndex}
props={props}
handleTextInputChange={handleTextInputChange}
inputAriaLabel={messageBundle[1]}
/>
),
props: {
value: messageBundle[1],
},
},
],
}));
setTableRows(updatedRows);
return bundles;
},
[tableKey, filter, first, max]
);
const handleTextInputChange = (
newValue: string,
evt: any,
rowIndex: number,
cellIndex: number
) => {
setTableRows((prev) => {
const newRows = cloneDeep(prev);
(
newRows[rowIndex]?.cells?.[cellIndex] as IEditableTextCell
).props.editableValue = newValue;
return newRows;
});
};
const updateEditableRows = async (
evt: any,
type: RowEditType,
isEditable?: boolean | undefined,
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 = () => {
@ -129,17 +296,8 @@ export const LocalizationTab = ({
</SelectGroup>,
];
const [tableKey, setTableKey] = useState(0);
const refreshTable = () => {
setTableKey(new Date().getTime());
};
const addKeyValue = async (pair: KeyValueType): Promise<void> => {
try {
adminClient.setConfig({
requestConfig: { headers: { "Content-Type": "text/plain" } },
});
await adminClient.realms.addLocalization(
{
realm: currentRealm!,
@ -154,9 +312,9 @@ export const LocalizationTab = ({
realmName: currentRealm!,
});
refreshTable();
addAlert(t("pairCreatedSuccess"), AlertVariant.success);
addAlert(t("addMessageBundleSuccess"), AlertVariant.success);
} catch (error) {
addError("realm-settings:pairCreatedError", error);
addError(t("addMessageBundleError"), error);
}
};
@ -331,10 +489,32 @@ export const LocalizationTab = ({
{t("messageBundleDescription")}
</TextContent>
<div className="tableBorder">
<KeycloakDataTable
key={tableKey}
loader={selectMenuValueSelected ? tableLoader : loader}
ariaLabelKey="realm-settings:localization"
<PaginatingTableToolbar
count={messageBundles.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(first, max) => {
setFirst(first);
setMax(max);
}}
inputGroupName={"common:search"}
inputGroupOnEnter={(search) => {
setFilter(search);
setFirst(0);
setMax(10);
}}
inputGroupPlaceholder={t("searchForMessageBundle")}
toolbarItem={
<Button
data-testid="add-bundle-button"
isDisabled={!formState.isSubmitSuccessful}
onClick={() => setAddMessageBundleModalOpen(true)}
>
{t("addMessageBundle")}
</Button>
}
searchTypeComponent={
<ToolbarItem>
<Select
@ -363,37 +543,38 @@ export const LocalizationTab = ({
</Select>
</ToolbarItem>
}
toolbarItem={
<Button
data-testid="add-bundle-button"
isDisabled={!formState.isSubmitSuccessful}
onClick={() => setAddMessageBundleModalOpen(true)}
>
{t("addMessageBundle")}
</Button>
}
searchPlaceholderKey=" "
emptyState={
>
{messageBundles.length === 0 && !filter && (
<ListEmptyState
hasIcon={true}
hasIcon
message={t("noMessageBundles")}
instructions={t("noMessageBundlesInstructions")}
onPrimaryAction={handleModalToggle}
primaryActionText={t("addMessageBundle")}
/>
}
canSelectAll
columns={[
{
name: "Key",
cellRenderer: (row) => row[0],
},
{
name: "Value",
cellRenderer: (row) => row[1],
},
]}
/>
)}
{messageBundles.length === 0 && filter && (
<ListEmptyState
hasIcon
icon={SearchIcon}
isSearchVariant
message={t("common:noSearchResults")}
instructions={t("common:noSearchResultsInstructions")}
/>
)}
{messageBundles.length !== 0 && (
<Table
aria-label={t("editableRowsTable")}
data-testid="editable-rows-table"
variant={TableVariant.compact}
cells={[t("common:key"), t("common:value")]}
rows={tableRows}
onRowEdit={updateEditableRows}
>
<TableHeader />
<TableBody />
</Table>
)}
</PaginatingTableToolbar>
</div>
</FormPanel>
</PageSection>

View file

@ -22,6 +22,7 @@ export default {
disablePolicyConfirm:
"Users and clients can't access the policy if it's disabled. Are you sure you want to continue?",
editProvider: "Edit provider",
editableRowsTable: "Editable rows table",
saveSuccess: "Realm successfully updated",
saveProviderSuccess: "The provider has been saved successfully.",
saveProviderListSuccess:
@ -352,12 +353,18 @@ export default {
convertedToHoursValue: "{{convertedToHours}}",
convertedToMinutesValue: "{{convertedToMinutes}}",
convertedToSecondsValue: "{{convertedToSeconds}}",
pairCreatedSuccess: "Success! The localization text has been created.",
pairCreatedError: "Error creating localization text.",
supportedLocales: "Supported locales",
defaultLocale: "Default locale",
selectLocales: "Select locales",
searchForMessageBundle: "Search for message bundle",
addMessageBundle: "Add message bundle",
addMessageBundleSuccess: "Success! The message bundle has been added.",
rowEditBtnAriaLabel: "Edit {{messageBundle}}",
rowSaveBtnAriaLabel: "Save edits for {{messageBundle}}",
rowCancelBtnAriaLabel: "Cancel edits for {{messageBundle}}",
updateMessageBundleSuccess: "Success! Message bundle updated.",
updateMessageBundleError: "Error updating message bundle.",
addMessageBundleError: "Error creating message bundle, {{error}}",
eventType: "Event saved type",
searchEventType: "Search saved event type",
addSavedTypes: "Add saved types",