diff --git a/NOTICE b/NOTICE index ae98f185ea7..c32e59c63c7 100644 --- a/NOTICE +++ b/NOTICE @@ -7,9 +7,9 @@ The following 3rd-party software packages may be used by or distributed with gooddata-ui-sdk. Any information relevant to third-party vendors listed below are collected using common, reasonable means. -Date generated: 2026-9-10 +Date generated: 2026-9-14 -Revision ID: 134c47a0c4df31513dbb70f1253bfcf3a9a37ca9 +Revision ID: 2ddc75aa28e022016649b61c0188b349322c1327 ================================================================================ ================================================================================ @@ -43212,7 +43212,7 @@ MIT Authors: richiebendall@gmail.com Package Manager: NPM -Project URL: https://www.npmjs.com/package/char-regex +Project URL: https://github.com/Richienb/char-regex#readme Package Download URL: https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz Dependency Path: remark-emoji > node-emoji > char-regex File Path(s): common/config/subspaces/sdk/pnpm-lock.yaml @@ -48198,7 +48198,7 @@ MIT Authors: tituswormer@gmail.com Package Manager: NPM -Project URL: https://www.npmjs.com/package/micromark-extension-gfm-table +Project URL: https://github.com/micromark/micromark-extension-gfm-table#readme Package Download URL: https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz Dependency Path: remark-gfm > micromark-extension-gfm > micromark-extension-gfm-table File Path(s): common/config/subspaces/sdk/pnpm-lock.yaml @@ -64353,4 +64353,4 @@ File Path(s): common/config/subspaces/sdk/pnpm-lock.yaml -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -Report Generated by FOSSA on 2026-9-10 \ No newline at end of file +Report Generated by FOSSA on 2026-9-14 \ No newline at end of file diff --git a/common/autoinstallers/lint-staged/package.json b/common/autoinstallers/lint-staged/package.json index 0832c2fcbbd..8aa4971c39e 100644 --- a/common/autoinstallers/lint-staged/package.json +++ b/common/autoinstallers/lint-staged/package.json @@ -6,6 +6,6 @@ "@gooddata/frontend-npm-scripts": "^1.4.0", "lint-staged": "^10.5.4", "oxfmt": "0.66.0", - "oxlint": "1.57.0" + "oxlint": "1.58.0" } } diff --git a/common/autoinstallers/lint-staged/pnpm-lock.yaml b/common/autoinstallers/lint-staged/pnpm-lock.yaml index f447bb3ef93..ae9587c874b 100644 --- a/common/autoinstallers/lint-staged/pnpm-lock.yaml +++ b/common/autoinstallers/lint-staged/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0 + specifier: 1.58.0 + version: 1.58.0 packages: @@ -149,116 +149,116 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.57.0': - resolution: {integrity: sha512-C7EiyfAJG4B70496eV543nKiq5cH0o/xIh/ufbjQz3SIvHhlDDsyn+mRFh+aW8KskTyUpyH2LGWL8p2oN6bl1A==} + '@oxlint/binding-android-arm-eabi@1.58.0': + resolution: {integrity: sha512-1T7UN3SsWWxpWyWGn1cT3ASNJOo+pI3eUkmEl7HgtowapcV8kslYpFQcYn431VuxghXakPNlbjRwhqmR37PFOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.57.0': - resolution: {integrity: sha512-9i80AresjZ/FZf5xK8tKFbhQnijD4s1eOZw6/FHUwD59HEZbVLRc2C88ADYJfLZrF5XofWDiRX/Ja9KefCLy7w==} + '@oxlint/binding-android-arm64@1.58.0': + resolution: {integrity: sha512-GryzujxuiRv2YFF7bRy8mKcxlbuAN+euVUtGJt9KKbLT8JBUIosamVhcthLh+VEr6KE6cjeVMAQxKAzJcoN7dg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.57.0': - resolution: {integrity: sha512-0eUfhRz5L2yKa9I8k3qpyl37XK3oBS5BvrgdVIx599WZK63P8sMbg+0s4IuxmIiZuBK68Ek+Z+gcKgeYf0otsg==} + '@oxlint/binding-darwin-arm64@1.58.0': + resolution: {integrity: sha512-7/bRSJIwl4GxeZL9rPZ11anNTyUO9epZrfEJH/ZMla3+/gbQ6xZixh9nOhsZ0QwsTW7/5J2A/fHbD1udC5DQQA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.57.0': - resolution: {integrity: sha512-UvrSuzBaYOue+QMAcuDITe0k/Vhj6KZGjfnI6x+NkxBTke/VoM7ZisaxgNY0LWuBkTnd1OmeQfEQdQ48fRjkQg==} + '@oxlint/binding-darwin-x64@1.58.0': + resolution: {integrity: sha512-EqdtJSiHweS2vfILNrpyJ6HUwpEq2g7+4Zx1FPi4hu3Hu7tC3znF6ufbXO8Ub2LD4mGgznjI7kSdku9NDD1Mkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.57.0': - resolution: {integrity: sha512-wtQq0dCoiw4bUwlsNVDJJ3pxJA218fOezpgtLKrbQqUtQJcM9yP8z+I9fu14aHg0uyAxIY+99toL6uBa2r7nxA==} + '@oxlint/binding-freebsd-x64@1.58.0': + resolution: {integrity: sha512-VQt5TH4M42mY20F545G637RKxV/yjwVtKk2vfXuazfReSIiuvWBnv+FVSvIV5fKVTJNjt3GSJibh6JecbhGdBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.57.0': - resolution: {integrity: sha512-qxFWl2BBBFcT4djKa+OtMdnLgoHEJXpqjyGwz8OhW35ImoCwR5qtAGqApNYce5260FQqoAHW8S8eZTjiX67Tsg==} + '@oxlint/binding-linux-arm-gnueabihf@1.58.0': + resolution: {integrity: sha512-fBYcj4ucwpAtjJT3oeBdFBYKvNyjRSK+cyuvBOTQjh0jvKp4yeA4S/D0IsCHus/VPaNG5L48qQkh+Vjy3HL2/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.57.0': - resolution: {integrity: sha512-SQoIsBU7J0bDW15/f0/RvxHfY3Y0+eB/caKBQtNFbuerTiA6JCYx9P1MrrFTwY2dTm/lMgTSgskvCEYk2AtG/Q==} + '@oxlint/binding-linux-arm-musleabihf@1.58.0': + resolution: {integrity: sha512-0BeuFfwlUHlJ1xpEdSD1YO3vByEFGPg36uLjK1JgFaxFb4W6w17F8ET8sz5cheZ4+x5f2xzdnRrrWv83E3Yd8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.57.0': - resolution: {integrity: sha512-jqxYd1W6WMeozsCmqe9Rzbu3SRrGTyGDAipRlRggetyYbUksJqJKvUNTQtZR/KFoJPb+grnSm5SHhdWrywv3RQ==} + '@oxlint/binding-linux-arm64-gnu@1.58.0': + resolution: {integrity: sha512-TXlZgnPTlxrQzxG9ZXU7BNwx1Ilrr17P3GwZY0If2EzrinqRH3zXPc3HrRcBJgcsoZNMuNL5YivtkJYgp467UQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/binding-linux-arm64-musl@1.57.0': - resolution: {integrity: sha512-i66WyEPVEvq9bxRUCJ/MP5EBfnTDN3nhwEdFZFTO5MmLLvzngfWEG3NSdXQzTT3vk5B9i6C2XSIYBh+aG6uqyg==} + '@oxlint/binding-linux-arm64-musl@1.58.0': + resolution: {integrity: sha512-zSoYRo5dxHLcUx93Stl2hW3hSNjPt99O70eRVWt5A1zwJ+FPjeCCANCD2a9R4JbHsdcl11TIQOjyigcRVOH2mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/binding-linux-ppc64-gnu@1.57.0': - resolution: {integrity: sha512-oMZDCwz4NobclZU3pH+V1/upVlJZiZvne4jQP+zhJwt+lmio4XXr4qG47CehvrW1Lx2YZiIHuxM2D4YpkG3KVA==} + '@oxlint/binding-linux-ppc64-gnu@1.58.0': + resolution: {integrity: sha512-NQ0U/lqxH2/VxBYeAIvMNUK1y0a1bJ3ZicqkF2c6wfakbEciP9jvIE4yNzCFpZaqeIeRYaV7AVGqEO1yrfVPjA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@oxlint/binding-linux-riscv64-gnu@1.57.0': - resolution: {integrity: sha512-uoBnjJ3MMEBbfnWC1jSFr7/nSCkcQYa72NYoNtLl1imshDnWSolYCjzb8LVCwYCCfLJXD+0gBLD7fyC14c0+0g==} + '@oxlint/binding-linux-riscv64-gnu@1.58.0': + resolution: {integrity: sha512-X9J+kr3gIC9FT8GuZt0ekzpNUtkBVzMVU4KiKDSlocyQuEgi3gBbXYN8UkQiV77FTusLDPsovjo95YedHr+3yg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxlint/binding-linux-riscv64-musl@1.57.0': - resolution: {integrity: sha512-BdrwD7haPZ8a9KrZhKJRSj6jwCor+Z8tHFZ3PT89Y3Jq5v3LfMfEePeAmD0LOTWpiTmzSzdmyw9ijneapiVHKQ==} + '@oxlint/binding-linux-riscv64-musl@1.58.0': + resolution: {integrity: sha512-CDze3pi1OO3Wvb/QsXjmLEY4XPKGM6kIo82ssNOgmcl1IdndF9VSGAE38YLhADWmOac7fjqhBw82LozuUVxD0Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxlint/binding-linux-s390x-gnu@1.57.0': - resolution: {integrity: sha512-BNs+7ZNsRstVg2tpNxAXfMX/Iv5oZh204dVyb8Z37+/gCh+yZqNTlg6YwCLIMPSk5wLWIGOaQjT0GUOahKYImw==} + '@oxlint/binding-linux-s390x-gnu@1.58.0': + resolution: {integrity: sha512-b/89glbxFaEAcA6Uf1FvCNecBJEgcUTsV1quzrqXM/o4R1M4u+2KCVuyGCayN2UpsRWtGGLb+Ver0tBBpxaPog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@oxlint/binding-linux-x64-gnu@1.57.0': - resolution: {integrity: sha512-AghS18w+XcENcAX0+BQGLiqjpqpaxKJa4cWWP0OWNLacs27vHBxu7TYkv9LUSGe5w8lOJHeMxcYfZNOAPqw2bg==} + '@oxlint/binding-linux-x64-gnu@1.58.0': + resolution: {integrity: sha512-0/yYpkq9VJFCEcuRlrViGj8pJUFFvNS4EkEREaN7CB1EcLXJIaVSSa5eCihwBGXtOZxhnblWgxks9juRdNQI7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/binding-linux-x64-musl@1.57.0': - resolution: {integrity: sha512-E/FV3GB8phu/Rpkhz5T96hAiJlGzn91qX5yj5gU754P5cmVGXY1Jw/VSjDSlZBCY3VHjsVLdzgdkJaomEmcNOg==} + '@oxlint/binding-linux-x64-musl@1.58.0': + resolution: {integrity: sha512-hr6FNvmcAXiH+JxSvaJ4SJ1HofkdqEElXICW9sm3/Rd5eC3t7kzvmLyRAB3NngKO2wzXRCAm4Z/mGWfrsS4X8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/binding-openharmony-arm64@1.57.0': - resolution: {integrity: sha512-xvZ2yZt0nUVfU14iuGv3V25jpr9pov5N0Wr28RXnHFxHCRxNDMtYPHV61gGLhN9IlXM96gI4pyYpLSJC5ClLCQ==} + '@oxlint/binding-openharmony-arm64@1.58.0': + resolution: {integrity: sha512-R+O368VXgRql1K6Xar+FEo7NEwfo13EibPMoTv3sesYQedRXd6m30Dh/7lZMxnrQVFfeo4EOfYIP4FpcgWQNHg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.57.0': - resolution: {integrity: sha512-Z4D8Pd0AyHBKeazhdIXeUUy5sIS3Mo0veOlzlDECg6PhRRKgEsBJCCV1n+keUZtQ04OP+i7+itS3kOykUyNhDg==} + '@oxlint/binding-win32-arm64-msvc@1.58.0': + resolution: {integrity: sha512-Q0FZiAY/3c4YRj4z3h9K1PgaByrifrfbBoODSeX7gy97UtB7pySPUQfC2B/GbxWU6k7CzQrRy5gME10PltLAFQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.57.0': - resolution: {integrity: sha512-StOZ9nFMVKvevicbQfql6Pouu9pgbeQnu60Fvhz2S6yfMaii+wnueLnqQ5I1JPgNF0Syew4voBlAaHD13wH6tw==} + '@oxlint/binding-win32-ia32-msvc@1.58.0': + resolution: {integrity: sha512-Y8FKBABrSPp9H0QkRLHDHOSUgM/309a3IvOVgPcVxYcX70wxJrk608CuTg7w+C6vEd724X5wJoNkBcGYfH7nNQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.57.0': - resolution: {integrity: sha512-6PuxhYgth8TuW0+ABPOIkGdBYw+qYGxgIdXPHSVpiCDm+hqTTWCmC739St1Xni0DJBt8HnSHTG67i1y6gr8qrA==} + '@oxlint/binding-win32-x64-msvc@1.58.0': + resolution: {integrity: sha512-bCn5rbiz5My+Bj7M09sDcnqW0QJyINRVxdZ65x1/Y2tGrMwherwK/lpk+HRQCKvXa8pcaQdF5KY5j54VGZLwNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -497,12 +497,12 @@ packages: vite-plus: optional: true - oxlint@1.57.0: - resolution: {integrity: sha512-DGFsuBX5MFZX9yiDdtKjTrYPq45CZ8Fft6qCltJITYZxfwYjVdGf/6wycGYTACloauwIPxUnYhBVeZbHvleGhw==} + oxlint@1.58.0: + resolution: {integrity: sha512-t4s9leczDMqlvOSjnbCQe7gtoLkWgBGZ7sBdCJ9EOj5IXFSG/X7OAzK4yuH4iW+4cAYe8kLFbC8tuYMwWZm+Cg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.15.0' + oxlint-tsgolint: '>=0.18.0' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -709,61 +709,61 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.66.0': optional: true - '@oxlint/binding-android-arm-eabi@1.57.0': + '@oxlint/binding-android-arm-eabi@1.58.0': optional: true - '@oxlint/binding-android-arm64@1.57.0': + '@oxlint/binding-android-arm64@1.58.0': optional: true - '@oxlint/binding-darwin-arm64@1.57.0': + '@oxlint/binding-darwin-arm64@1.58.0': optional: true - '@oxlint/binding-darwin-x64@1.57.0': + '@oxlint/binding-darwin-x64@1.58.0': optional: true - '@oxlint/binding-freebsd-x64@1.57.0': + '@oxlint/binding-freebsd-x64@1.58.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.57.0': + '@oxlint/binding-linux-arm-gnueabihf@1.58.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.57.0': + '@oxlint/binding-linux-arm-musleabihf@1.58.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.57.0': + '@oxlint/binding-linux-arm64-gnu@1.58.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.57.0': + '@oxlint/binding-linux-arm64-musl@1.58.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.57.0': + '@oxlint/binding-linux-ppc64-gnu@1.58.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.57.0': + '@oxlint/binding-linux-riscv64-gnu@1.58.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.57.0': + '@oxlint/binding-linux-riscv64-musl@1.58.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.57.0': + '@oxlint/binding-linux-s390x-gnu@1.58.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.57.0': + '@oxlint/binding-linux-x64-gnu@1.58.0': optional: true - '@oxlint/binding-linux-x64-musl@1.57.0': + '@oxlint/binding-linux-x64-musl@1.58.0': optional: true - '@oxlint/binding-openharmony-arm64@1.57.0': + '@oxlint/binding-openharmony-arm64@1.58.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.57.0': + '@oxlint/binding-win32-arm64-msvc@1.58.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.57.0': + '@oxlint/binding-win32-ia32-msvc@1.58.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.57.0': + '@oxlint/binding-win32-x64-msvc@1.58.0': optional: true '@types/parse-json@4.0.2': {} @@ -1005,27 +1005,27 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.66.0 '@oxfmt/binding-win32-x64-msvc': 0.66.0 - oxlint@1.57.0: + oxlint@1.58.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.57.0 - '@oxlint/binding-android-arm64': 1.57.0 - '@oxlint/binding-darwin-arm64': 1.57.0 - '@oxlint/binding-darwin-x64': 1.57.0 - '@oxlint/binding-freebsd-x64': 1.57.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.57.0 - '@oxlint/binding-linux-arm-musleabihf': 1.57.0 - '@oxlint/binding-linux-arm64-gnu': 1.57.0 - '@oxlint/binding-linux-arm64-musl': 1.57.0 - '@oxlint/binding-linux-ppc64-gnu': 1.57.0 - '@oxlint/binding-linux-riscv64-gnu': 1.57.0 - '@oxlint/binding-linux-riscv64-musl': 1.57.0 - '@oxlint/binding-linux-s390x-gnu': 1.57.0 - '@oxlint/binding-linux-x64-gnu': 1.57.0 - '@oxlint/binding-linux-x64-musl': 1.57.0 - '@oxlint/binding-openharmony-arm64': 1.57.0 - '@oxlint/binding-win32-arm64-msvc': 1.57.0 - '@oxlint/binding-win32-ia32-msvc': 1.57.0 - '@oxlint/binding-win32-x64-msvc': 1.57.0 + '@oxlint/binding-android-arm-eabi': 1.58.0 + '@oxlint/binding-android-arm64': 1.58.0 + '@oxlint/binding-darwin-arm64': 1.58.0 + '@oxlint/binding-darwin-x64': 1.58.0 + '@oxlint/binding-freebsd-x64': 1.58.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.58.0 + '@oxlint/binding-linux-arm-musleabihf': 1.58.0 + '@oxlint/binding-linux-arm64-gnu': 1.58.0 + '@oxlint/binding-linux-arm64-musl': 1.58.0 + '@oxlint/binding-linux-ppc64-gnu': 1.58.0 + '@oxlint/binding-linux-riscv64-gnu': 1.58.0 + '@oxlint/binding-linux-riscv64-musl': 1.58.0 + '@oxlint/binding-linux-s390x-gnu': 1.58.0 + '@oxlint/binding-linux-x64-gnu': 1.58.0 + '@oxlint/binding-linux-x64-musl': 1.58.0 + '@oxlint/binding-openharmony-arm64': 1.58.0 + '@oxlint/binding-win32-arm64-msvc': 1.58.0 + '@oxlint/binding-win32-ia32-msvc': 1.58.0 + '@oxlint/binding-win32-x64-msvc': 1.58.0 p-map@4.0.0: dependencies: diff --git a/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-11-09-11-45.json b/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-11-09-11-45.json new file mode 100644 index 00000000000..2b016713a32 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-11-09-11-45.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-gen-ai: Enhance Markdown handling and improve CustomHyperlink processing.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-14-09-12-45.json b/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-14-09-12-45.json new file mode 100644 index 00000000000..ac04f126eac --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/SHA_master_1_2026-14-09-12-45.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-dashboard: Support saving draft insights during dashboard save flow.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/ine-cq-2825-timezone-tooltip-docs-link_2026-09-14-09-44-59.json b/common/changes/@gooddata/sdk-ui-all/ine-cq-2825-timezone-tooltip-docs-link_2026-09-14-09-44-59.json new file mode 100644 index 00000000000..072f95fdd44 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/ine-cq-2825-timezone-tooltip-docs-link_2026-09-14-09-44-59.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "sdk-ui-dashboard: Add help link.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all", + "email": "ivan.nejezchleb@gooddata.com" +} diff --git a/common/changes/@gooddata/sdk-ui-all/ine-cq-2958_2026-09-11-14-53-39.json b/common/changes/@gooddata/sdk-ui-all/ine-cq-2958_2026-09-11-14-53-39.json new file mode 100644 index 00000000000..cc065ce42a2 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/ine-cq-2958_2026-09-11-14-53-39.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Reference computed attributes in drill to URL placeholders", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all", + "email": "ivan.nejezchleb@gooddata.com" +} diff --git a/common/changes/@gooddata/sdk-ui-all/ine-cq-2968_2026-09-11-14-47-14.json b/common/changes/@gooddata/sdk-ui-all/ine-cq-2968_2026-09-11-14-47-14.json new file mode 100644 index 00000000000..d34b8c78040 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/ine-cq-2968_2026-09-11-14-47-14.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Keep arrow keys inside a rich text editor instead of navigating the surrounding menu", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all", + "email": "ivan.nejezchleb@gooddata.com" +} diff --git a/common/changes/@gooddata/sdk-ui-all/jkd-gdai-2315-fe_2026-09-10-16-30-00.json b/common/changes/@gooddata/sdk-ui-all/jkd-gdai-2315-fe_2026-09-10-16-30-00.json new file mode 100644 index 00000000000..30ef8db23a4 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/jkd-gdai-2315-fe_2026-09-10-16-30-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-backend-tiger: Copilot adding a chart to an open dashboard now renders the patched draft as a single card, and follow-up proposals in the same conversation keep working after a reload.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/jsc-f1-2861-copy-object-permissions_2026-09-11-12-00-00.json b/common/changes/@gooddata/sdk-ui-all/jsc-f1-2861-copy-object-permissions_2026-09-11-12-00-00.json new file mode 100644 index 00000000000..7fbe9635483 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/jsc-f1-2861-copy-object-permissions_2026-09-11-12-00-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-ext: Add the useCopyObjectPermissions hook, which reproduces one object's own access on another.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/mkn-olp-restricted-widget-removal_2026-09-10-13-30-00.json b/common/changes/@gooddata/sdk-ui-all/mkn-olp-restricted-widget-removal_2026-09-10-13-30-00.json new file mode 100644 index 00000000000..4007807c080 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/mkn-olp-restricted-widget-removal_2026-09-10-13-30-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-all: Anyone who can edit a dashboard can now remove a restricted visualization widget from it, which until now had no menu at all; neither the menu nor the delete confirmation names such a widget.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pb-menu-zindex-fix_2026-09-11-12-55-50.json b/common/changes/@gooddata/sdk-ui-all/pb-menu-zindex-fix_2026-09-11-12-55-50.json new file mode 100644 index 00000000000..13abf3e6481 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pb-menu-zindex-fix_2026-09-11-12-55-50.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-kit: Menu z-index is supplied by the active OverlayController", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-cancel-error-hygiene_2026-08-13.json b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-cancel-error-hygiene_2026-08-13.json new file mode 100644 index 00000000000..68dd615bdca --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-cancel-error-hygiene_2026-08-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui:Convert aborted executions to CANCELLED instead of UNKNOWN_ERROR; sdk-backend-tiger no longer logs request cancellations as backend errors.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-error-boundaries_2026-08-13.json b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-error-boundaries_2026-08-13.json new file mode 100644 index 00000000000..d0c26e61d9a --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-error-boundaries_2026-08-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-pluggable-host:Add HostErrorBoundary so an uncaught error renders an error screen instead of unmounting the whole host page.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-platform-context-keep-mounted_2026-08-13.json b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-platform-context-keep-mounted_2026-08-13.json new file mode 100644 index 00000000000..de13534e14c --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-lx-2812-platform-context-keep-mounted_2026-08-13.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-pluggable-host:Keep the mounted app rendered while the same workspace's platform context refreshes instead of swapping it for a loader.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-regenerate-default-theme_2026-09-10-14-00-00.json b/common/changes/@gooddata/sdk-ui-all/pdo-regenerate-default-theme_2026-09-10-14-00-00.json new file mode 100644 index 00000000000..f17a350ed16 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-regenerate-default-theme_2026-09-10-14-00-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-kit: Regenerate defaultTheme.scss from its spec, restoring the :root defaults for --gd-avatar-background-color, --gd-viz-tooltip-max-height and the geo multi-layer legend variables, which were referenced without a fallback", + "type": "patch", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-uidropdown-popup-role_2026-09-11-08-00-00.json b/common/changes/@gooddata/sdk-ui-all/pdo-uidropdown-popup-role_2026-09-11-08-00-00.json new file mode 100644 index 00000000000..1ddce075d26 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-uidropdown-popup-role_2026-09-11-08-00-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "sdk-ui-kit: UiDropdown no longer repeats a listbox, menu, tree or grid popupRole on the floating panel around the body that already carries it", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all", + "email": "petr.dolejsi@gooddata.com" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pdo-uitooltip-width-defaults_2026-09-09-10-00-00.json b/common/changes/@gooddata/sdk-ui-all/pdo-uitooltip-width-defaults_2026-09-09-10-00-00.json new file mode 100644 index 00000000000..56b1782f5c5 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pdo-uitooltip-width-defaults_2026-09-09-10-00-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-kit: UiTooltip caps its width at 300px by default (override via the --gd-tooltip-maxWidth CSS variable, or by setting an explicit width)", + "type": "patch", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pkoz-lx-2965-host-export-mode-in-search_2026-09-11-00-00.json b/common/changes/@gooddata/sdk-ui-all/pkoz-lx-2965-host-export-mode-in-search_2026-09-11-00-00.json new file mode 100644 index 00000000000..ac495101aa3 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pkoz-lx-2965-host-export-mode-in-search_2026-09-11-00-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-pluggable-host: The host hides its navigation, chat and brand title for an export mode named in the search string as well as in the fragment, and recognises the reports module's export_slideshow mode next to the dashboards' export.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-11-11-52-06.json b/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-11-11-52-06.json new file mode 100644 index 00000000000..5e0d7a2c4b1 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-11-11-52-06.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Render waterfall charts in the AI assistant chat.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-14-09-25-47.json b/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-14-09-25-47.json new file mode 100644 index 00000000000..125fa79da47 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master-1_2026-09-14-09-25-47.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Render donut, pyramid and funnel charts in the AI assistant chat.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master-2_2026-09-14-11-07-06.json b/common/changes/@gooddata/sdk-ui-all/pvep-master-2_2026-09-14-11-07-06.json new file mode 100644 index 00000000000..8c0d688e360 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master-2_2026-09-14-11-07-06.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Render radar charts in the AI assistant chat.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-10-08-25-41.json b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-10-08-25-41.json new file mode 100644 index 00000000000..a80c06c274b --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-10-08-25-41.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Start a new conversation instead of resuming the last one when the AI Assistant is opened after 8 hours of inactivity.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-07-47-37.json b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-07-47-37.json new file mode 100644 index 00000000000..9038cf18865 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-07-47-37.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Render heatmap charts in the AI assistant chat.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-10-25-34.json b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-10-25-34.json new file mode 100644 index 00000000000..7891dfafda7 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/pvep-master_2026-09-14-10-25-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@gooddata/sdk-ui-all", + "comment": "sdk-ui-gen-ai: Render stacked area charts in the AI assistant chat.", + "type": "none" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} \ No newline at end of file diff --git a/common/changes/@gooddata/sdk-ui-all/scv-restricted-filters-edit-mode_2026-09-10-14-00-00.json b/common/changes/@gooddata/sdk-ui-all/scv-restricted-filters-edit-mode_2026-09-10-14-00-00.json new file mode 100644 index 00000000000..bac5b4f7395 --- /dev/null +++ b/common/changes/@gooddata/sdk-ui-all/scv-restricted-filters-edit-mode_2026-09-10-14-00-00.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "comment": "sdk-ui-dashboard: Let an editor remove the dashboard filters they are not allowed to read, through the removeRestrictedFilters command and the action the filter bar notice offers in edit mode.", + "type": "none", + "packageName": "@gooddata/sdk-ui-all" + } + ], + "packageName": "@gooddata/sdk-ui-all" +} diff --git a/common/config/rush/.pnpmfile.cjs b/common/config/rush/.pnpmfile.cjs index 1e8ca5d7fd9..b35f30848ee 100644 --- a/common/config/rush/.pnpmfile.cjs +++ b/common/config/rush/.pnpmfile.cjs @@ -3,7 +3,7 @@ "use strict"; // baseline-browser-mapping needs to be updated to the latest version every 14 days -const baselineBrowserMappingVersion = "2.11.21"; +const baselineBrowserMappingVersion = "2.11.23"; // browserslist needs to be updated every 6 months const browserslistVersion = "4.28.9"; diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index ae05853be03..c2ee065f1b6 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -95,7 +95,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -133,11 +133,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -148,8 +151,8 @@ importers: specifier: 1.0.3 version: 1.0.3 sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 ts-invariant: specifier: 0.10.3 version: 0.10.3 @@ -161,7 +164,7 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../examples/sdk-interactive-examples: devDependencies: @@ -243,7 +246,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -275,11 +278,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -291,10 +297,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-attributefilter: dependencies: @@ -370,7 +376,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -402,11 +408,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -418,10 +427,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-chartconfig: dependencies: @@ -497,7 +506,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -529,11 +538,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -545,10 +557,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-columnchart: dependencies: @@ -624,7 +636,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -656,11 +668,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -672,10 +687,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-combochart: dependencies: @@ -751,7 +766,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -783,11 +798,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -799,10 +817,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-dashboard: dependencies: @@ -878,7 +896,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -910,11 +928,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -926,10 +947,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-datefilter: dependencies: @@ -1005,7 +1026,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1037,11 +1058,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1053,10 +1077,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-dependentfilters: dependencies: @@ -1132,7 +1156,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1164,11 +1188,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1180,10 +1207,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-execute: dependencies: @@ -1268,7 +1295,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1300,11 +1327,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1316,10 +1346,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-granularity: dependencies: @@ -1395,7 +1425,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1427,11 +1457,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1443,10 +1476,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-headline: dependencies: @@ -1522,7 +1555,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1554,11 +1587,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1570,10 +1606,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-pivottable: dependencies: @@ -1649,7 +1685,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1681,11 +1717,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1697,10 +1736,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-relativedatefilter: dependencies: @@ -1776,7 +1815,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1808,11 +1847,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1824,10 +1866,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../examples/sdk-interactive-examples/examples/example-repeater: dependencies: @@ -1903,7 +1945,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -1935,11 +1977,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 process: specifier: 0.11.10 version: 0.11.10 @@ -1951,10 +1996,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-html: specifier: ~3.2.0 - version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../libs/api-client-tiger: dependencies: @@ -2038,17 +2083,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-backend-base: dependencies: @@ -2141,17 +2189,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-backend-mockingbird: dependencies: @@ -2232,17 +2283,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-backend-spi: dependencies: @@ -2308,17 +2362,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-backend-tiger: dependencies: @@ -2444,17 +2501,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-code-convertors: dependencies: @@ -2517,17 +2577,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-code-schemas: devDependencies: @@ -2580,11 +2643,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 readdirp: specifier: 4.1.2 version: 4.1.2 @@ -2593,7 +2659,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-e2e-utils: dependencies: @@ -2665,11 +2731,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 @@ -2738,17 +2807,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-model: dependencies: @@ -2829,17 +2901,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-pluggable-application-model: dependencies: @@ -2902,11 +2977,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 @@ -3056,11 +3134,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -3081,7 +3162,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -3165,11 +3246,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 @@ -3286,11 +3370,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -3311,10 +3398,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -3464,11 +3551,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -3479,8 +3569,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -3489,7 +3579,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -3675,11 +3765,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -3690,8 +3783,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -3700,7 +3793,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -3931,11 +4024,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -3946,8 +4042,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -3959,7 +4055,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -4199,11 +4295,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -4214,8 +4313,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -4227,7 +4326,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -4392,11 +4491,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -4407,8 +4509,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -4417,7 +4519,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -4624,11 +4726,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -4639,8 +4744,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -4652,7 +4757,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -4802,11 +4907,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -4817,8 +4925,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -4827,7 +4935,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -5091,11 +5199,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -5106,8 +5217,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -5119,7 +5230,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -5230,11 +5341,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -5393,11 +5507,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -5408,8 +5525,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -5418,7 +5535,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -5520,11 +5637,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -5536,7 +5656,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-ui-pluggable-host: dependencies: @@ -5665,11 +5785,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -5686,8 +5809,8 @@ importers: specifier: 1.0.3 version: 1.0.3 sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 tslib: specifier: 2.8.1 version: 2.8.1 @@ -5696,10 +5819,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -5822,11 +5945,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -5837,8 +5963,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -5847,7 +5973,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -5916,7 +6042,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) core-js: specifier: 3.45.1 version: 3.45.1 @@ -5957,11 +6083,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -5972,14 +6101,14 @@ importers: specifier: 1.0.3 version: 1.0.3 sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 typescript: specifier: 7.0.2 version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-ui-tests-e2e: devDependencies: @@ -6059,11 +6188,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 rolldown: specifier: 1.0.3 version: 1.0.3 @@ -6119,11 +6251,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 @@ -6251,11 +6386,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -6273,7 +6411,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-ui-tests-storybook: devDependencies: @@ -6351,13 +6489,13 @@ importers: version: 10.5.6(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1)) '@storybook/builder-vite': specifier: 10.5.6 - version: 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) + version: 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) '@storybook/cli': specifier: 10.5.6 version: 10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1) '@storybook/react-vite': specifier: 10.5.6 - version: 10.5.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(esbuild@0.28.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) + version: 10.5.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(esbuild@0.28.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) '@types/lodash-es': specifier: 4.17.12 version: 4.17.12 @@ -6434,11 +6572,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -6474,10 +6615,10 @@ importers: version: 11.1.1 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-static-copy: specifier: 4.1.0 - version: 4.1.0(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 4.1.0(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) ../../libs/sdk-ui-theme-provider: dependencies: @@ -6603,11 +6744,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -6622,7 +6766,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/sdk-ui-vis-commons: dependencies: @@ -6751,11 +6895,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -6766,8 +6913,8 @@ importers: specifier: 19.1.1 version: 19.1.1(react@19.1.1) sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 stylelint: specifier: 16.26.1 version: 16.26.1(typescript@7.0.2) @@ -6776,7 +6923,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest-dom: specifier: 0.1.1 version: 0.1.1(vitest@4.1.8) @@ -6911,11 +7058,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 raf: specifier: 3.4.1 version: 3.4.1 @@ -6936,13 +7086,13 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vite-plugin-css-injected-by-js: specifier: 3.1.1 - version: 3.1.1(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 3.1.1(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../libs/util: dependencies: @@ -7005,17 +7155,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../templates/pluggable-module/harness: dependencies: @@ -7067,7 +7220,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -7099,23 +7252,26 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 rolldown: specifier: 1.0.3 version: 1.0.3 sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 typescript: specifier: 7.0.2 version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../templates/pluggable-module/module: dependencies: @@ -7149,7 +7305,7 @@ importers: version: link:../../../tools/oxlint-config '@module-federation/vite': specifier: 1.16.11 - version: 1.16.11(node-fetch@2.7.0)(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 1.16.11(node-fetch@2.7.0)(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) '@testing-library/dom': specifier: 10.4.1 version: 10.4.1 @@ -7176,7 +7332,7 @@ importers: version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) '@vitejs/plugin-react': specifier: 6.0.2 - version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + version: 6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -7214,11 +7370,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -7229,17 +7388,17 @@ importers: specifier: 1.0.3 version: 1.0.3 sass: - specifier: 1.100.0 - version: 1.100.0 + specifier: 1.104.0 + version: 1.104.0 typescript: specifier: 7.0.2 version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/app-toolkit: dependencies: @@ -7323,17 +7482,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/catalog-export: dependencies: @@ -7429,17 +7591,20 @@ importers: specifier: 4.1.5 version: 4.1.5 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/create-pluggable-module: devDependencies: @@ -7489,11 +7654,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 tslib: specifier: 2.8.1 version: 2.8.1 @@ -7502,10 +7670,10 @@ importers: version: 7.0.2 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/dashboard-plugin-template: dependencies: @@ -7625,11 +7793,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -7653,7 +7824,7 @@ importers: version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) webpack: specifier: 5.107.2 version: 5.107.2(esbuild@0.28.1)(webpack-cli@7.0.3) @@ -7670,8 +7841,8 @@ importers: ../../tools/eslint-config: devDependencies: '@eslint/js': - specifier: 9.28.0 - version: 9.28.0 + specifier: 9.39.5 + version: 9.39.5 '@gooddata/lint-config': specifier: workspace:* version: link:../lint-config @@ -7758,7 +7929,7 @@ importers: version: 7.0.2 vite-node: specifier: 5.3.0 - version: 5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/i18n-toolkit: dependencies: @@ -7845,17 +8016,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/lint-config: devDependencies: @@ -7957,23 +8131,23 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/oxlint-config: devDependencies: - '@eslint/js': - specifier: 9.28.0 - version: 9.28.0 '@gooddata/eslint-config': specifier: workspace:* version: link:../eslint-config @@ -7992,9 +8166,6 @@ importers: '@typescript-eslint/parser': specifier: 8.58.0 version: 8.58.0(eslint@9.39.5(jiti@2.6.1)) - '@vitest/eslint-plugin': - specifier: 1.6.19 - version: 1.6.19(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.5(jiti@2.6.1)))(eslint@9.39.5(jiti@2.6.1)))(eslint@9.39.5(jiti@2.6.1))(typescript@7.0.2)(vitest@4.1.8) eslint: specifier: ^9.39.2 version: 9.39.5(jiti@2.6.1) @@ -8035,11 +8206,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 react: specifier: 19.1.1 version: 19.1.1 @@ -8054,7 +8228,7 @@ importers: version: 7.0.2 vite-node: specifier: 5.3.0 - version: 5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/plugin-toolkit: dependencies: @@ -8162,17 +8336,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/react-app-template: dependencies: @@ -8298,11 +8475,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 source-map-loader: specifier: 4.0.2 version: 4.0.2(webpack@5.107.2) @@ -8380,11 +8560,14 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 @@ -8450,17 +8633,20 @@ importers: specifier: 0.66.0 version: 0.66.0 oxlint: - specifier: 1.57.0 - version: 1.57.0(oxlint-tsgolint@0.18.1) + specifier: 1.67.0 + version: 1.67.0(oxlint-tsgolint@0.22.1) + oxlint-plugin-eslint: + specifier: 1.82.0 + version: 1.82.0 oxlint-tsgolint: - specifier: 0.18.1 - version: 0.18.1 + specifier: 0.22.1 + version: 0.22.1 typescript: specifier: 7.0.2 version: 7.0.2 vitest: specifier: 4.1.8 - version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + version: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) ../../tools/stylelint-config: dependencies: @@ -9039,10 +9225,6 @@ packages: resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.28.0': - resolution: {integrity: sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.39.5': resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -10200,154 +10382,154 @@ packages: cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.18.1': - resolution: {integrity: sha512-CxSd15ZwHn70UJFTXVvy76bZ9zwI097cVyjvUFmYRJwvkQF3VnrTf2oe1gomUacErksvtqLgn9OKvZhLMYwvog==} + '@oxlint-tsgolint/darwin-arm64@0.22.1': + resolution: {integrity: sha512-4150Lpgc1YM09GcjA6GSrra1JoPjC7aOpfywLjWEY4vW0Sd1qKzqHF1WRaiw0/qUZ40OATYdv3aRd7ipPkWQbw==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.18.1': - resolution: {integrity: sha512-LE7VW/T/VcKhl3Z1ev5BusrxdlQ3DWweSeOB+qpBeur2h8+vCWq+M7tCO29C7lveBDfx1+rNwj4aiUVlA+Qs+g==} + '@oxlint-tsgolint/darwin-x64@0.22.1': + resolution: {integrity: sha512-vFWcPWYOgZs4HWcgS1EjUZg33NLcNfEYU49KGImmCfZWkflENrmBYV4HN/C0YeAPum6ZZ/goPSvQrB/cOD+NfA==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.18.1': - resolution: {integrity: sha512-2AG8YIXVJJbnM0rcsJmzzWOjZXBu5REwowgUpbHZueF7OYM3wR7Xu8pXEpAojEHAtYYZ3X4rpPoetomkJx7kCw==} + '@oxlint-tsgolint/linux-arm64@0.22.1': + resolution: {integrity: sha512-6LiUpP0Zir3+29FvBm7Y28q/dBjSHqTZ5MhG1Ckw4fGhI4cAvbcwXaKvbjx1TP7rRmBNOoq/M5xdpHjTb+GAew==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.18.1': - resolution: {integrity: sha512-f8vDYPEdiwpA2JaDEkadTXfuqIgweQ8zcL4SX75EN2kkW2oAynjN7cd8m86uXDgB0JrcyOywbRtwnXdiIzXn2A==} + '@oxlint-tsgolint/linux-x64@0.22.1': + resolution: {integrity: sha512-fuX1hEQfpHauUbXADsfqVhRzrUrGabzGXbj5wsp2vKhV5uk/Rze8Mba9GdjFGECzvXudMGqHqxB4r6jGRdhxVA==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.18.1': - resolution: {integrity: sha512-fBdML05KMDAL9ebWeoHIzkyI86Eq6r9YH5UDRuXJ9vAIo1EnKo0ti7hLUxNdc2dy2FF/T4k98p5wkkXvLyXqfA==} + '@oxlint-tsgolint/win32-arm64@0.22.1': + resolution: {integrity: sha512-8SZidAj+jrbZf9ZjBEYW0tiNZ+KasqB2zgW26qdiPpQSF/DzURnPmXz651IeA9YsmbVdHGIooEHUmev6QJdquA==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.18.1': - resolution: {integrity: sha512-cYZMhNrsq9ZZ3OUWHyawqiS+c8HfieYG0zuZP2LbEuWWPfdZM/22iAlo608J+27G1s9RXQhvgX6VekwWbXbD7A==} + '@oxlint-tsgolint/win32-x64@0.22.1': + resolution: {integrity: sha512-QweSk9H5lFh5Y+WUf2Kq/OAN88V6+62ZwGhP38gqdRotI90luXSMkruFTj7Q2rYrzH4ZVNaSqx7NY8JpSfIzqg==} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.57.0': - resolution: {integrity: sha512-C7EiyfAJG4B70496eV543nKiq5cH0o/xIh/ufbjQz3SIvHhlDDsyn+mRFh+aW8KskTyUpyH2LGWL8p2oN6bl1A==} + '@oxlint/binding-android-arm-eabi@1.67.0': + resolution: {integrity: sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.57.0': - resolution: {integrity: sha512-9i80AresjZ/FZf5xK8tKFbhQnijD4s1eOZw6/FHUwD59HEZbVLRc2C88ADYJfLZrF5XofWDiRX/Ja9KefCLy7w==} + '@oxlint/binding-android-arm64@1.67.0': + resolution: {integrity: sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.57.0': - resolution: {integrity: sha512-0eUfhRz5L2yKa9I8k3qpyl37XK3oBS5BvrgdVIx599WZK63P8sMbg+0s4IuxmIiZuBK68Ek+Z+gcKgeYf0otsg==} + '@oxlint/binding-darwin-arm64@1.67.0': + resolution: {integrity: sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.57.0': - resolution: {integrity: sha512-UvrSuzBaYOue+QMAcuDITe0k/Vhj6KZGjfnI6x+NkxBTke/VoM7ZisaxgNY0LWuBkTnd1OmeQfEQdQ48fRjkQg==} + '@oxlint/binding-darwin-x64@1.67.0': + resolution: {integrity: sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.57.0': - resolution: {integrity: sha512-wtQq0dCoiw4bUwlsNVDJJ3pxJA218fOezpgtLKrbQqUtQJcM9yP8z+I9fu14aHg0uyAxIY+99toL6uBa2r7nxA==} + '@oxlint/binding-freebsd-x64@1.67.0': + resolution: {integrity: sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.57.0': - resolution: {integrity: sha512-qxFWl2BBBFcT4djKa+OtMdnLgoHEJXpqjyGwz8OhW35ImoCwR5qtAGqApNYce5260FQqoAHW8S8eZTjiX67Tsg==} + '@oxlint/binding-linux-arm-gnueabihf@1.67.0': + resolution: {integrity: sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.57.0': - resolution: {integrity: sha512-SQoIsBU7J0bDW15/f0/RvxHfY3Y0+eB/caKBQtNFbuerTiA6JCYx9P1MrrFTwY2dTm/lMgTSgskvCEYk2AtG/Q==} + '@oxlint/binding-linux-arm-musleabihf@1.67.0': + resolution: {integrity: sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.57.0': - resolution: {integrity: sha512-jqxYd1W6WMeozsCmqe9Rzbu3SRrGTyGDAipRlRggetyYbUksJqJKvUNTQtZR/KFoJPb+grnSm5SHhdWrywv3RQ==} + '@oxlint/binding-linux-arm64-gnu@1.67.0': + resolution: {integrity: sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.57.0': - resolution: {integrity: sha512-i66WyEPVEvq9bxRUCJ/MP5EBfnTDN3nhwEdFZFTO5MmLLvzngfWEG3NSdXQzTT3vk5B9i6C2XSIYBh+aG6uqyg==} + '@oxlint/binding-linux-arm64-musl@1.67.0': + resolution: {integrity: sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.57.0': - resolution: {integrity: sha512-oMZDCwz4NobclZU3pH+V1/upVlJZiZvne4jQP+zhJwt+lmio4XXr4qG47CehvrW1Lx2YZiIHuxM2D4YpkG3KVA==} + '@oxlint/binding-linux-ppc64-gnu@1.67.0': + resolution: {integrity: sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.57.0': - resolution: {integrity: sha512-uoBnjJ3MMEBbfnWC1jSFr7/nSCkcQYa72NYoNtLl1imshDnWSolYCjzb8LVCwYCCfLJXD+0gBLD7fyC14c0+0g==} + '@oxlint/binding-linux-riscv64-gnu@1.67.0': + resolution: {integrity: sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.57.0': - resolution: {integrity: sha512-BdrwD7haPZ8a9KrZhKJRSj6jwCor+Z8tHFZ3PT89Y3Jq5v3LfMfEePeAmD0LOTWpiTmzSzdmyw9ijneapiVHKQ==} + '@oxlint/binding-linux-riscv64-musl@1.67.0': + resolution: {integrity: sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.57.0': - resolution: {integrity: sha512-BNs+7ZNsRstVg2tpNxAXfMX/Iv5oZh204dVyb8Z37+/gCh+yZqNTlg6YwCLIMPSk5wLWIGOaQjT0GUOahKYImw==} + '@oxlint/binding-linux-s390x-gnu@1.67.0': + resolution: {integrity: sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.57.0': - resolution: {integrity: sha512-AghS18w+XcENcAX0+BQGLiqjpqpaxKJa4cWWP0OWNLacs27vHBxu7TYkv9LUSGe5w8lOJHeMxcYfZNOAPqw2bg==} + '@oxlint/binding-linux-x64-gnu@1.67.0': + resolution: {integrity: sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.57.0': - resolution: {integrity: sha512-E/FV3GB8phu/Rpkhz5T96hAiJlGzn91qX5yj5gU754P5cmVGXY1Jw/VSjDSlZBCY3VHjsVLdzgdkJaomEmcNOg==} + '@oxlint/binding-linux-x64-musl@1.67.0': + resolution: {integrity: sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.57.0': - resolution: {integrity: sha512-xvZ2yZt0nUVfU14iuGv3V25jpr9pov5N0Wr28RXnHFxHCRxNDMtYPHV61gGLhN9IlXM96gI4pyYpLSJC5ClLCQ==} + '@oxlint/binding-openharmony-arm64@1.67.0': + resolution: {integrity: sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.57.0': - resolution: {integrity: sha512-Z4D8Pd0AyHBKeazhdIXeUUy5sIS3Mo0veOlzlDECg6PhRRKgEsBJCCV1n+keUZtQ04OP+i7+itS3kOykUyNhDg==} + '@oxlint/binding-win32-arm64-msvc@1.67.0': + resolution: {integrity: sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.57.0': - resolution: {integrity: sha512-StOZ9nFMVKvevicbQfql6Pouu9pgbeQnu60Fvhz2S6yfMaii+wnueLnqQ5I1JPgNF0Syew4voBlAaHD13wH6tw==} + '@oxlint/binding-win32-ia32-msvc@1.67.0': + resolution: {integrity: sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.57.0': - resolution: {integrity: sha512-6PuxhYgth8TuW0+ABPOIkGdBYw+qYGxgIdXPHSVpiCDm+hqTTWCmC739St1Xni0DJBt8HnSHTG67i1y6gr8qrA==} + '@oxlint/binding-win32-x64-msvc@1.67.0': + resolution: {integrity: sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -12075,8 +12257,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.11.21: - resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} + baseline-browser-mapping@2.11.23: + resolution: {integrity: sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==} engines: {node: '>=6.0.0'} hasBin: true @@ -15004,19 +15186,26 @@ packages: vite-plus: optional: true - oxlint-tsgolint@0.18.1: - resolution: {integrity: sha512-Hgb0wMfuXBYL0ddY+1hAG8IIfC40ADwPnBuUaC6ENAuCtTF4dHwsy7mCYtQ2e7LoGvfoSJRY0+kqQRiembJ/jQ==} + oxlint-plugin-eslint@1.82.0: + resolution: {integrity: sha512-u86jGed4TZWbar5rXdxROcU+zaRXhKuzdvNwmmFPuhlhxv8LH5ioyjeuE/QgSUjzjFVPU0q5UokWHnkknvnw3w==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxlint-tsgolint@0.22.1: + resolution: {integrity: sha512-YUSGSLUnoolsu8gxISEDio3q1rtsCozwfOzASUn3DT2mR2EeQ93uEEnen7s+6LpF+lyTQFln1pQfqwBh/fsVEg==} hasBin: true - oxlint@1.57.0: - resolution: {integrity: sha512-DGFsuBX5MFZX9yiDdtKjTrYPq45CZ8Fft6qCltJITYZxfwYjVdGf/6wycGYTACloauwIPxUnYhBVeZbHvleGhw==} + oxlint@1.67.0: + resolution: {integrity: sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.15.0' + oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' peerDependenciesMeta: oxlint-tsgolint: optional: true + vite-plus: + optional: true p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -15808,8 +15997,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.100.0: - resolution: {integrity: sha512-B5j0rYMlinhhOo9tjQebMVVn0TfyXAF+wB3b2ggZUuJ/is/Y+7+JGjirAMxHZ9Z3hIP98NPfamlAkBHa1lAaXQ==} + sass@1.104.0: + resolution: {integrity: sha512-btHMApW2bgolvClhRW8AlQJzgI9lUB3pPSofBQQT+E46GWvf9o0TVQ13SYv5riWZVFyPN+JNz3TKW9XhBlc10w==} engines: {node: '>=20.19.0'} hasBin: true @@ -17709,8 +17898,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.28.0': {} - '@eslint/js@9.39.5': {} '@eslint/object-schema@2.1.7': {} @@ -18006,11 +18193,11 @@ snapshots: '@dagrejs/graphlib': 2.2.4 '@joint/core': 4.3.0 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))': dependencies: glob: 13.0.2 react-docgen-typescript: 2.4.0(typescript@7.0.2) - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) optionalDependencies: typescript: 7.0.2 @@ -18342,12 +18529,12 @@ snapshots: find-pkg: 2.0.0 resolve: 1.22.8 - '@module-federation/vite@1.16.11(node-fetch@2.7.0)(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))': + '@module-federation/vite@1.16.11(node-fetch@2.7.0)(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))': dependencies: '@module-federation/dts-plugin': 2.6.0(node-fetch@2.7.0)(typescript@7.0.2) '@module-federation/runtime': 2.6.0(node-fetch@2.7.0) '@module-federation/sdk': 2.6.0(node-fetch@2.7.0) - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) transitivePeerDependencies: - bufferutil - node-fetch @@ -18784,79 +18971,79 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.66.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.18.1': + '@oxlint-tsgolint/darwin-arm64@0.22.1': optional: true - '@oxlint-tsgolint/darwin-x64@0.18.1': + '@oxlint-tsgolint/darwin-x64@0.22.1': optional: true - '@oxlint-tsgolint/linux-arm64@0.18.1': + '@oxlint-tsgolint/linux-arm64@0.22.1': optional: true - '@oxlint-tsgolint/linux-x64@0.18.1': + '@oxlint-tsgolint/linux-x64@0.22.1': optional: true - '@oxlint-tsgolint/win32-arm64@0.18.1': + '@oxlint-tsgolint/win32-arm64@0.22.1': optional: true - '@oxlint-tsgolint/win32-x64@0.18.1': + '@oxlint-tsgolint/win32-x64@0.22.1': optional: true - '@oxlint/binding-android-arm-eabi@1.57.0': + '@oxlint/binding-android-arm-eabi@1.67.0': optional: true - '@oxlint/binding-android-arm64@1.57.0': + '@oxlint/binding-android-arm64@1.67.0': optional: true - '@oxlint/binding-darwin-arm64@1.57.0': + '@oxlint/binding-darwin-arm64@1.67.0': optional: true - '@oxlint/binding-darwin-x64@1.57.0': + '@oxlint/binding-darwin-x64@1.67.0': optional: true - '@oxlint/binding-freebsd-x64@1.57.0': + '@oxlint/binding-freebsd-x64@1.67.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.57.0': + '@oxlint/binding-linux-arm-gnueabihf@1.67.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.57.0': + '@oxlint/binding-linux-arm-musleabihf@1.67.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.57.0': + '@oxlint/binding-linux-arm64-gnu@1.67.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.57.0': + '@oxlint/binding-linux-arm64-musl@1.67.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.57.0': + '@oxlint/binding-linux-ppc64-gnu@1.67.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.57.0': + '@oxlint/binding-linux-riscv64-gnu@1.67.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.57.0': + '@oxlint/binding-linux-riscv64-musl@1.67.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.57.0': + '@oxlint/binding-linux-s390x-gnu@1.67.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.57.0': + '@oxlint/binding-linux-x64-gnu@1.67.0': optional: true - '@oxlint/binding-linux-x64-musl@1.57.0': + '@oxlint/binding-linux-x64-musl@1.67.0': optional: true - '@oxlint/binding-openharmony-arm64@1.57.0': + '@oxlint/binding-openharmony-arm64@1.67.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.57.0': + '@oxlint/binding-win32-arm64-msvc@1.67.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.57.0': + '@oxlint/binding-win32-ia32-msvc@1.67.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.57.0': + '@oxlint/binding-win32-x64-msvc@1.67.0': optional: true '@parcel/watcher-android-arm64@2.6.0': @@ -19335,12 +19522,12 @@ snapshots: axe-core: 4.12.1 storybook: 10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1) - '@storybook/builder-vite@10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': + '@storybook/builder-vite@10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': dependencies: - '@storybook/csf-plugin': 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) + '@storybook/csf-plugin': 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) storybook: 10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1) ts-dedent: 2.3.0 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) transitivePeerDependencies: - esbuild - rollup @@ -19384,14 +19571,14 @@ snapshots: - utf-8-validate - vite-plus - '@storybook/csf-plugin@10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': + '@storybook/csf-plugin@10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': dependencies: storybook: 10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1) unplugin: 2.3.11 optionalDependencies: esbuild: 0.28.1 rollup: 4.62.3 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) webpack: 5.107.2(esbuild@0.28.1) '@storybook/global@5.0.0': {} @@ -19409,11 +19596,11 @@ snapshots: '@types/react': 19.1.11 '@types/react-dom': 19.1.7(@types/react@19.1.11) - '@storybook/react-vite@10.5.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(esbuild@0.28.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': + '@storybook/react-vite@10.5.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(esbuild@0.28.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@7.0.2)(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.4.0(rollup@4.62.3) - '@storybook/builder-vite': 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) + '@storybook/builder-vite': 10.5.6(esbuild@0.28.1)(rollup@4.62.3)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))(webpack@5.107.2(esbuild@0.28.1)) '@storybook/react': 10.5.6(@types/react-dom@19.1.7(@types/react@19.1.11))(@types/react@19.1.11)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1))(typescript@7.0.2) empathic: 2.0.1 magic-string: 0.30.21 @@ -19423,7 +19610,7 @@ snapshots: resolve: 1.22.12 storybook: 10.5.6(@types/react@19.1.11)(prettier@3.9.6)(react@19.1.1) tsconfig-paths: 4.2.0 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) optionalDependencies: typescript: 7.0.2 transitivePeerDependencies: @@ -20087,10 +20274,10 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: @@ -20104,7 +20291,7 @@ snapshots: obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) '@vitest/eslint-plugin@1.6.19(@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.5(jiti@2.6.1)))(eslint@9.39.5(jiti@2.6.1)))(eslint@9.39.5(jiti@2.6.1))(typescript@7.0.2)(vitest@4.1.8)': dependencies: @@ -20114,7 +20301,7 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@9.39.5(jiti@2.6.1)))(eslint@9.39.5(jiti@2.6.1)) typescript: 7.0.2 - vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -20135,13 +20322,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -20618,7 +20805,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.11.21: {} + baseline-browser-mapping@2.11.23: {} basic-ftp@5.3.1: {} @@ -20680,7 +20867,7 @@ snapshots: browserslist@4.28.9: dependencies: - baseline-browser-mapping: 2.11.21 + baseline-browser-mapping: 2.11.23 caniuse-lite: 1.0.30001810 electron-to-chromium: 1.5.422 node-releases: 2.0.54 @@ -22452,7 +22639,7 @@ snapshots: semver: 7.7.4 optionalDependencies: jest-diff: 29.7.0 - vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) html-webpack-plugin@5.6.4(webpack@5.107.2): dependencies: @@ -24055,37 +24242,39 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.66.0 '@oxfmt/binding-win32-x64-msvc': 0.66.0 - oxlint-tsgolint@0.18.1: + oxlint-plugin-eslint@1.82.0: {} + + oxlint-tsgolint@0.22.1: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.18.1 - '@oxlint-tsgolint/darwin-x64': 0.18.1 - '@oxlint-tsgolint/linux-arm64': 0.18.1 - '@oxlint-tsgolint/linux-x64': 0.18.1 - '@oxlint-tsgolint/win32-arm64': 0.18.1 - '@oxlint-tsgolint/win32-x64': 0.18.1 - - oxlint@1.57.0(oxlint-tsgolint@0.18.1): + '@oxlint-tsgolint/darwin-arm64': 0.22.1 + '@oxlint-tsgolint/darwin-x64': 0.22.1 + '@oxlint-tsgolint/linux-arm64': 0.22.1 + '@oxlint-tsgolint/linux-x64': 0.22.1 + '@oxlint-tsgolint/win32-arm64': 0.22.1 + '@oxlint-tsgolint/win32-x64': 0.22.1 + + oxlint@1.67.0(oxlint-tsgolint@0.22.1): optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.57.0 - '@oxlint/binding-android-arm64': 1.57.0 - '@oxlint/binding-darwin-arm64': 1.57.0 - '@oxlint/binding-darwin-x64': 1.57.0 - '@oxlint/binding-freebsd-x64': 1.57.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.57.0 - '@oxlint/binding-linux-arm-musleabihf': 1.57.0 - '@oxlint/binding-linux-arm64-gnu': 1.57.0 - '@oxlint/binding-linux-arm64-musl': 1.57.0 - '@oxlint/binding-linux-ppc64-gnu': 1.57.0 - '@oxlint/binding-linux-riscv64-gnu': 1.57.0 - '@oxlint/binding-linux-riscv64-musl': 1.57.0 - '@oxlint/binding-linux-s390x-gnu': 1.57.0 - '@oxlint/binding-linux-x64-gnu': 1.57.0 - '@oxlint/binding-linux-x64-musl': 1.57.0 - '@oxlint/binding-openharmony-arm64': 1.57.0 - '@oxlint/binding-win32-arm64-msvc': 1.57.0 - '@oxlint/binding-win32-ia32-msvc': 1.57.0 - '@oxlint/binding-win32-x64-msvc': 1.57.0 - oxlint-tsgolint: 0.18.1 + '@oxlint/binding-android-arm-eabi': 1.67.0 + '@oxlint/binding-android-arm64': 1.67.0 + '@oxlint/binding-darwin-arm64': 1.67.0 + '@oxlint/binding-darwin-x64': 1.67.0 + '@oxlint/binding-freebsd-x64': 1.67.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 + '@oxlint/binding-linux-arm-musleabihf': 1.67.0 + '@oxlint/binding-linux-arm64-gnu': 1.67.0 + '@oxlint/binding-linux-arm64-musl': 1.67.0 + '@oxlint/binding-linux-ppc64-gnu': 1.67.0 + '@oxlint/binding-linux-riscv64-gnu': 1.67.0 + '@oxlint/binding-linux-riscv64-musl': 1.67.0 + '@oxlint/binding-linux-s390x-gnu': 1.67.0 + '@oxlint/binding-linux-x64-gnu': 1.67.0 + '@oxlint/binding-linux-x64-musl': 1.67.0 + '@oxlint/binding-openharmony-arm64': 1.67.0 + '@oxlint/binding-win32-arm64-msvc': 1.67.0 + '@oxlint/binding-win32-ia32-msvc': 1.67.0 + '@oxlint/binding-win32-x64-msvc': 1.67.0 + oxlint-tsgolint: 0.22.1 p-limit@2.3.0: dependencies: @@ -24988,7 +25177,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.100.0: + sass@1.104.0: dependencies: chokidar: 5.0.0 immutable: 5.1.9 @@ -26000,13 +26189,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3): + vite-node@5.3.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3): dependencies: cac: 6.7.14 es-module-lexer: 2.3.1 obug: 2.1.4 pathe: 2.0.3 - vite: 7.3.6(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 7.3.6(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - jiti @@ -26020,11 +26209,11 @@ snapshots: - tsx - yaml - vite-plugin-css-injected-by-js@3.1.1(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)): + vite-plugin-css-injected-by-js@3.1.1(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)): dependencies: - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) - vite-plugin-html@3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)): + vite-plugin-html@3.2.2(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -26038,17 +26227,17 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) - vite-plugin-static-copy@4.1.0(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)): + vite-plugin-static-copy@4.1.0(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)): dependencies: chokidar: 3.6.0 p-map: 7.0.6 picocolors: 1.1.1 tinyglobby: 0.2.17 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) - vite@7.3.6(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3): + vite@7.3.6(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.33.0)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3): dependencies: esbuild: 0.28.1 fdir: 6.5.0(picomatch@4.0.5) @@ -26061,11 +26250,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.33.0 - sass: 1.100.0 + sass: 1.104.0 terser: 5.49.0 yaml: 2.8.3 - vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3): + vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -26077,7 +26266,7 @@ snapshots: esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.6.1 - sass: 1.100.0 + sass: 1.104.0 terser: 5.49.0 yaml: 2.8.3 @@ -26089,12 +26278,12 @@ snapshots: dom-accessibility-api: 0.6.3 lodash-es: 4.18.1 redent: 4.0.0 - vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vitest: 4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) - vitest@4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3): + vitest@4.1.8(@types/node@24.12.0)(@vitest/coverage-v8@4.1.8)(esbuild@0.28.1)(happy-dom@20.12.0)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -26111,7 +26300,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.100.0)(terser@5.49.0)(yaml@2.8.3) + vite: 8.0.16(@types/node@24.12.0)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.104.0)(terser@5.49.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.0 diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json index 3cb269ec38e..632b011f7e2 100644 --- a/common/config/rush/version-policies.json +++ b/common/config/rush/version-policies.json @@ -2,14 +2,14 @@ { "definitionName": "lockStepVersion", "policyName": "sdk", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "nextBump": "prerelease", "mainProject": "@gooddata/sdk-ui-all" }, { "definitionName": "lockStepVersion", "policyName": "sdk-not-npm-publish", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "nextBump": "prerelease", "mainProject": "@gooddata/sdk-ui-all" } diff --git a/examples/playground/package.json b/examples/playground/package.json index 080c7d16fab..ea762e474ac 100644 --- a/examples/playground/package.json +++ b/examples/playground/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/playground", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData.UI SDK - Developer Playground", "license": "LicenseRef-LICENSE", @@ -67,12 +67,13 @@ "maplibre-gl": "^4.7.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "rolldown": "1.0.3", - "sass": "1.100.0", + "sass": "1.104.0", "ts-invariant": "0.10.3", "tslib": "2.8.1", "typescript": "7.0.2", diff --git a/examples/playground/vite.config.ts b/examples/playground/vite.config.ts index c72d2ccb932..c960b7939dd 100644 --- a/examples/playground/vite.config.ts +++ b/examples/playground/vite.config.ts @@ -221,7 +221,6 @@ try { const serverOptions = httpsOptions ? { https: httpsOptions } : {}; // https://vitejs.dev/config/ -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ""); const backendUrl = env["VITE_BACKEND_URL"] ?? "https://staging.dev-latest.stg11.panther.intgdc.com"; diff --git a/examples/sdk-interactive-examples/examples-template/package.json b/examples/sdk-interactive-examples/examples-template/package.json index 17f08ef7c9d..22789a72e79 100644 --- a/examples/sdk-interactive-examples/examples-template/package.json +++ b/examples/sdk-interactive-examples/examples-template/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-examples-template", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "GoodData interactive example template", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples-template/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples-template/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples-template/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples-template/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples-template/vite.config.ts b/examples/sdk-interactive-examples/examples-template/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples-template/vite.config.ts +++ b/examples/sdk-interactive-examples/examples-template/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-attributefilter/package.json b/examples/sdk-interactive-examples/examples/example-attributefilter/package.json index b67dba9cf46..fc65a1aed4b 100644 --- a/examples/sdk-interactive-examples/examples/example-attributefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-attributefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-attributefilter", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example demonstrates how to use the AttributeFilter component to filter data in a visualization.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-attributefilter/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-attributefilter/scripts/updateWorkspaceVersion.mjs index a409578244e..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-attributefilter/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-attributefilter/scripts/updateWorkspaceVersion.mjs @@ -1,7 +1,8 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-attributefilter/vite.config.ts b/examples/sdk-interactive-examples/examples/example-attributefilter/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-attributefilter/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-attributefilter/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-chartconfig/package.json b/examples/sdk-interactive-examples/examples/example-chartconfig/package.json index e5312c573dc..2f98d6b4f87 100644 --- a/examples/sdk-interactive-examples/examples/example-chartconfig/package.json +++ b/examples/sdk-interactive-examples/examples/example-chartconfig/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-chartconfig", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This interactive example demonstrates how to manipulate the chart config.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-chartconfig/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-chartconfig/scripts/updateWorkspaceVersion.mjs index a409578244e..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-chartconfig/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-chartconfig/scripts/updateWorkspaceVersion.mjs @@ -1,7 +1,8 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-chartconfig/vite.config.ts b/examples/sdk-interactive-examples/examples/example-chartconfig/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-chartconfig/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-chartconfig/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-columnchart/package.json b/examples/sdk-interactive-examples/examples/example-columnchart/package.json index bb93cf2ae7a..ddd80f30114 100644 --- a/examples/sdk-interactive-examples/examples/example-columnchart/package.json +++ b/examples/sdk-interactive-examples/examples/example-columnchart/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-columnchart", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example demonstrates the usage of the ColumnChart component with the viewBy and stackBy properties.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-columnchart/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-columnchart/scripts/updateWorkspaceVersion.mjs index a409578244e..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-columnchart/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-columnchart/scripts/updateWorkspaceVersion.mjs @@ -1,7 +1,8 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-columnchart/vite.config.ts b/examples/sdk-interactive-examples/examples/example-columnchart/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-columnchart/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-columnchart/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-combochart/package.json b/examples/sdk-interactive-examples/examples/example-combochart/package.json index 20e6a550901..a558e74c104 100644 --- a/examples/sdk-interactive-examples/examples/example-combochart/package.json +++ b/examples/sdk-interactive-examples/examples/example-combochart/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-combochart", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "Example demonstrates ComboChart secondaryMeasures definition. ", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-combochart/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-combochart/scripts/updateWorkspaceVersion.mjs index a409578244e..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-combochart/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-combochart/scripts/updateWorkspaceVersion.mjs @@ -1,7 +1,8 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-combochart/vite.config.ts b/examples/sdk-interactive-examples/examples/example-combochart/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-combochart/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-combochart/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-dashboard/package.json b/examples/sdk-interactive-examples/examples/example-dashboard/package.json index 74256133401..1a7e1926cb4 100644 --- a/examples/sdk-interactive-examples/examples/example-dashboard/package.json +++ b/examples/sdk-interactive-examples/examples/example-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-dashboard", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example shows how to use the Dashboard component.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-dashboard/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-dashboard/scripts/updateWorkspaceVersion.mjs index 2fe8462f644..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-dashboard/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-dashboard/scripts/updateWorkspaceVersion.mjs @@ -1,4 +1,5 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; import packageData from "../package.json" with { type: "json" }; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-dashboard/vite.config.ts b/examples/sdk-interactive-examples/examples/example-dashboard/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-dashboard/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-dashboard/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-datefilter/package.json b/examples/sdk-interactive-examples/examples/example-datefilter/package.json index caa4a8cfff8..79465f4af92 100644 --- a/examples/sdk-interactive-examples/examples/example-datefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-datefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-datefilter", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "Example demonstrates usage of Date Filter component.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-datefilter/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-datefilter/scripts/updateWorkspaceVersion.mjs index a409578244e..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-datefilter/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-datefilter/scripts/updateWorkspaceVersion.mjs @@ -1,7 +1,8 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-datefilter/vite.config.ts b/examples/sdk-interactive-examples/examples/example-datefilter/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-datefilter/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-datefilter/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json b/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json index ce4f2e3b366..d672d4f5eab 100644 --- a/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json +++ b/examples/sdk-interactive-examples/examples/example-dependentfilters/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-dependentfilters", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example demonstrates how to use multiple attribute filters linked together to filter data in a visualization.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-dependentfilters/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-dependentfilters/scripts/updateWorkspaceVersion.mjs index 2fe8462f644..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-dependentfilters/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-dependentfilters/scripts/updateWorkspaceVersion.mjs @@ -1,4 +1,5 @@ // (C) 2026 GoodData Corporation + import fs from "fs"; import packageData from "../package.json" with { type: "json" }; @@ -24,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-dependentfilters/vite.config.ts b/examples/sdk-interactive-examples/examples/example-dependentfilters/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-dependentfilters/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-dependentfilters/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-execute/package.json b/examples/sdk-interactive-examples/examples/example-execute/package.json index 46571274b17..f3196f5c534 100644 --- a/examples/sdk-interactive-examples/examples/example-execute/package.json +++ b/examples/sdk-interactive-examples/examples/example-execute/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-execute", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example demonstrates using Execute component and build custom visualization.", "license": "LicenseRef-LICENSE", @@ -64,8 +64,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-execute/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-execute/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-execute/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-execute/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-execute/vite.config.ts b/examples/sdk-interactive-examples/examples/example-execute/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-execute/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-execute/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-granularity/package.json b/examples/sdk-interactive-examples/examples/example-granularity/package.json index 57d4c3938fb..d607eb72eb9 100644 --- a/examples/sdk-interactive-examples/examples/example-granularity/package.json +++ b/examples/sdk-interactive-examples/examples/example-granularity/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-granularity", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example exmplains DateFilter granularity ", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-granularity/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-granularity/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-granularity/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-granularity/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-granularity/vite.config.ts b/examples/sdk-interactive-examples/examples/example-granularity/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-granularity/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-granularity/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-headline/package.json b/examples/sdk-interactive-examples/examples/example-headline/package.json index 1db631402f7..72d8dbe55e0 100644 --- a/examples/sdk-interactive-examples/examples/example-headline/package.json +++ b/examples/sdk-interactive-examples/examples/example-headline/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-headline", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example shows how to use the Headline component.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-headline/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-headline/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-headline/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-headline/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-headline/vite.config.ts b/examples/sdk-interactive-examples/examples/example-headline/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-headline/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-headline/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-pivottable/package.json b/examples/sdk-interactive-examples/examples/example-pivottable/package.json index fb88090731c..baac7d97226 100644 --- a/examples/sdk-interactive-examples/examples/example-pivottable/package.json +++ b/examples/sdk-interactive-examples/examples/example-pivottable/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-pivottable", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "Basic PivotTable manipulation.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-pivottable/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-pivottable/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-pivottable/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-pivottable/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-pivottable/vite.config.ts b/examples/sdk-interactive-examples/examples/example-pivottable/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-pivottable/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-pivottable/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json b/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json index 8718e83138a..4f22ef3e6f3 100644 --- a/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json +++ b/examples/sdk-interactive-examples/examples/example-relativedatefilter/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-relativedatefilter", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "Example demonstrates how to set relative DateFilter for visualization.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-relativedatefilter/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-relativedatefilter/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-relativedatefilter/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-relativedatefilter/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-relativedatefilter/vite.config.ts b/examples/sdk-interactive-examples/examples/example-relativedatefilter/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-relativedatefilter/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-relativedatefilter/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/examples/example-repeater/package.json b/examples/sdk-interactive-examples/examples/example-repeater/package.json index 8d4278d096f..7dfb768bb72 100644 --- a/examples/sdk-interactive-examples/examples/example-repeater/package.json +++ b/examples/sdk-interactive-examples/examples/example-repeater/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-example-repeater", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "This example demonstrates how to use Repeater component.", "license": "LicenseRef-LICENSE", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "process": "0.11.10", "rolldown": "1.0.3", "typescript": "7.0.2", diff --git a/examples/sdk-interactive-examples/examples/example-repeater/scripts/updateWorkspaceVersion.mjs b/examples/sdk-interactive-examples/examples/example-repeater/scripts/updateWorkspaceVersion.mjs index a815e858593..3f45c9395d9 100644 --- a/examples/sdk-interactive-examples/examples/example-repeater/scripts/updateWorkspaceVersion.mjs +++ b/examples/sdk-interactive-examples/examples/example-repeater/scripts/updateWorkspaceVersion.mjs @@ -2,7 +2,7 @@ import fs from "fs"; -import packageData from "../package.json" assert { type: "json" }; +import packageData from "../package.json" with { type: "json" }; const packageJsonPath = "./package.json"; @@ -25,4 +25,5 @@ packageData.devDependencies = updatedDevDependencies; await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageData, null, 2)); +// oxlint-disable-next-line eslint-js/no-console console.log("Updated package.json with version:", packageData.version); diff --git a/examples/sdk-interactive-examples/examples/example-repeater/vite.config.ts b/examples/sdk-interactive-examples/examples/example-repeater/vite.config.ts index 3a04ff46b81..da3b113b984 100644 --- a/examples/sdk-interactive-examples/examples/example-repeater/vite.config.ts +++ b/examples/sdk-interactive-examples/examples/example-repeater/vite.config.ts @@ -9,6 +9,7 @@ import packageJson from "./package.json" with { type: "json" }; const backendUrl = packageJson.gooddata.hostname; const workspaceId = packageJson.gooddata.workspaceId; +// oxlint-disable-next-line eslint-js/no-console console.log("backendUrl: ", backendUrl); export default defineConfig({ diff --git a/examples/sdk-interactive-examples/package.json b/examples/sdk-interactive-examples/package.json index 5cc7d3e123e..ea7ab0fb86d 100644 --- a/examples/sdk-interactive-examples/package.json +++ b/examples/sdk-interactive-examples/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-interactive-examples", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": false, "description": "GoodData React interactive examples", "license": "LicenseRef-LICENSE", diff --git a/libs/api-client-tiger/openapi-generator/index.mustache b/libs/api-client-tiger/openapi-generator/index.mustache index d9939b2b520..f5c045c6f6c 100644 --- a/libs/api-client-tiger/openapi-generator/index.mustache +++ b/libs/api-client-tiger/openapi-generator/index.mustache @@ -1,6 +1,8 @@ // (C) 2024-2026 GoodData Corporation /* oxlint-disable no-barrel-files/no-barrel-files */ +/* eslint-disable no-restricted-syntax */ + {{>licenseInfo}} diff --git a/libs/api-client-tiger/package.json b/libs/api-client-tiger/package.json index 0504417828c..c303c88ca32 100644 --- a/libs/api-client-tiger/package.json +++ b/libs/api-client-tiger/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/api-client-tiger", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "API Client for GoodData Cloud and GoodData.CN", "license": "MIT", "author": "GoodData", @@ -92,8 +92,9 @@ "mkdirp": "1.0.4", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/api-client-tiger/src/endpoints/actions/index.ts b/libs/api-client-tiger/src/endpoints/actions/index.ts index ca6c7dd7566..168b05d2cd8 100644 --- a/libs/api-client-tiger/src/endpoints/actions/index.ts +++ b/libs/api-client-tiger/src/endpoints/actions/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Actions API - Export commonly used ActionsApi functions export { diff --git a/libs/api-client-tiger/src/endpoints/authActions/index.ts b/libs/api-client-tiger/src/endpoints/authActions/index.ts index 8fc5d00bd88..b5a7f4c96dd 100644 --- a/libs/api-client-tiger/src/endpoints/authActions/index.ts +++ b/libs/api-client-tiger/src/endpoints/authActions/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Auth Actions API - Export auth-related ActionsApi functions with AuthApi_ prefix export { diff --git a/libs/api-client-tiger/src/endpoints/automation/index.ts b/libs/api-client-tiger/src/endpoints/automation/index.ts index fd1586d6bc8..b88cec37845 100644 --- a/libs/api-client-tiger/src/endpoints/automation/index.ts +++ b/libs/api-client-tiger/src/endpoints/automation/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Automation API - Export ActionsAutomation functions with AutomationApi_ prefix export { diff --git a/libs/api-client-tiger/src/endpoints/entitiesObjects/index.ts b/libs/api-client-tiger/src/endpoints/entitiesObjects/index.ts index 94e1a867b07..7143cea0772 100644 --- a/libs/api-client-tiger/src/endpoints/entitiesObjects/index.ts +++ b/libs/api-client-tiger/src/endpoints/entitiesObjects/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax export { // DashboardsApi diff --git a/libs/api-client-tiger/src/endpoints/execution/index.ts b/libs/api-client-tiger/src/endpoints/execution/index.ts index 3c2409efaf3..06dafbf9880 100644 --- a/libs/api-client-tiger/src/endpoints/execution/index.ts +++ b/libs/api-client-tiger/src/endpoints/execution/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax export { // Execution API diff --git a/libs/api-client-tiger/src/endpoints/explain/index.ts b/libs/api-client-tiger/src/endpoints/explain/index.ts index 18fbec51c5a..5999c19f0ed 100644 --- a/libs/api-client-tiger/src/endpoints/explain/index.ts +++ b/libs/api-client-tiger/src/endpoints/explain/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Explain API export { diff --git a/libs/api-client-tiger/src/endpoints/export/index.ts b/libs/api-client-tiger/src/endpoints/export/index.ts index faaab620336..174cc5cde24 100644 --- a/libs/api-client-tiger/src/endpoints/export/index.ts +++ b/libs/api-client-tiger/src/endpoints/export/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Export API - Export ActionsExport functions with ExportApi_ prefix export { diff --git a/libs/api-client-tiger/src/endpoints/genAI/index.ts b/libs/api-client-tiger/src/endpoints/genAI/index.ts index 37c0634ec49..95c502b5750 100644 --- a/libs/api-client-tiger/src/endpoints/genAI/index.ts +++ b/libs/api-client-tiger/src/endpoints/genAI/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax import type { AxiosInstance, AxiosPromise, AxiosRequestConfig } from "axios"; diff --git a/libs/api-client-tiger/src/endpoints/labelElements/index.ts b/libs/api-client-tiger/src/endpoints/labelElements/index.ts index 1cdf8ceb68b..bd92dee9d6b 100644 --- a/libs/api-client-tiger/src/endpoints/labelElements/index.ts +++ b/libs/api-client-tiger/src/endpoints/labelElements/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Label Elements API export { diff --git a/libs/api-client-tiger/src/endpoints/layout/index.ts b/libs/api-client-tiger/src/endpoints/layout/index.ts index 8279b6460db..6ecc9126afc 100644 --- a/libs/api-client-tiger/src/endpoints/layout/index.ts +++ b/libs/api-client-tiger/src/endpoints/layout/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Layout API - Export all LayoutApi functions export { diff --git a/libs/api-client-tiger/src/endpoints/locationStyle/index.ts b/libs/api-client-tiger/src/endpoints/locationStyle/index.ts index b63c4aac70d..120ee0671aa 100644 --- a/libs/api-client-tiger/src/endpoints/locationStyle/index.ts +++ b/libs/api-client-tiger/src/endpoints/locationStyle/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax export { LocationStyleApi_GetDefaultStyle, diff --git a/libs/api-client-tiger/src/endpoints/profile/index.ts b/libs/api-client-tiger/src/endpoints/profile/index.ts index 569ed5932ca..24482734e41 100644 --- a/libs/api-client-tiger/src/endpoints/profile/index.ts +++ b/libs/api-client-tiger/src/endpoints/profile/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax export { isLiveFeatures, diff --git a/libs/api-client-tiger/src/endpoints/result/index.ts b/libs/api-client-tiger/src/endpoints/result/index.ts index 3be81890dfc..6851aaa465a 100644 --- a/libs/api-client-tiger/src/endpoints/result/index.ts +++ b/libs/api-client-tiger/src/endpoints/result/index.ts @@ -1,13 +1,13 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax import { type AxiosInstance, type AxiosPromise, type AxiosRequestConfig } from "axios"; import type { GeoJsonFeatureCollection } from "../../generated/result-json-api/api.js"; // Result API - Export ActionsApi functions with ResultApi_ prefix -// eslint-disable-next-line no-duplicate-imports -- Using both import and export from same module for re-exporting with aliases +// oxlint-disable-next-line eslint-js/no-duplicate-imports -- Using both import and export from same module for re-exporting with aliases export { ActionsApi_AnalyzeCsv as ResultApi_AnalyzeCsv, type ActionsApiAnalyzeCsvRequest as ResultApiAnalyzeCsvRequest, diff --git a/libs/api-client-tiger/src/endpoints/scanModel/index.ts b/libs/api-client-tiger/src/endpoints/scanModel/index.ts index c10f84dd1cd..c0f086a2302 100644 --- a/libs/api-client-tiger/src/endpoints/scanModel/index.ts +++ b/libs/api-client-tiger/src/endpoints/scanModel/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Scan Model API - Export scan model ActionsApi functions with ScanModelApi_ prefix export { diff --git a/libs/api-client-tiger/src/endpoints/smartFunctions/index.ts b/libs/api-client-tiger/src/endpoints/smartFunctions/index.ts index c399f3696c2..f06af4036be 100644 --- a/libs/api-client-tiger/src/endpoints/smartFunctions/index.ts +++ b/libs/api-client-tiger/src/endpoints/smartFunctions/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Smart Functions API - Export ActionsApi functions with SmartFunctionsApi_ prefix export { diff --git a/libs/api-client-tiger/src/endpoints/userManagement/index.ts b/libs/api-client-tiger/src/endpoints/userManagement/index.ts index 84869aa4c2a..0f8881127f2 100644 --- a/libs/api-client-tiger/src/endpoints/userManagement/index.ts +++ b/libs/api-client-tiger/src/endpoints/userManagement/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // User Management API export { diff --git a/libs/api-client-tiger/src/endpoints/validDescendants/index.ts b/libs/api-client-tiger/src/endpoints/validDescendants/index.ts index dfc2424da27..39a0348a1cb 100644 --- a/libs/api-client-tiger/src/endpoints/validDescendants/index.ts +++ b/libs/api-client-tiger/src/endpoints/validDescendants/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Valid Descendants API export { diff --git a/libs/api-client-tiger/src/endpoints/validObjects/index.ts b/libs/api-client-tiger/src/endpoints/validObjects/index.ts index 03d560ecdbf..8800e28a656 100644 --- a/libs/api-client-tiger/src/endpoints/validObjects/index.ts +++ b/libs/api-client-tiger/src/endpoints/validObjects/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax // Valid Objects API export { diff --git a/libs/api-client-tiger/src/gd-tiger-model/index.ts b/libs/api-client-tiger/src/gd-tiger-model/index.ts index b8c6f448758..29d6d416d4e 100644 --- a/libs/api-client-tiger/src/gd-tiger-model/index.ts +++ b/libs/api-client-tiger/src/gd-tiger-model/index.ts @@ -1,5 +1,7 @@ // (C) 2007-2026 GoodData Corporation +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + import * as AnalyticalDashboardModelV1 from "./AnalyticalDashboardModelV1.js"; import * as AnalyticalDashboardModelV2 from "./AnalyticalDashboardModelV2.js"; import * as AnalyticalDashboardModelV3 from "./AnalyticalDashboardModelV3.js"; @@ -9,36 +11,30 @@ import * as VisualizationObjectModelV2 from "./VisualizationObjectModelV2.js"; /** * @public */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export { AnalyticalDashboardModelV1 }; /** * @public */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export { AnalyticalDashboardModelV2 }; /** * @alpha */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export { AnalyticalDashboardModelV3 }; /** * @public */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export { VisualizationObjectModelV1 }; /** * @public */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export { VisualizationObjectModelV2 }; /** * Tiger-specific types for analytical objects * @internal */ -// oxlint-disable-next-line no-barrel-files/no-barrel-files export type * from "./TigerTypes.js"; diff --git a/libs/api-client-tiger/src/generated/afm-rest-api/index.ts b/libs/api-client-tiger/src/generated/afm-rest-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/afm-rest-api/index.ts +++ b/libs/api-client-tiger/src/generated/afm-rest-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/ai-json-api/index.ts b/libs/api-client-tiger/src/generated/ai-json-api/index.ts index e8f7c312b4b..f09a96bf37d 100644 --- a/libs/api-client-tiger/src/generated/ai-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/ai-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * Gen-AI HTTP API * HTTP API for chat sessions, items, and SSE messages. diff --git a/libs/api-client-tiger/src/generated/auth-json-api/index.ts b/libs/api-client-tiger/src/generated/auth-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/auth-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/auth-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/automation-json-api/index.ts b/libs/api-client-tiger/src/generated/automation-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/automation-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/automation-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/export-json-api/index.ts b/libs/api-client-tiger/src/generated/export-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/export-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/export-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/metadata-json-api/index.ts b/libs/api-client-tiger/src/generated/metadata-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/metadata-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/metadata-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/result-json-api/index.ts b/libs/api-client-tiger/src/generated/result-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/result-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/result-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/generated/scan-json-api/index.ts b/libs/api-client-tiger/src/generated/scan-json-api/index.ts index 6f5b9ded866..153ab89f1d5 100644 --- a/libs/api-client-tiger/src/generated/scan-json-api/index.ts +++ b/libs/api-client-tiger/src/generated/scan-json-api/index.ts @@ -1,6 +1,7 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + /** * OpenAPI definition * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) diff --git a/libs/api-client-tiger/src/index.ts b/libs/api-client-tiger/src/index.ts index 4d186f8e1d9..4a6735ac654 100644 --- a/libs/api-client-tiger/src/index.ts +++ b/libs/api-client-tiger/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax /** * This package provides low-level functions for communication with GoodData Cloud and GoodData.CN. @@ -6453,5 +6453,5 @@ export const defaultTigerClient: ITigerClient = tigerClientFactory(defaultAxios) /** * @deprecated This will be removed in the next major release, please use the named export "tigerFactory" instead */ -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default defaultTigerClient; diff --git a/libs/sdk-backend-base/.dependency-cruiser.js b/libs/sdk-backend-base/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-backend-base/.dependency-cruiser.js +++ b/libs/sdk-backend-base/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-backend-base/api/sdk-backend-base.api.md b/libs/sdk-backend-base/api/sdk-backend-base.api.md index a45439a9518..16a74ac281b 100644 --- a/libs/sdk-backend-base/api/sdk-backend-base.api.md +++ b/libs/sdk-backend-base/api/sdk-backend-base.api.md @@ -108,6 +108,7 @@ import { IGetVisualizationClassesOptions } from '@gooddata/sdk-backend-spi'; import { IGroupableCatalogItemBase } from '@gooddata/sdk-model'; import { IInsight } from '@gooddata/sdk-model'; import { IInsightDefinition } from '@gooddata/sdk-model'; +import { IInsightDefinitionWithOptionalIdentity } from '@gooddata/sdk-model'; import { IInsightReferences } from '@gooddata/sdk-backend-spi'; import { IInsightReferencing } from '@gooddata/sdk-backend-spi'; import { IInsightsQuery } from '@gooddata/sdk-backend-spi'; @@ -689,7 +690,7 @@ export abstract class DecoratedWorkspaceCatalogFactory implements IWorkspaceCata export abstract class DecoratedWorkspaceDashboardsService implements IWorkspaceDashboardsService { protected constructor(decorated: IWorkspaceDashboardsService, workspace: string); // (undocumented) - createDashboard(dashboard: IDashboardDefinition): Promise; + createDashboard(dashboard: IDashboardDefinition, generateId?: boolean): Promise; // (undocumented) createDashboardPlugin(plugin: IDashboardPluginDefinition): Promise; // (undocumented) @@ -803,7 +804,7 @@ export abstract class DecoratedWorkspaceExportTemplatesService implements IWorks export abstract class DecoratedWorkspaceInsightsService implements IWorkspaceInsightsService { protected constructor(decorated: IWorkspaceInsightsService, workspace: string); // (undocumented) - createInsight(insight: IInsightDefinition): Promise; + createInsight(insight: IInsightDefinitionWithOptionalIdentity, generateId?: boolean): Promise; // (undocumented) protected readonly decorated: IWorkspaceInsightsService; // (undocumented) diff --git a/libs/sdk-backend-base/package.json b/libs/sdk-backend-base/package.json index 95689c0d5c3..856e9a91ec2 100644 --- a/libs/sdk-backend-base/package.json +++ b/libs/sdk-backend-base/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-base", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Base for backend implementations", "license": "MIT", "author": "GoodData", @@ -71,8 +71,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-backend-base/src/cachingBackend/index.ts b/libs/sdk-backend-base/src/cachingBackend/index.ts index 28868a97d3b..3479877cbb9 100644 --- a/libs/sdk-backend-base/src/cachingBackend/index.ts +++ b/libs/sdk-backend-base/src/cachingBackend/index.ts @@ -83,7 +83,7 @@ import { type IFiscalYear, type IGeoJsonFeature, type IInsight, - type IInsightDefinition, + type IInsightDefinitionWithOptionalIdentity, type IMeasure, type IMeasureDefinitionType, type IMetadataObject, @@ -2367,9 +2367,12 @@ class WithInsightsCaching extends DecoratedWorkspaceInsightsService { // Insight mutations are not cached, but they must invalidate the read cache so that edits // made during a session are never served stale (the SDK has no separate invalidation signal). - public override async createInsight(insight: IInsightDefinition): Promise { + public override async createInsight( + insight: IInsightDefinitionWithOptionalIdentity, + generateId?: boolean, + ): Promise { try { - return await super.createInsight(insight); + return await super.createInsight(insight, generateId); } finally { this.invalidateCache(); } diff --git a/libs/sdk-backend-base/src/decoratedBackend/dashboards.ts b/libs/sdk-backend-base/src/decoratedBackend/dashboards.ts index 02ed5420f2f..58091d9ae97 100644 --- a/libs/sdk-backend-base/src/decoratedBackend/dashboards.ts +++ b/libs/sdk-backend-base/src/decoratedBackend/dashboards.ts @@ -103,8 +103,8 @@ export abstract class DecoratedWorkspaceDashboardsService implements IWorkspaceD return this.decorated.getDashboardReferencedObjects(dashboard, types); } - createDashboard(dashboard: IDashboardDefinition): Promise { - return this.decorated.createDashboard(dashboard); + createDashboard(dashboard: IDashboardDefinition, generateId?: boolean): Promise { + return this.decorated.createDashboard(dashboard, generateId); } updateDashboard(dashboard: IDashboard, updatedDashboard: IDashboardDefinition): Promise { diff --git a/libs/sdk-backend-base/src/decoratedBackend/insights.ts b/libs/sdk-backend-base/src/decoratedBackend/insights.ts index 0374150d622..30b2e306b42 100644 --- a/libs/sdk-backend-base/src/decoratedBackend/insights.ts +++ b/libs/sdk-backend-base/src/decoratedBackend/insights.ts @@ -18,6 +18,7 @@ import { type IFilter, type IInsight, type IInsightDefinition, + type IInsightDefinitionWithOptionalIdentity, type IMetadataObjectBase, type IMetadataObjectIdentity, type IObjectCertificationWrite, @@ -68,8 +69,11 @@ export abstract class DecoratedWorkspaceInsightsService implements IWorkspaceIns return this.decorated.getInsightsQuery(); } - public createInsight(insight: IInsightDefinition): Promise { - return this.decorated.createInsight(insight); + public createInsight( + insight: IInsightDefinitionWithOptionalIdentity, + generateId?: boolean, + ): Promise { + return this.decorated.createInsight(insight, generateId); } public updateInsight(insight: IInsight): Promise { diff --git a/libs/sdk-backend-base/src/index.ts b/libs/sdk-backend-base/src/index.ts index 8b868624b7d..a0a438685b0 100644 --- a/libs/sdk-backend-base/src/index.ts +++ b/libs/sdk-backend-base/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides foundational reusable code useful for building new or decorating existing Analytical Backend implementations. diff --git a/libs/sdk-backend-base/vitest.config.ts b/libs/sdk-backend-base/vitest.config.ts index 04ad74ff664..09371ab4118 100644 --- a/libs/sdk-backend-base/vitest.config.ts +++ b/libs/sdk-backend-base/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-backend-mockingbird/.dependency-cruiser.js b/libs/sdk-backend-mockingbird/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-backend-mockingbird/.dependency-cruiser.js +++ b/libs/sdk-backend-mockingbird/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-backend-mockingbird/package.json b/libs/sdk-backend-mockingbird/package.json index c9018aaf400..168a39b9b5a 100644 --- a/libs/sdk-backend-mockingbird/package.json +++ b/libs/sdk-backend-mockingbird/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-mockingbird", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "Mock GoodData Backend SPI implementation", "license": "MIT", "author": "GoodData", @@ -67,8 +67,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-backend-mockingbird/src/index.ts b/libs/sdk-backend-mockingbird/src/index.ts index 7a05293d103..23959cb6eb9 100644 --- a/libs/sdk-backend-mockingbird/src/index.ts +++ b/libs/sdk-backend-mockingbird/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides a mock Analytical Backend implementation used mainly for testing. diff --git a/libs/sdk-backend-mockingbird/vitest.config.ts b/libs/sdk-backend-mockingbird/vitest.config.ts index 04ad74ff664..09371ab4118 100644 --- a/libs/sdk-backend-mockingbird/vitest.config.ts +++ b/libs/sdk-backend-mockingbird/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-backend-spi/.dependency-cruiser.js b/libs/sdk-backend-spi/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/libs/sdk-backend-spi/.dependency-cruiser.js +++ b/libs/sdk-backend-spi/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/libs/sdk-backend-spi/api/sdk-backend-spi.api.md b/libs/sdk-backend-spi/api/sdk-backend-spi.api.md index 65117f52a64..9a3f4c941f2 100644 --- a/libs/sdk-backend-spi/api/sdk-backend-spi.api.md +++ b/libs/sdk-backend-spi/api/sdk-backend-spi.api.md @@ -108,6 +108,7 @@ import { IGeoJsonFeature } from '@gooddata/sdk-model'; import { IGranularAccessGrantee } from '@gooddata/sdk-model'; import { IInsight } from '@gooddata/sdk-model'; import { IInsightDefinition } from '@gooddata/sdk-model'; +import type { IInsightDefinitionWithOptionalIdentity } from '@gooddata/sdk-model'; import { IIpAllowlist } from '@gooddata/sdk-model'; import { IIpAllowlistDefinition } from '@gooddata/sdk-model'; import type { IListedDashboard } from '@gooddata/sdk-model'; @@ -2980,7 +2981,7 @@ export interface IWorkspaceComputedAttributesService { // @alpha export interface IWorkspaceDashboardsService { - createDashboard(dashboard: IDashboardDefinition): Promise; + createDashboard(dashboard: IDashboardDefinition, generateId?: boolean): Promise; createDashboardPlugin(plugin: IDashboardPluginDefinition): Promise; createFilterView(filterView: IDashboardFilterViewSaveRequest): Promise; createScheduledMail(scheduledMail: IScheduledMailDefinition, exportFilterContext?: IFilterContextDefinition): Promise; @@ -3110,7 +3111,7 @@ export interface IWorkspaceFactsService { // @public export interface IWorkspaceInsightsService { - createInsight(insight: IInsightDefinition): Promise; + createInsight(insight: IInsightDefinitionWithOptionalIdentity, generatedId?: boolean): Promise; deleteInsight(ref: ObjRef): Promise; getInsight(ref: ObjRef, options?: IGetInsightOptions): Promise; getInsightReferencedObjects(insight: IInsight, types?: SupportedInsightReferenceTypes[]): Promise; diff --git a/libs/sdk-backend-spi/package.json b/libs/sdk-backend-spi/package.json index cadb57f7abe..d0b0d4f544f 100644 --- a/libs/sdk-backend-spi/package.json +++ b/libs/sdk-backend-spi/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-spi", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Backend SPI abstraction interfaces", "license": "MIT", "author": "GoodData", @@ -62,8 +62,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-backend-spi/src/common/settings.ts b/libs/sdk-backend-spi/src/common/settings.ts index 8e1c3df3a44..c29e7a5b07e 100644 --- a/libs/sdk-backend-spi/src/common/settings.ts +++ b/libs/sdk-backend-spi/src/common/settings.ts @@ -1,5 +1,5 @@ // (C) 2020-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export type { IUserSettings, IWorkspaceSettings, IUserWorkspaceSettings } from "@gooddata/sdk-model"; diff --git a/libs/sdk-backend-spi/src/index.ts b/libs/sdk-backend-spi/src/index.ts index bc758d8c59a..a8367bbba1a 100644 --- a/libs/sdk-backend-spi/src/index.ts +++ b/libs/sdk-backend-spi/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides definitions of the Service Provider Interface (SPI) for the Analytical Backend. diff --git a/libs/sdk-backend-spi/src/workspace/dashboards/index.ts b/libs/sdk-backend-spi/src/workspace/dashboards/index.ts index 13c88a09e21..a82cbe3159d 100644 --- a/libs/sdk-backend-spi/src/workspace/dashboards/index.ts +++ b/libs/sdk-backend-spi/src/workspace/dashboards/index.ts @@ -635,9 +635,10 @@ export interface IWorkspaceDashboardsService { * Create and save dashboard for the provided dashboard definition * * @param dashboard - dashboard definition + * @param generateId - whether to generate a new identifier for the dashboard, default is true * @returns promise of the created dashboard */ - createDashboard(dashboard: IDashboardDefinition): Promise; + createDashboard(dashboard: IDashboardDefinition, generateId?: boolean): Promise; /** * Update dashboard diff --git a/libs/sdk-backend-spi/src/workspace/insights/index.ts b/libs/sdk-backend-spi/src/workspace/insights/index.ts index 22fea59af67..c7952922c64 100644 --- a/libs/sdk-backend-spi/src/workspace/insights/index.ts +++ b/libs/sdk-backend-spi/src/workspace/insights/index.ts @@ -8,6 +8,7 @@ import type { IFilter, IInsight, IInsightDefinition, + IInsightDefinitionWithOptionalIdentity, IMetadataObject, IMetadataObjectBase, IMetadataObjectIdentity, @@ -96,9 +97,10 @@ export interface IWorkspaceInsightsService { * Create and save insight for the provided insight definition * * @param insight - insight definition + * @param generatedId - if true, backend will generate insight id, otherwise the id must be provided in the insight definition, defaults to true * @returns promise of created insight */ - createInsight(insight: IInsightDefinition): Promise; + createInsight(insight: IInsightDefinitionWithOptionalIdentity, generatedId?: boolean): Promise; /** * Update provided insight diff --git a/libs/sdk-backend-spi/vitest.config.ts b/libs/sdk-backend-spi/vitest.config.ts index e8188177383..f1f67daac91 100644 --- a/libs/sdk-backend-spi/vitest.config.ts +++ b/libs/sdk-backend-spi/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-backend-tiger/.dependency-cruiser.js b/libs/sdk-backend-tiger/.dependency-cruiser.js index 91b5e93379d..1f55fadc71a 100644 --- a/libs/sdk-backend-tiger/.dependency-cruiser.js +++ b/libs/sdk-backend-tiger/.dependency-cruiser.js @@ -8,7 +8,6 @@ import { moduleWithDependencies, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ ...DefaultRules, diff --git a/libs/sdk-backend-tiger/package.json b/libs/sdk-backend-tiger/package.json index 022b8ca878a..aa56166b4fc 100644 --- a/libs/sdk-backend-tiger/package.json +++ b/libs/sdk-backend-tiger/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-backend-tiger", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Backend SPI implementation for GoodData Cloud and GoodData.CN", "license": "MIT", "author": "GoodData", @@ -93,8 +93,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-backend-tiger/src/backend/index.ts b/libs/sdk-backend-tiger/src/backend/index.ts index b9d7b7f97c0..ff764ecae03 100644 --- a/libs/sdk-backend-tiger/src/backend/index.ts +++ b/libs/sdk-backend-tiger/src/backend/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -import { type AxiosInstance, type AxiosResponse } from "axios"; +import axios, { type AxiosInstance, type AxiosResponse } from "axios"; import { inRange, isEmpty, isError, omit } from "lodash-es"; import { invariant } from "ts-invariant"; @@ -449,6 +449,12 @@ function interceptBackendErrorsToConsole(client: AxiosInstance): AxiosInstance { (error) => { const response: AxiosResponse = error.response; + // Cancellations are expected whenever a component aborts its in-flight requests + // on unmount; logging them as backend errors only floods the console. + if (axios.isCancel(error)) { + return Promise.reject(error); + } + // If there is no response object (for example for blocked requests), print the whole error. if (!response) { console.error("Tiger backend threw an error:", error); diff --git a/libs/sdk-backend-tiger/src/backend/workspace/dashboards/index.ts b/libs/sdk-backend-tiger/src/backend/workspace/dashboards/index.ts index d63829f8143..3e0a407aa1e 100644 --- a/libs/sdk-backend-tiger/src/backend/workspace/dashboards/index.ts +++ b/libs/sdk-backend-tiger/src/backend/workspace/dashboards/index.ts @@ -463,7 +463,10 @@ export class TigerWorkspaceDashboards implements IWorkspaceDashboardsService { ); }; - public createDashboard = async (dashboard: IDashboardDefinition): Promise => { + public createDashboard = async ( + dashboard: IDashboardDefinition, + generateId = true, + ): Promise => { // Process filter contexts for each tab first const dashboardWithTabFilterContexts = await this.processDashboardTabsFilterContexts(dashboard); @@ -495,6 +498,11 @@ export class TigerWorkspaceDashboards implements IWorkspaceDashboardsService { workspaceId: this.workspace, jsonApiAnalyticalDashboardPostOptionalIdDocument: { data: { + ...(generateId + ? {} + : { + id: dashboard.identifier, + }), type: "analyticalDashboard", attributes: { content: dashboardContent, diff --git a/libs/sdk-backend-tiger/src/backend/workspace/insights/index.ts b/libs/sdk-backend-tiger/src/backend/workspace/insights/index.ts index 7ffccc2e23a..48823ce36cf 100644 --- a/libs/sdk-backend-tiger/src/backend/workspace/insights/index.ts +++ b/libs/sdk-backend-tiger/src/backend/workspace/insights/index.ts @@ -47,6 +47,7 @@ import { type IFilter, type IInsight, type IInsightDefinition, + type IInsightDefinitionWithOptionalIdentity, type IMetadataObjectBase, type IMetadataObjectIdentity, type IObjectCertificationWrite, @@ -257,12 +258,18 @@ export class TigerWorkspaceInsights implements IWorkspaceInsightsService { return insight; }; - public createInsight = async (insight: IInsightDefinition): Promise => { + public createInsight = async ( + insight: IInsightDefinitionWithOptionalIdentity, + generateId = true, + ): Promise => { const createResponse = await this.authCall((client) => { return EntitiesApi_CreateEntityVisualizationObjects(client.axios, client.basePath, { workspaceId: this.workspace, jsonApiVisualizationObjectPostOptionalIdDocument: { data: { + ...(generateId || !insight.insight.identifier + ? {} + : { id: insight.insight.identifier }), type: "visualizationObject", attributes: { description: insightSummary(insight), diff --git a/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.test.ts b/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.test.ts index 94a4f460b76..71bf2daf3bb 100644 --- a/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.test.ts +++ b/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.test.ts @@ -4,11 +4,14 @@ import { describe, expect, it, vi } from "vitest"; import { type AiConversationItemResponse } from "@gooddata/api-client-tiger"; import { + type IChatConversationDashboardContent, + type IChatConversationItem, type IChatConversationMultipartContent, type IChatConversationMultipartPart, type IChatConversationWhatIfContent, isChatConversationSearchContent, } from "@gooddata/sdk-backend-spi"; +import { type IInsightWidget, type IdentifierRef } from "@gooddata/sdk-model"; import { convertChatConversationErrorFromBackend, @@ -356,4 +359,172 @@ describe("genAIConvertor", () => { errorSpy.mockRestore(); }); }); + + describe("dashboard patch", () => { + const DASHBOARD_ID = "existing_dashboard"; + + // Shapes mirror what gen-ai puts on the wire: the base rides ahead of the patch as its + // own `dashboard` part, carrying the real dashboard id and no references of its own. + const section = (vis: string) => ({ + widgets: [{ visualization: vis, title: vis, columns: 6, rows: 22 }], + }); + + const baseDocument = (widgets: string[]) => ({ + type: "dashboard", + id: DASHBOARD_ID, + title: "Existing dashboard", + version: "3", + sections: widgets.map(section), + }); + + const basePart = (widgets: string[] = ["chart1"]) => ({ + type: "dashboard", + dashboard: baseDocument(widgets), + saved_dashboard_id: DASHBOARD_ID, + references: null, + }); + + const patchPart = (vis: string) => ({ + type: "dashboardPatch", + patch: { + dashboard_id: DASHBOARD_ID, + operations: [{ op: "add", path: "/sections/-", value: section(vis) }], + references: null, + }, + }); + + const makeItem = (parts: object[], itemIndex = 0): AiConversationItemResponse => + ({ + conversationId: "conv-1", + itemIndex, + itemId: `item-${itemIndex}`, + role: "assistant", + createdAt: "2024-01-01T00:00:00Z", + content: { type: "multipart", parts }, + }) as unknown as AiConversationItemResponse; + + const dashboardParts = (item: IChatConversationItem) => + (item.content as IChatConversationMultipartContent).parts.filter( + (part): part is IChatConversationDashboardContent => part.type === "dashboard", + ); + + /** Ids of the visualizations the dashboard's widgets point at, in layout order. */ + const widgetVisualizations = (part: IChatConversationDashboardContent) => + (part.dashboard?.layout?.sections ?? []).flatMap((section) => + section.items.map((item) => { + const widget = item.widget as IInsightWidget; + return (widget?.insight as IdentifierRef)?.identifier; + }), + ); + + it("applies a patch against the base sent as a sibling part in the same message", () => { + const converted = convertChatConversationItemFromBackend( + makeItem([{ type: "text", text: "Added it." }, basePart(), patchPart("chart2")]), + [], + [], + dateNormalizer, + )!; + + const parts = dashboardParts(converted); + + // The base part is folded into the patch: a single card, showing the patched draft. + expect(parts).toHaveLength(1); + expect(widgetVisualizations(parts[0]!)).toEqual(["chart1", "chart2"]); + }); + + it("does not offer the unchanged base dashboard as a card of its own", () => { + const converted = convertChatConversationItemFromBackend( + makeItem([basePart(), patchPart("chart2")]), + [], + [], + dateNormalizer, + )!; + + const unchanged = dashboardParts(converted).filter( + (part) => widgetVisualizations(part).length === 1, + ); + + expect(unchanged).toEqual([]); + }); + + it("keeps a standalone dashboard part when no patch accompanies it", () => { + const converted = convertChatConversationItemFromBackend( + makeItem([basePart()]), + [], + [], + dateNormalizer, + )!; + + const parts = dashboardParts(converted); + + expect(parts).toHaveLength(1); + expect(widgetVisualizations(parts[0]!)).toEqual(["chart1"]); + expect(parts[0]!.saved).toBe(DASHBOARD_ID); + }); + + it("resolves the base from history for a follow-up patch that carries none", () => { + const history: IChatConversationItem[] = []; + + const first = convertChatConversationItemFromBackend( + makeItem([basePart(), patchPart("chart2")], 0), + [], + history, + dateNormalizer, + )!; + history.push(first); + + const second = convertChatConversationItemFromBackend( + makeItem([patchPart("chart3")], 1), + [], + history, + dateNormalizer, + )!; + + const parts = dashboardParts(second); + + expect(parts).toHaveLength(1); + // The operations are defined against the relayed document, so the second proposal + // rebases on the same base rather than on the result of the first one. + expect(widgetVisualizations(parts[0]!)).toEqual(["chart1", "chart3"]); + }); + + it("rebases on a base resent in the current message rather than the one in history", () => { + const history: IChatConversationItem[] = []; + + const first = convertChatConversationItemFromBackend( + makeItem([basePart(), patchPart("chart2")], 0), + [], + history, + dateNormalizer, + )!; + history.push(first); + + // The user edited the dashboard in between, so gen-ai relays the changed document. + const second = convertChatConversationItemFromBackend( + makeItem([basePart(["chart1", "edited"]), patchPart("chart3")], 1), + [], + history, + dateNormalizer, + )!; + + const parts = dashboardParts(second); + + expect(parts).toHaveLength(1); + expect(widgetVisualizations(parts[0]!)).toEqual(["chart1", "edited", "chart3"]); + }); + + it("renders no dashboard card when a patch has no base to apply to", () => { + const converted = convertChatConversationItemFromBackend( + makeItem([patchPart("chart2")]), + [], + [], + dateNormalizer, + )!; + + const parts = dashboardParts(converted); + + expect(parts).toHaveLength(1); + expect(parts[0]!.dashboard).toBeNull(); + }); + }); }); diff --git a/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.ts b/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.ts index 3c077011aa1..be758d710cc 100644 --- a/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.ts +++ b/libs/sdk-backend-tiger/src/convertors/fromBackend/genAIConvertor.ts @@ -15,6 +15,7 @@ import { type AiDashboardPatchPart, type AiInteractionStepResponse, type AiKeyDriverAnalysis, + type AiMultipartContentPartsInner, type AiSearchObject, type AiSearchRelationship, type AiSuggestions, @@ -195,84 +196,7 @@ function convertChatConversationContentFromBackend( case "multipart": return { type: "multipart", - // Unknown parts are dropped rather than failing the whole item, so the rest - // of the message (known parts) keeps rendering normally. - parts: content.parts - .map((part): IChatConversationMultipartPart | undefined => { - switch (part.type) { - case "text": - return { - type: "text", - text: part.text, - }; - case "alertProposal": - return { - type: "alertProposal", - alertProposal: convertAlertProposalFrom(part.alertProposal), - }; - case "visualization": - return { - type: "visualization", - visualization: part.visualization - ? visualizationObjectsItemToInsight( - yamlVisualisationToMetadataObject( - [], - part.visualization as AacVisualisation, - ), - ) - : null, - }; - case "dashboard": { - return { - type: "dashboard", - ...applyDashboardDefinition(part), - }; - } - case "dashboardPatch": { - if (!part.patch) { - return undefined; - } - - return { - type: "dashboard", - ...applyDashboardPatch(history, part.patch), - }; - } - case "kda": - return { - type: "kda", - kda: convertKda( - part.kda as AiKeyDriverAnalysis, - dateNormalizer, - locale, - timezone, - ), - }; - case "whatIf": - return { - type: "whatIf", - whatIf: convertWhatIf(part.whatIf as AiWhatIfScenario), - }; - case "searchResults": - return { - type: "searchResults", - keywords: part.keywords, - searchResults: convertSearchResults(part.objects), - relationships: convertSearchRelationships(part.relationships), - }; - case "clarifyingQuestions": - return { - type: "clarifyingQuestions", - questions: part.questions, - }; - default: - // Unknown part type (e.g. sent by a newer backend): log and drop - // it, do not fail the whole item. - assertNever(part); - return undefined; - } - }) - .filter((part): part is IChatConversationMultipartPart => part !== undefined), + parts: convertMultipartParts(content.parts, history, dateNormalizer, locale, timezone), suggestions: convertChatSuggestionItemFromBackend(content.suggestions), }; case "toolCall": @@ -297,6 +221,133 @@ function convertChatConversationContentFromBackend( } } +/** + * Converts the parts of a single multipart message. + * + * Parts are converted in order, and each one sees the siblings converted before it. That matters + * for `dashboardPatch`: the base document it applies to arrives as a `dashboard` part next to it + * in the same message, and `history` only holds the previous messages - the current item is + * appended to it after it has been converted, in both the replay and the SSE path. + * + * A `dashboard` part that only carries the base for a sibling patch is folded into that patch: it + * is used as the base but not emitted, so the message renders a single card showing the patched + * draft instead of a second card offering the unchanged dashboard. + */ +function convertMultipartParts( + rawParts: AiMultipartContentPartsInner[], + history: IChatConversationItem[], + dateNormalizer: DateNormalizer, + locale?: FormattingLocale, + timezone?: string, +): IChatConversationMultipartPart[] { + const patchedDashboardIds = new Set( + rawParts.flatMap((part) => + part.type === "dashboardPatch" && part.patch?.dashboard_id ? [part.patch.dashboard_id] : [], + ), + ); + + const siblings: IChatConversationMultipartPart[] = []; + const parts: IChatConversationMultipartPart[] = []; + + rawParts.forEach((rawPart) => { + const part = convertMultipartPart(rawPart, history, siblings, dateNormalizer, locale, timezone); + + // Unknown parts are dropped rather than failing the whole item, so the rest + // of the message (known parts) keeps rendering normally. + if (!part) { + return; + } + + siblings.push(part); + + const isPatchBase = + rawPart.type === "dashboard" && + part.type === "dashboard" && + !!part.dashboard && + patchedDashboardIds.has(part.dashboard.identifier); + + if (!isPatchBase) { + parts.push(part); + } + }); + + return parts; +} + +function convertMultipartPart( + part: AiMultipartContentPartsInner, + history: IChatConversationItem[], + siblings: IChatConversationMultipartPart[], + dateNormalizer: DateNormalizer, + locale?: FormattingLocale, + timezone?: string, +): IChatConversationMultipartPart | undefined { + switch (part.type) { + case "text": + return { + type: "text", + text: part.text, + }; + case "alertProposal": + return { + type: "alertProposal", + alertProposal: convertAlertProposalFrom(part.alertProposal), + }; + case "visualization": + return { + type: "visualization", + visualization: part.visualization + ? visualizationObjectsItemToInsight( + yamlVisualisationToMetadataObject([], part.visualization as AacVisualisation), + ) + : null, + }; + case "dashboard": { + return { + type: "dashboard", + ...applyDashboardDefinition(part), + }; + } + case "dashboardPatch": { + if (!part.patch) { + return undefined; + } + + return { + type: "dashboard", + ...applyDashboardPatch(history, siblings, part.patch), + }; + } + case "kda": + return { + type: "kda", + kda: convertKda(part.kda as AiKeyDriverAnalysis, dateNormalizer, locale, timezone), + }; + case "whatIf": + return { + type: "whatIf", + whatIf: convertWhatIf(part.whatIf as AiWhatIfScenario), + }; + case "searchResults": + return { + type: "searchResults", + keywords: part.keywords, + searchResults: convertSearchResults(part.objects), + relationships: convertSearchRelationships(part.relationships), + }; + case "clarifyingQuestions": + return { + type: "clarifyingQuestions", + questions: part.questions, + }; + default: + // Unknown part type (e.g. sent by a newer backend): log and drop + // it, do not fail the whole item. + assertNever(part); + return undefined; + } +} + function tryParseJson(jsonString: string): string | object { try { return JSON.parse(jsonString); @@ -655,7 +706,11 @@ function asNonPersistedFilterContext( // Apply dashboard patch to the items -function applyDashboardPatch(items: IChatConversationItem[], patch: AiDashboardPatchPart["patch"]) { +function applyDashboardPatch( + items: IChatConversationItem[], + siblings: IChatConversationMultipartPart[], + patch: AiDashboardPatchPart["patch"], +) { if (!patch?.dashboard_id) { return { dashboard: null, @@ -663,13 +718,14 @@ function applyDashboardPatch(items: IChatConversationItem[], patch: AiDashboardP }; } - const related = collectRelatedItems(items, { + const related = collectRelatedItems(items, siblings, { identifier: patch.dashboard_id, type: "analyticalDashboard", }); - const last = related[related.length - 1]; + // The most recent card for this dashboard need not carry a base - a patch that failed to + // apply resolves to one without. Rebase on the last card that actually has one. + const last = related.filter((part) => !!part.base).pop(); const base = last?.base; - const saved = last?.saved; const previousInsights = last?.insights ?? []; if (!base) { @@ -697,24 +753,43 @@ function applyDashboardPatch(items: IChatConversationItem[], patch: AiDashboardP }; } - const { dashboard, insights } = buildDashboardReferences(newDocument, patch.references, saved); + const { dashboard, insights } = buildDashboardReferences( + newDocument, + patch.references, + patch.dashboard_id, + ); + // `base` is carried over unchanged: the operations are defined against the relayed document, + // not against the result of a previous proposal, so a follow-up patch - which arrives without + // a base of its own as long as the relayed document has not changed - rebases on the same one. + // + // `saved` deliberately stays unset. The dashboard does exist saved, but this card describes a + // change the user has not accepted yet, so it has to offer the draft and not the saved copy. return { + base, dashboard: convertToTemporaryFilterContexts(dashboard), insights: [...previousInsights, ...insights], }; } -function collectRelatedItems(items: IChatConversationItem[], ref: ObjRef) { +function collectRelatedItems( + items: IChatConversationItem[], + siblings: IChatConversationMultipartPart[], + ref: ObjRef, +) { const related: IChatConversationDashboardContent[] = []; + const collect = (part: IChatConversationMultipartPart) => { + if (part.type === "dashboard" && part.dashboard && areObjRefsEqual(part.dashboard.ref, ref)) { + related.push(part); + } + }; items.forEach((item) => { if (item.content.type === "multipart") { - item.content.parts.forEach((part) => { - if (part.type === "dashboard" && part.dashboard && areObjRefsEqual(part.dashboard.ref, ref)) { - related.push(part); - } - }); + item.content.parts.forEach(collect); } }); + // Siblings come last: a base resent in the current message wins over anything in history, + // which is exactly what happens when the user edited the dashboard since the last proposal. + siblings.forEach(collect); return related; } @@ -770,13 +845,20 @@ function buildDashboardReferences( ? convertFilterContextFromBackend(buildFilterContextWrapper(data.filterContext)) : undefined; - const vis = [...(references?.visualizations ?? []), ...(references?.new_visualizations ?? [])]; - const insights = - vis.map((vis) => { + const existingInsights = + (references?.visualizations ?? []).map((vis) => { return visualizationObjectsItemToInsight( yamlVisualisationToMetadataObject(entities, vis as AacVisualisation), ); }) ?? []; + const newInsights = + (references?.new_visualizations ?? []).map((vis) => { + const insight = visualizationObjectsItemToInsight( + yamlVisualisationToMetadataObject(entities, vis as AacVisualisation), + ); + insight.insight.isDraft = true; + return insight; + }) ?? []; const dashboard = data ? convertDashboard( @@ -787,6 +869,6 @@ function buildDashboardReferences( return { dashboard, - insights, + insights: [...existingInsights, ...newInsights], }; } diff --git a/libs/sdk-backend-tiger/src/convertors/shared/layoutConverter.test.ts b/libs/sdk-backend-tiger/src/convertors/shared/layoutConverter.test.ts new file mode 100644 index 00000000000..04fa3231905 --- /dev/null +++ b/libs/sdk-backend-tiger/src/convertors/shared/layoutConverter.test.ts @@ -0,0 +1,88 @@ +// (C) 2026 GoodData Corporation + +import { describe, expect, it } from "vitest"; + +import { type IDashboardLayout, type IInsightWidget, idRef } from "@gooddata/sdk-model"; + +import { cloneWithSanitizedIds as sanitizeFromBackend } from "../fromBackend/IdSanitization.js"; +import { cloneWithSanitizedIds as sanitizeToBackend } from "../toBackend/IdSanitization.js"; + +import { convertLayout } from "./layoutConverter.js"; + +function layoutWithDrillUrl(url: string): IDashboardLayout { + const widget = { + type: "insight", + insight: idRef("insight1", "insight"), + ignoreDashboardFilters: [], + drills: [ + { + type: "drillToCustomUrl", + transition: "new-window", + origin: { type: "drillFromAttribute", attribute: idRef("label1", "displayForm") }, + target: { url }, + }, + ], + title: "widget", + description: "", + } as unknown as IInsightWidget; + + return { + type: "IDashboardLayout", + sections: [{ type: "IDashboardLayoutSection", items: [{ type: "IDashboardLayoutItem", widget }] }], + } as unknown as IDashboardLayout; +} + +function drillUrlOf(layout: unknown) { + return (layout as any).sections[0].items[0].widget.drills[0].target.url; +} + +/** + * The url stored on the backend is an array of string chunks and object references; the converters + * build it on save and flatten it back on load. These assert the reference each placeholder is + * stored as, and that loading the stored form reproduces the original url verbatim. + */ +describe("drill to custom url layout conversion", () => { + it("stores a display form placeholder as a label reference and loads it back unchanged", () => { + const url = "https://example.com/?q={attribute_title(campaign_channels.category)}&x=1"; + + const stored = sanitizeToBackend(convertLayout(false, layoutWithDrillUrl(url))); + + expect(drillUrlOf(stored)).toEqual([ + "https://example.com/?q=", + { identifier: { id: "campaign_channels.category", type: "label" } }, + "&x=1", + ]); + + const loaded = convertLayout(true, sanitizeFromBackend(stored) as IDashboardLayout); + expect(drillUrlOf(loaded)).toBe(url); + }); + + it("stores a computed attribute placeholder as a computed attribute reference and loads it back unchanged", () => { + const url = "https://example.com/?q={attribute_title(computed_attribute/ca1)}&x=1"; + + const stored = sanitizeToBackend(convertLayout(false, layoutWithDrillUrl(url))); + + expect(drillUrlOf(stored)).toEqual([ + "https://example.com/?q=", + { identifier: { id: "ca1", type: "computedAttribute" } }, + "&x=1", + ]); + + const loaded = convertLayout(true, sanitizeFromBackend(stored) as IDashboardLayout); + expect(drillUrlOf(loaded)).toBe(url); + }); + + it("leaves the filter selection placeholders as plain text on both sides", () => { + const url = + "https://example.com/?a={dash_attribute_filter_selection(label1)}" + + "&b={dash_attribute_filter_selection(computed_attribute/ca1)}" + + "&c={attribute_filter_selection(label1)}"; + + const stored = sanitizeToBackend(convertLayout(false, layoutWithDrillUrl(url))); + + expect(drillUrlOf(stored)).toEqual([url]); + + const loaded = convertLayout(true, sanitizeFromBackend(stored) as IDashboardLayout); + expect(drillUrlOf(loaded)).toBe(url); + }); +}); diff --git a/libs/sdk-backend-tiger/src/convertors/toBackend/afm/DimensionsConverter.ts b/libs/sdk-backend-tiger/src/convertors/toBackend/afm/DimensionsConverter.ts index f8466779278..37be149949d 100644 --- a/libs/sdk-backend-tiger/src/convertors/toBackend/afm/DimensionsConverter.ts +++ b/libs/sdk-backend-tiger/src/convertors/toBackend/afm/DimensionsConverter.ts @@ -117,7 +117,7 @@ function handleAttributeSort(sortItem: IAttributeSortItem, dims: Dimension[], so const dimIdx = dims.findIndex((dim) => dim.itemIdentifiers.includes(attributeIdentifier)); if (dimIdx < 0) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log( `attempting to sort by attribute with localId ${attributeIdentifier} but this attribute is not in any dimension.`, ); diff --git a/libs/sdk-backend-tiger/src/index.ts b/libs/sdk-backend-tiger/src/index.ts index 64a20d7a8f3..70e07e32439 100644 --- a/libs/sdk-backend-tiger/src/index.ts +++ b/libs/sdk-backend-tiger/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides the Analytical Backend implementation for GoodData Cloud and GoodData.CN. @@ -145,5 +145,5 @@ export { convertInsightToVisualizationContent } from "./convertors/toBackend/Ins /** * @deprecated This will be removed in the next major release, please use the named export "tigerFactory" instead */ -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default tigerFactory; diff --git a/libs/sdk-backend-tiger/tests/goodmock/snapshot-and-sanitize.mjs b/libs/sdk-backend-tiger/tests/goodmock/snapshot-and-sanitize.mjs index 3ad9f48560b..c6d2375f67f 100755 --- a/libs/sdk-backend-tiger/tests/goodmock/snapshot-and-sanitize.mjs +++ b/libs/sdk-backend-tiger/tests/goodmock/snapshot-and-sanitize.mjs @@ -78,5 +78,5 @@ for (const mapping of sanitized) { } writeFileSync(outputPath, JSON.stringify({ mappings: sanitized }, null, 4) + "\n"); -// eslint-disable-next-line no-console +// oxlint-disable-next-line eslint-js/no-console console.log(`Wrote ${sanitized.length} mappings to ${outputPath}`); diff --git a/libs/sdk-backend-tiger/vitest.config.ts b/libs/sdk-backend-tiger/vitest.config.ts index 41a17e74341..5ef19da7554 100644 --- a/libs/sdk-backend-tiger/vitest.config.ts +++ b/libs/sdk-backend-tiger/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-backend-tiger/vitest.integrated.config.ts b/libs/sdk-backend-tiger/vitest.integrated.config.ts index a74ab3bdb4d..a583324f83a 100644 --- a/libs/sdk-backend-tiger/vitest.integrated.config.ts +++ b/libs/sdk-backend-tiger/vitest.integrated.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default defineConfig({ test: { include: ["**/tests/integrated/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], diff --git a/libs/sdk-code-convertors/.dependency-cruiser.js b/libs/sdk-code-convertors/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-code-convertors/.dependency-cruiser.js +++ b/libs/sdk-code-convertors/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-code-convertors/.oxlintrc.json b/libs/sdk-code-convertors/.oxlintrc.json index 833c141e390..23a8eca69c1 100644 --- a/libs/sdk-code-convertors/.oxlintrc.json +++ b/libs/sdk-code-convertors/.oxlintrc.json @@ -10,6 +10,12 @@ "rules": { "@typescript-eslint/no-explicit-any": "warn" } + }, + { + "files": ["wasm/**/*"], + "globals": { + "Javy": "readonly" + } } ] } diff --git a/libs/sdk-code-convertors/package.json b/libs/sdk-code-convertors/package.json index a9833ef7728..9a6b7fdf589 100644 --- a/libs/sdk-code-convertors/package.json +++ b/libs/sdk-code-convertors/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-code-convertors", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData AAC declarative converters", "license": "MIT", "author": "GoodData", @@ -68,8 +68,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-code-convertors/python/pyproject.toml b/libs/sdk-code-convertors/python/pyproject.toml index 2d134b2b329..ee0614cea3b 100644 --- a/libs/sdk-code-convertors/python/pyproject.toml +++ b/libs/sdk-code-convertors/python/pyproject.toml @@ -2,7 +2,7 @@ [project] name = "gooddata-code-convertors" -version = "11.57.0a1" +version = "11.57.0a3" description = "GoodData AAC YAML / Declarative API code converters (WASM-powered)" readme = "README.md" license = { file = "LICENSE" } diff --git a/libs/sdk-code-convertors/scripts/check-python-types.mjs b/libs/sdk-code-convertors/scripts/check-python-types.mjs index 02b6c551a94..fdcb7fe091d 100644 --- a/libs/sdk-code-convertors/scripts/check-python-types.mjs +++ b/libs/sdk-code-convertors/scripts/check-python-types.mjs @@ -60,5 +60,5 @@ if (failed) { } const fileNames = GENERATED_FILES.map((path) => path.split("/").pop()).join(" and "); -// eslint-disable-next-line no-console -- CLI script with intentional progress logging +// oxlint-disable-next-line eslint-js/no-console -- CLI script with intentional progress logging console.log(`Python types check passed — ${fileNames} are in sync with metadata.json.`); diff --git a/libs/sdk-code-convertors/src/errorContext.test.ts b/libs/sdk-code-convertors/src/errorContext.test.ts index bc30d8adeaf..9b8c5aad194 100644 --- a/libs/sdk-code-convertors/src/errorContext.test.ts +++ b/libs/sdk-code-convertors/src/errorContext.test.ts @@ -30,23 +30,24 @@ describe("error context propagation", () => { }, }; + let error: ICoreError | undefined; try { declarativeDashboardToYaml([], dashboard); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual([ - "dashboard", - "my_dash", - "layout", - "sections", - "0", - "items", - "0", - "insight", - "visualisation", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual([ + "dashboard", + "my_dash", + "layout", + "sections", + "0", + "items", + "0", + "insight", + "visualisation", + ]); }); }); @@ -80,18 +81,14 @@ describe("error context propagation", () => { }, }; + let error: ICoreError | undefined; try { declarativeAttributeHierarchyToYaml(hierarchy); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual([ - "attribute_hierarchy", - "my_hierarchy", - "attributes", - "0", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual(["attribute_hierarchy", "my_hierarchy", "attributes", "0"]); }); }); }); diff --git a/libs/sdk-code-convertors/src/index.ts b/libs/sdk-code-convertors/src/index.ts index cc523e6c016..0a10d24c621 100644 --- a/libs/sdk-code-convertors/src/index.ts +++ b/libs/sdk-code-convertors/src/index.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files // AAC schema types (curated public surface — isolates api-extractor from generated internals) export type { diff --git a/libs/sdk-code-convertors/src/utils/configUtils.test.ts b/libs/sdk-code-convertors/src/utils/configUtils.test.ts index fd1daef0b19..74f22952a89 100644 --- a/libs/sdk-code-convertors/src/utils/configUtils.test.ts +++ b/libs/sdk-code-convertors/src/utils/configUtils.test.ts @@ -93,12 +93,14 @@ describe("loadColorDefinitions", () => { }); it("refuses a slot it does not name, reporting which", () => { + let error: ICoreError | undefined; try { loadColorDefinitions([{ id: "properties.color.other", color: guid("5") }]); - expect.fail("Should have thrown error"); } catch (err: unknown) { - expect((err as ICoreError).code).toBe(CoreErrorCode.ItemNotSupported); - expect((err as ICoreError).message).toContain("properties.color.other"); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.ItemNotSupported); + expect(error?.message).toContain("properties.color.other"); }); }); diff --git a/libs/sdk-code-convertors/src/utils/yamlUtils.test.ts b/libs/sdk-code-convertors/src/utils/yamlUtils.test.ts index eb58825ea7a..818e11e3997 100644 --- a/libs/sdk-code-convertors/src/utils/yamlUtils.test.ts +++ b/libs/sdk-code-convertors/src/utils/yamlUtils.test.ts @@ -13,31 +13,33 @@ describe("yamlUtils", () => { it("should throw error with context when reference type is not supported", () => { const context = { type: "test", path: ["root"] }; + let error: ICoreError | undefined; try { getIdentifier({ uri: "/foo" }, true, context); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.code).toBe(CoreErrorCode.ReferenceTypeNotSupported); - expect(error.context).toEqual(context); - expect(error.message).toContain("in: root"); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.ReferenceTypeNotSupported); + expect(error?.context).toEqual(context); + expect(error?.message).toContain("in: root"); }); }); describe("createFilterContextItemKeyName", () => { it("should throw error with context for unsupported item", () => { const context = { type: "test", path: ["dashboard"] }; + let error: ICoreError | undefined; try { // @ts-expect-error - passing invalid item createFilterContextItemKeyName({ unknown: "item" }, "date", context); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.code).toBe(CoreErrorCode.ItemNotSupported); - expect(error.context).toEqual(context); - expect(error.message).toContain("in: dashboard"); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.ItemNotSupported); + expect(error?.context).toEqual(context); + expect(error?.message).toContain("in: dashboard"); }); it("should propagate context to getIdentifier in date filter", () => { @@ -48,29 +50,31 @@ describe("yamlUtils", () => { granularity: "GDC.time.year", }, }; + let error: ICoreError | undefined; try { createFilterContextItemKeyName(item, "date", context); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual(["dashboard", "dateFilter", "dataSet"]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual(["dashboard", "dateFilter", "dataSet"]); }); }); describe("createFilterItemKeyName", () => { it("should throw error with context for unsupported item", () => { const context = { type: "test", path: ["filter"] }; + let error: ICoreError | undefined; try { // @ts-expect-error - passing invalid item createFilterItemKeyName({ unknown: "item" }, "date", context); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.code).toBe(CoreErrorCode.ItemNotSupported); - expect(error.context).toEqual(context); - expect(error.message).toContain("in: filter"); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.ItemNotSupported); + expect(error?.context).toEqual(context); + expect(error?.message).toContain("in: filter"); }); it("should propagate context to getIdentifier in absolute date filter", () => { @@ -80,13 +84,14 @@ describe("yamlUtils", () => { dataSet: { uri: "/foo" }, // invalid for getIdentifier }, }; + let error: ICoreError | undefined; try { createFilterItemKeyName(item, "date", context); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual(["filter", "absoluteDateFilter", "dataSet"]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual(["filter", "absoluteDateFilter", "dataSet"]); }); }); }); diff --git a/libs/sdk-code-convertors/src/visualisationConversion.test.ts b/libs/sdk-code-convertors/src/visualisationConversion.test.ts index 8e8716db0b1..eb2150e2f9e 100644 --- a/libs/sdk-code-convertors/src/visualisationConversion.test.ts +++ b/libs/sdk-code-convertors/src/visualisationConversion.test.ts @@ -569,15 +569,17 @@ describe("visualisation conversion", () => { metrics: [{ field: "m1" }], }); + let error: ICoreError | undefined; try { yamlVisualisationToDeclarative(emptyEntities, clashing); - expect.fail("Should have thrown error"); } catch (err: unknown) { - expect((err as ICoreError).code).toBe(CoreErrorCode.DuplicateFilterName); - expect((err as ICoreError).message).toContain("af"); - // The dashboard wording names a tab and filter groups, neither of which a query has. - expect((err as ICoreError).message).not.toContain("dashboard"); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.DuplicateFilterName); + expect(error?.message).toContain("af"); + // The dashboard wording names a tab and filter groups, neither of which a query has. + expect(error?.message).not.toContain("dashboard"); }); it("leaves a measure's own carried filter unnamed, which must never claim one", () => { @@ -662,13 +664,15 @@ describe("visualisation conversion", () => { }) as Visualisation; it("refuses one id claimed by two layers, which would name neither", () => { + let error: ICoreError | undefined; try { yamlVisualisationToDeclarative(emptyEntities, withLayerIds("dup", "dup")); - expect.fail("Should have thrown error"); } catch (err: unknown) { - expect((err as ICoreError).code).toBe(CoreErrorCode.DuplicateLayerIdentifier); - expect((err as ICoreError).message).toContain('"dup"'); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.DuplicateLayerIdentifier); + expect(error?.message).toContain('"dup"'); }); it("converts layers whose ids differ", () => { diff --git a/libs/sdk-code-convertors/src/visualisationErrorContext.test.ts b/libs/sdk-code-convertors/src/visualisationErrorContext.test.ts index 2730759e637..72211d55807 100644 --- a/libs/sdk-code-convertors/src/visualisationErrorContext.test.ts +++ b/libs/sdk-code-convertors/src/visualisationErrorContext.test.ts @@ -12,14 +12,15 @@ describe("declarativeVisualisationToYaml error context", () => { // content is empty which is invalid for isInsight }; + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], visualisation); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.type).toBe("visualisation"); - expect(error.context?.path).toEqual(["visualisation", "my_vis"]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.type).toBe("visualisation"); + expect(error?.context?.path).toEqual(["visualisation", "my_vis"]); }); it("should include full path for invalid attribute in bucket", () => { @@ -43,23 +44,24 @@ describe("declarativeVisualisationToYaml error context", () => { }, }; + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], visualisation); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual([ - "visualisation", - "my_vis", - "insight", - "buckets", - "0", - "items", - "0", - "attribute", - "displayForm", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual([ + "visualisation", + "my_vis", + "insight", + "buckets", + "0", + "items", + "0", + "attribute", + "displayForm", + ]); }); it("should include full path for invalid measure in bucket", () => { @@ -87,23 +89,24 @@ describe("declarativeVisualisationToYaml error context", () => { }, }; + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], visualisation); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual([ - "visualisation", - "my_vis", - "insight", - "buckets", - "0", - "items", - "0", - "measure", - "item", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual([ + "visualisation", + "my_vis", + "insight", + "buckets", + "0", + "items", + "0", + "measure", + "item", + ]); }); it("should include full path for unsupported bucket item type", () => { @@ -124,22 +127,23 @@ describe("declarativeVisualisationToYaml error context", () => { }, }; + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], visualisation); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - // Based on the code, it uses bucketItemErrorContext which has bi and ii - expect(error.context?.path).toEqual([ - "visualisation", - "my_vis", - "insight", - "buckets", - "0", - "items", - "0", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + // Based on the code, it uses bucketItemErrorContext which has bi and ii + expect(error?.context?.path).toEqual([ + "visualisation", + "my_vis", + "insight", + "buckets", + "0", + "items", + "0", + ]); }); it("should include full path for invalid filter", () => { @@ -158,22 +162,23 @@ describe("declarativeVisualisationToYaml error context", () => { }, }; + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], visualisation); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.context?.path).toEqual([ - "visualisation", - "my_vis", - "insight", - "filters", - "0", - "date", - "positiveAttributeFilter", - "displayForm", - ]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.context?.path).toEqual([ + "visualisation", + "my_vis", + "insight", + "filters", + "0", + "date", + "positiveAttributeFilter", + "displayForm", + ]); }); }); @@ -221,15 +226,16 @@ describe("declarativeVisualisationToYaml layer overrides", () => { { attributeFilterConfigs: { f1: { displayAsLabel: { identifier: "x", type: "displayForm" } } } }, ], ])("refuses a layer carrying its own %s, which the grammar cannot express", (field, override) => { + let error: ICoreError | undefined; try { declarativeVisualisationToYaml([], withLayer(override)); - expect.fail("Should have thrown error"); } catch (err: unknown) { - const error = err as ICoreError; - expect(error.code).toBe(CoreErrorCode.ItemNotSupported); - expect(error.message).toContain(`layer "layer1" own ${field}`); - expect(error.context?.path).toEqual(["visualisation", "my_map", "insight", "layers", "0", field]); + error = err as ICoreError; } + expect(error).toBeDefined(); + expect(error?.code).toBe(CoreErrorCode.ItemNotSupported); + expect(error?.message).toContain(`layer "layer1" own ${field}`); + expect(error?.context?.path).toEqual(["visualisation", "my_map", "insight", "layers", "0", field]); }); // Present-but-empty is the shape stored layers carry, and it loses nothing. diff --git a/libs/sdk-code-convertors/vitest.config.ts b/libs/sdk-code-convertors/vitest.config.ts index a4e9c324d3d..e5597c5dfbd 100644 --- a/libs/sdk-code-convertors/vitest.config.ts +++ b/libs/sdk-code-convertors/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-code-schemas/.dependency-cruiser.js b/libs/sdk-code-schemas/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-code-schemas/.dependency-cruiser.js +++ b/libs/sdk-code-schemas/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-code-schemas/eslint.config.ts b/libs/sdk-code-schemas/eslint.config.ts index a8db92cf5b1..c070b053311 100644 --- a/libs/sdk-code-schemas/eslint.config.ts +++ b/libs/sdk-code-schemas/eslint.config.ts @@ -1,5 +1,3 @@ // (C) 2026 GoodData Corporation -import config from "@gooddata/eslint-config/oxlint-esm-vitest"; - -export default [...config]; +export { default } from "@gooddata/eslint-config/oxlint-esm-vitest"; diff --git a/libs/sdk-code-schemas/package.json b/libs/sdk-code-schemas/package.json index 80147a8749d..015b34a8f9a 100644 --- a/libs/sdk-code-schemas/package.json +++ b/libs/sdk-code-schemas/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-code-schemas", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData AAC JSON Schema types and compiled schemas", "license": "MIT", "author": "GoodData", @@ -65,8 +65,9 @@ "lodash-es": "^4.17.23", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "readdirp": "4.1.2", "typescript": "7.0.2", "vitest": "4.1.8" diff --git a/libs/sdk-code-schemas/scripts/refresh-schema.mjs b/libs/sdk-code-schemas/scripts/refresh-schema.mjs index 2a64d2fc3a9..d9fd9af4e0f 100644 --- a/libs/sdk-code-schemas/scripts/refresh-schema.mjs +++ b/libs/sdk-code-schemas/scripts/refresh-schema.mjs @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* eslint-disable no-console -- CLI script with intentional progress logging */ +// oxlint-disable eslint-js/no-console -- CLI script with intentional progress logging // // Generates TypeScript types and compiled JSON schema from the AAC JSON Schema source files. diff --git a/libs/sdk-code-schemas/src/index.ts b/libs/sdk-code-schemas/src/index.ts index 0891ca5fcf5..3eb7df67e60 100644 --- a/libs/sdk-code-schemas/src/index.ts +++ b/libs/sdk-code-schemas/src/index.ts @@ -10,7 +10,8 @@ * @packageDocumentation */ -// oxlint-disable no-barrel-files/no-barrel-files +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax + export type * as v1 from "./v1/metadata.js"; export { metadata_v1 } from "./v1/schema.js"; diff --git a/libs/sdk-code-schemas/vitest.config.ts b/libs/sdk-code-schemas/vitest.config.ts index 5763e9a98d7..f51ea80caac 100644 --- a/libs/sdk-code-schemas/vitest.config.ts +++ b/libs/sdk-code-schemas/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-e2e-utils/package.json b/libs/sdk-e2e-utils/package.json index 10fe2afdbb3..120399dde3e 100644 --- a/libs/sdk-e2e-utils/package.json +++ b/libs/sdk-e2e-utils/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-e2e-utils", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData utility functions for Playwright E2E tests", "license": "MIT", "author": "GoodData", @@ -61,8 +61,9 @@ "jiti": "2.6.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2" } } diff --git a/libs/sdk-e2e-utils/src/goodmock.ts b/libs/sdk-e2e-utils/src/goodmock.ts index f660017f543..24e5149c0bd 100644 --- a/libs/sdk-e2e-utils/src/goodmock.ts +++ b/libs/sdk-e2e-utils/src/goodmock.ts @@ -33,7 +33,7 @@ export async function loadMappings(host: string, mappingFilePath: string): Promi body: JSON.stringify(json), }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Goodmock mappings loaded from ${mappingFilePath} (status: ${response.status})`); } @@ -47,7 +47,7 @@ export async function resetScenarios(host: string): Promise { const response = await fetch(`http://${host}/__admin/scenarios/reset`, { method: "POST", }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Goodmock scenarios reset (status: ${response.status})`); } @@ -59,7 +59,7 @@ export async function resetMappings(host: string): Promise { const response = await fetch(`http://${host}/__admin/reset`, { method: "POST", }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Goodmock mappings reset (status: ${response.status})`); } @@ -83,7 +83,7 @@ export async function mockLogRequests(host: string): Promise { }, }), }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Goodmock log requests mocked (status: ${response.status})`); } @@ -119,7 +119,7 @@ export async function startRecording(host: string, backendHost: string): Promise response: { proxyBaseUrl: backendHost }, }), }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Goodmock recording started, proxying to ${backendHost} (status: ${response.status})`); } @@ -219,7 +219,7 @@ function sanitizeWorkspaceId( const sanitizedDataString = dataString.split(sourceWorkspaceId).join(targetWorkspaceId); const sanitizedData = JSON.parse(sanitizedDataString) as { mappings: IGoodmockMapping[] }; - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Sanitized workspaceId in mappings (${sourceWorkspaceId} -> ${targetWorkspaceId})`); return sanitizedData.mappings; @@ -402,6 +402,6 @@ export async function snapshotAndSaveRecording( mkdirSync(dirname(mappingFilePath), { recursive: true }); writeFileSync(mappingFilePath, output); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Recording saved to ${mappingFilePath} (${sanitizedMappings.length} mappings)`); } diff --git a/libs/sdk-e2e-utils/src/index.ts b/libs/sdk-e2e-utils/src/index.ts index 171b83b1b10..b49415a1541 100644 --- a/libs/sdk-e2e-utils/src/index.ts +++ b/libs/sdk-e2e-utils/src/index.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { authHeader, authedRouteFetch, injectAuthHeader } from "./auth.js"; export { diff --git a/libs/sdk-embedding/.dependency-cruiser.js b/libs/sdk-embedding/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-embedding/.dependency-cruiser.js +++ b/libs/sdk-embedding/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-embedding/package.json b/libs/sdk-embedding/package.json index 18f764d087e..11bc76cbbc0 100644 --- a/libs/sdk-embedding/package.json +++ b/libs/sdk-embedding/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-embedding", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Embedding APIs", "license": "MIT", "author": "GoodData", @@ -67,8 +67,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-embedding/src/index.ts b/libs/sdk-embedding/src/index.ts index e0dc840f5a1..e0d1890d8ad 100644 --- a/libs/sdk-embedding/src/index.ts +++ b/libs/sdk-embedding/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package defines APIs for embedding and interfacing with the embedded GoodData applications - Analytical Designer and Dashboards/KPI Dashboards. diff --git a/libs/sdk-embedding/src/internal/index.ts b/libs/sdk-embedding/src/internal/index.ts index a037d34edec..7ff0eaafa9b 100644 --- a/libs/sdk-embedding/src/internal/index.ts +++ b/libs/sdk-embedding/src/internal/index.ts @@ -3,7 +3,7 @@ import { messagingUtils } from "./messagingUtils.js"; import { validators } from "./validators.js"; -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { messagingUtils, validators }; export { diff --git a/libs/sdk-embedding/vitest.config.ts b/libs/sdk-embedding/vitest.config.ts index 04ad74ff664..09371ab4118 100644 --- a/libs/sdk-embedding/vitest.config.ts +++ b/libs/sdk-embedding/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-model/.dependency-cruiser.js b/libs/sdk-model/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-model/.dependency-cruiser.js +++ b/libs/sdk-model/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-model/api/sdk-model.api.md b/libs/sdk-model/api/sdk-model.api.md index 5c081c79711..4cf02b7a7b1 100644 --- a/libs/sdk-model/api/sdk-model.api.md +++ b/libs/sdk-model/api/sdk-model.api.md @@ -3554,12 +3554,10 @@ export interface IInPlatformNotificationChannelMetadataObject extends INotificat export type IInPlatformNotificationChannelMetadataObjectDefinition = ToMdObjectDefinition; // @public -export type IInsight = IInsightDefinition & { +export type IInsight = IInsightDefinition & IInsightIdentity & { insight: IAuditable & { - identifier: string; - uri: string; - ref: ObjRef; isLocked?: boolean; + isDraft?: boolean; certification?: IObjectCertification; }; }; @@ -3582,6 +3580,20 @@ export type IInsightDefinition = { }; }; +// @public +export type IInsightDefinitionWithOptionalIdentity = IInsightDefinition & { + insight: Partial; +}; + +// @public +export type IInsightIdentity = { + insight: { + identifier: string; + uri: string; + ref: ObjRef; + }; +}; + // @alpha export interface IInsightLayerDefinition { attributeFilterConfigs?: IAttributeFilterConfigs; @@ -7659,7 +7671,7 @@ export type RemotePluggableApplicationsRegistry = IRemotePluggableApplicationsRe export type ReportBackground = IReportColorBackground | IReportImageBackground; // @alpha -export type ReportBuiltInVariable = "reportTitle" | "periodStart" | "periodEnd" | "workspaceName" | "generatedAt" | "pageNumber" | "totalPages" | "logo" | "logoInverse"; +export type ReportBuiltInVariable = "reportName" | "reportDescription" | "periodStart" | "periodEnd" | "reportDateRange" | "reportAttributeFilters" | "exportedAt" | "exportedBy" | "lastModifiedAt" | "lastModifiedBy" | "workspaceName" | "workspaceId" | "totalPages" | "currentPageNumber" | "logo" | "logoInverse"; // @alpha export const ReportBuiltInVariables: ReportBuiltInVariable[]; @@ -7706,6 +7718,9 @@ export const ReportsBrandKitImageVariablePrefix = "image_"; // @alpha export type ReportSlot = IReportVisualizationSlot | IReportTextSlot | IReportImageSlot; +// @alpha +export function reportTextPlaceholder(name: string): string; + // @alpha export type ReportTextSlotKind = "title" | "subtitle" | "sectionTitle" | "description" | "summary" | "body" | "custom"; diff --git a/libs/sdk-model/package.json b/libs/sdk-model/package.json index 417180b5709..c4a24dd261c 100644 --- a/libs/sdk-model/package.json +++ b/libs/sdk-model/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-model", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Model definitions used by UI components and Backend SPI", "license": "MIT", "author": "GoodData", @@ -71,8 +71,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/sdk-model/src/dashboard/drillUrl.ts b/libs/sdk-model/src/dashboard/drillUrl.ts index abd73b59d15..281ef4082db 100644 --- a/libs/sdk-model/src/dashboard/drillUrl.ts +++ b/libs/sdk-model/src/dashboard/drillUrl.ts @@ -1,7 +1,8 @@ // (C) 2022-2026 GoodData Corporation +import { type IAttributeDisplayFormMetadataObject } from "../ldm/metadata/attributeDisplayForm/index.js"; import { idRef } from "../objRef/factory.js"; -import { type IdentifierRef, isIdentifierRef } from "../objRef/index.js"; +import { type IdentifierRef, type ObjRef, isComputedAttributeRef, isIdentifierRef } from "../objRef/index.js"; /** * @internal @@ -34,12 +35,87 @@ const dashboardMeasureValueFilterMatchRegexp = /\{dash_mvf_condition\((.*?)\)\}/ const insightAttributeFilterMatchRegexp = /\{attribute_filter_selection\((.*?)\)\}/g; const insightMeasureValueFilterMatchRegexp = /\{mvf_condition\((.*?)\)\}/g; -const attributeIdentifierToPlaceholder = (ref: IdentifierRef) => `{attribute_title(${ref.identifier})}`; +/** + * A placeholder names its target by identifier alone, which used to be enough because every target + * was a display form. A computed attribute has no labels on the backend and must be referenced by + * its own object type, so its identifier carries this type prefix to keep the two apart - both when + * a placeholder is turned into a stored reference and when that reference is rendered back into the + * URL. A bare identifier keeps meaning a display form, so URLs stored before this existed parse + * unchanged; the prefix cannot collide with an identifier because "/" is not a legal identifier + * character. + */ +const COMPUTED_ATTRIBUTE_PREFIX = "computed_attribute/"; + +const isComputedAttributePlaceholderIdentifier = (identifier: string): boolean => + identifier.startsWith(COMPUTED_ATTRIBUTE_PREFIX); + +/** + * The identifier of the object a placeholder points at, without the type prefix. + */ +const placeholderIdentifier = (identifier: string): string => + isComputedAttributePlaceholderIdentifier(identifier) + ? identifier.slice(COMPUTED_ATTRIBUTE_PREFIX.length) + : identifier; + +const placeholderToRef = (identifier: string): IdentifierRef => + isComputedAttributePlaceholderIdentifier(identifier) + ? idRef(placeholderIdentifier(identifier), "computedAttribute") + : idRef(identifier, "displayForm"); + +/** + * The identifier text a placeholder uses to name the given object, exactly as it appears inside the + * placeholder in the URL - type-prefixed for a computed attribute, bare for a display form. + * + * @internal + */ +export const placeholderIdentifierText = (ref: ObjRef): string => { + const prefix = isComputedAttributeRef(ref) ? COMPUTED_ATTRIBUTE_PREFIX : ""; + // Tiger references objects by identifier only; the uri branch just keeps the function total. + const identifier = isIdentifierRef(ref) ? ref.identifier : ref.uri; + + return `${prefix}${identifier}`; +}; + +/** + * The reference a placeholder uses to name the given display form. + * + * The identifier always comes from `id` and only the kind of object is taken from the ref, because a + * display form can be described by a uri ref, which a placeholder has no way to express - placeholders + * name their target by identifier alone. + * + * @internal + */ +export const displayFormPlaceholderRef = (df: IAttributeDisplayFormMetadataObject): ObjRef => + isComputedAttributeRef(df.ref) ? idRef(df.id, "computedAttribute") : idRef(df.id, "displayForm"); + +/** + * Builds the `{attribute_title(...)}` placeholder text referencing the given object. + * + * @internal + */ +export const attributeIdentifierToPlaceholder = (ref: ObjRef): string => + `{attribute_title(${placeholderIdentifierText(ref)})}`; + +/** + * Builds the `{dash_attribute_filter_selection(...)}` placeholder text referencing the given object. + * + * @internal + */ +export const dashboardAttributeFilterToPlaceholder = (ref: ObjRef): string => + `{dash_attribute_filter_selection(${placeholderIdentifierText(ref)})}`; + +/** + * Builds the `{attribute_filter_selection(...)}` placeholder text referencing the given object. + * + * @internal + */ +export const insightAttributeFilterToPlaceholder = (ref: ObjRef): string => + `{attribute_filter_selection(${placeholderIdentifierText(ref)})}`; const matchToUrlPlaceholder = (match: any): IDrillToUrlPlaceholder => ({ placeholder: match[0], - identifier: match[1], - ref: idRef(match[1], "displayForm"), + identifier: placeholderIdentifier(match[1]), + ref: placeholderToRef(match[1]), toBeEncoded: match.index !== 0, }); diff --git a/libs/sdk-model/src/dashboard/drillUrls.test.ts b/libs/sdk-model/src/dashboard/drillUrls.test.ts index 67a27911ab7..e274163e0cd 100644 --- a/libs/sdk-model/src/dashboard/drillUrls.test.ts +++ b/libs/sdk-model/src/dashboard/drillUrls.test.ts @@ -2,10 +2,19 @@ import { describe, expect, it } from "vitest"; +import { type IAttributeDisplayFormMetadataObject } from "../ldm/metadata/attributeDisplayForm/index.js"; +import { idRef, uriRef } from "../objRef/factory.js"; + import { + attributeIdentifierToPlaceholder, + dashboardAttributeFilterToPlaceholder, + displayFormPlaceholderRef, getAttributeIdentifiersPlaceholdersFromUrl, + getDashboardAttributeFilterPlaceholdersFromUrl, getDashboardMeasureValueFilterPlaceholdersFromUrl, + getInsightAttributeFilterPlaceholdersFromUrl, getInsightMeasureValueFilterPlaceholdersFromUrl, + insightAttributeFilterToPlaceholder, joinDrillUrlParts, splitDrillUrlParts, } from "./drillUrl.js"; @@ -35,6 +44,104 @@ describe("drill url placeholders", () => { expect(joinDrillUrlParts(url)).toBe(url); }); + it("should reference a computed attribute by its own type, not as a display form", () => { + const url = "https://example.com/?q={attribute_title(computed_attribute/ca1)}"; + + expect(getAttributeIdentifiersPlaceholdersFromUrl(url)).toEqual([ + { + placeholder: "{attribute_title(computed_attribute/ca1)}", + identifier: "ca1", + ref: { + identifier: "ca1", + type: "computedAttribute", + }, + toBeEncoded: true, + }, + ]); + + const urlParts = splitDrillUrlParts(url); + expect(urlParts).toEqual(["https://example.com/?q=", idRef("ca1", "computedAttribute"), ""]); + expect(joinDrillUrlParts(urlParts)).toBe(url); + }); + + it("should build the placeholder text for a display form and for a computed attribute", () => { + expect(attributeIdentifierToPlaceholder(idRef("label1", "displayForm"))).toBe( + "{attribute_title(label1)}", + ); + expect(attributeIdentifierToPlaceholder(idRef("ca1", "computedAttribute"))).toBe( + "{attribute_title(computed_attribute/ca1)}", + ); + }); + + it("should reference a computed attribute from the filter selection placeholders", () => { + expect( + getDashboardAttributeFilterPlaceholdersFromUrl( + "https://example.com/?f={dash_attribute_filter_selection(computed_attribute/ca1)}", + ), + ).toEqual([ + { + placeholder: "{dash_attribute_filter_selection(computed_attribute/ca1)}", + identifier: "ca1", + ref: { identifier: "ca1", type: "computedAttribute" }, + toBeEncoded: true, + }, + ]); + + expect( + getInsightAttributeFilterPlaceholdersFromUrl( + "https://example.com/?f={attribute_filter_selection(computed_attribute/ca1)}", + ), + ).toEqual([ + { + placeholder: "{attribute_filter_selection(computed_attribute/ca1)}", + identifier: "ca1", + ref: { identifier: "ca1", type: "computedAttribute" }, + toBeEncoded: true, + }, + ]); + }); + + it("should keep the filter selection placeholders unprefixed for a display form", () => { + expect(dashboardAttributeFilterToPlaceholder(idRef("label1", "displayForm"))).toBe( + "{dash_attribute_filter_selection(label1)}", + ); + expect(dashboardAttributeFilterToPlaceholder(idRef("ca1", "computedAttribute"))).toBe( + "{dash_attribute_filter_selection(computed_attribute/ca1)}", + ); + expect(insightAttributeFilterToPlaceholder(idRef("label1", "displayForm"))).toBe( + "{attribute_filter_selection(label1)}", + ); + expect(insightAttributeFilterToPlaceholder(idRef("ca1", "computedAttribute"))).toBe( + "{attribute_filter_selection(computed_attribute/ca1)}", + ); + }); + + it("should name a display form by identifier even when its ref is a uri", () => { + // A placeholder can only name its target by identifier, so a uri-backed display form has to + // fall back to its id rather than leaking the uri into the URL. + const uriBackedDisplayForm = { + id: "label1", + ref: uriRef("/gdc/md/project/obj/123"), + } as IAttributeDisplayFormMetadataObject; + + expect(displayFormPlaceholderRef(uriBackedDisplayForm)).toEqual(idRef("label1", "displayForm")); + expect(attributeIdentifierToPlaceholder(displayFormPlaceholderRef(uriBackedDisplayForm))).toBe( + "{attribute_title(label1)}", + ); + }); + + it("should keep the computed attribute type when naming a display form", () => { + const computedAttribute = { + id: "ca1", + ref: idRef("ca1", "computedAttribute"), + } as IAttributeDisplayFormMetadataObject; + + expect(displayFormPlaceholderRef(computedAttribute)).toEqual(idRef("ca1", "computedAttribute")); + expect(attributeIdentifierToPlaceholder(displayFormPlaceholderRef(computedAttribute))).toBe( + "{attribute_title(computed_attribute/ca1)}", + ); + }); + it("should extract dashboard measure value filter placeholders", () => { const placeholders = getDashboardMeasureValueFilterPlaceholdersFromUrl( "https://example.com/?condition={dash_mvf_condition(metric.revenue)}", diff --git a/libs/sdk-model/src/index.ts b/libs/sdk-model/src/index.ts index 3fec43155b8..8406268c2f0 100644 --- a/libs/sdk-model/src/index.ts +++ b/libs/sdk-model/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides domain models for GoodData.UI. @@ -514,7 +514,9 @@ export { export { type IInsight, + type IInsightIdentity, type IInsightDefinition, + type IInsightDefinitionWithOptionalIdentity, type IVisualizationClass, type IVisualizationClassBody, type VisualizationProperties, @@ -1507,6 +1509,7 @@ export { type IReportVariableDefinition, ReportBuiltInVariables, getReportTextPlaceholders, + reportTextPlaceholder, resolveReportTextPlaceholders, } from "./reports/variables.js"; diff --git a/libs/sdk-model/src/insight/index.ts b/libs/sdk-model/src/insight/index.ts index 61bba91c6ff..4732d497f9f 100644 --- a/libs/sdk-model/src/insight/index.ts +++ b/libs/sdk-model/src/insight/index.ts @@ -54,8 +54,38 @@ import { type IUser } from "../user/index.js"; * * @public */ -export type IInsight = IInsightDefinition & { - insight: IAuditable & { +export type IInsight = IInsightDefinition & + IInsightIdentity & { + insight: IAuditable & { + /** + * Insight is locked for editing & deleting + */ + isLocked?: boolean; + + /** + * Insight is draft only, its not saved yet + */ + isDraft?: boolean; + + /** + * Certification metadata. + * @internal + */ + certification?: IObjectCertification; + }; + }; + +/** + * Identity of an Insight. Insight is typically created using Analytical Designer + * and can be embedded using UI SDK. + * + * @remarks + * Identity of an Insight contains all metadata needed to reference the Insight. + * + * @public + */ +export type IInsightIdentity = { + insight: { /** * Unique identifier of the Insight */ @@ -70,17 +100,6 @@ export type IInsight = IInsightDefinition & { * Object to use when referencing insight. */ ref: ObjRef; - - /** - * Insight is locked for editing & deleting - */ - isLocked?: boolean; - - /** - * Certification metadata. - * @internal - */ - certification?: IObjectCertification; }; }; @@ -204,6 +223,16 @@ export type IInsightDefinition = { }; }; +/** + * Insight definition specifies what and how should be visualized by an insight + * with optional identity. + * + * @public + */ +export type IInsightDefinitionWithOptionalIdentity = IInsightDefinition & { + insight: Partial; +}; + /** * Object defining the {@link IVisualizationClass} object body. * diff --git a/libs/sdk-model/src/internal.ts b/libs/sdk-model/src/internal.ts index 733c0b3f172..b6399fe7ad4 100644 --- a/libs/sdk-model/src/internal.ts +++ b/libs/sdk-model/src/internal.ts @@ -1,10 +1,15 @@ // (C) 2023-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { type IDrillToUrlPlaceholder, + attributeIdentifierToPlaceholder, + dashboardAttributeFilterToPlaceholder, + displayFormPlaceholderRef, + insightAttributeFilterToPlaceholder, joinDrillUrlParts, + placeholderIdentifierText, splitDrillUrlParts, getAttributeIdentifiersPlaceholdersFromUrl, getDashboardAttributeFilterPlaceholdersFromUrl, diff --git a/libs/sdk-model/src/ldm/metadata/attributeDisplayForm/index.test.ts b/libs/sdk-model/src/ldm/metadata/attributeDisplayForm/index.test.ts index 6c15b6e625b..0705ea3856b 100644 --- a/libs/sdk-model/src/ldm/metadata/attributeDisplayForm/index.test.ts +++ b/libs/sdk-model/src/ldm/metadata/attributeDisplayForm/index.test.ts @@ -11,7 +11,7 @@ import { attributeDisplayFormMetadataObjectTitle, } from "./index.js"; -/* oxlint-disable no-undef */ +// oxlint-disable no-undef describe("attribute display form", () => { const _attributeDisplayFormTitle = "Account"; diff --git a/libs/sdk-model/src/reports/brandKit.ts b/libs/sdk-model/src/reports/brandKit.ts index e4645b649d0..28fa5b714b7 100644 --- a/libs/sdk-model/src/reports/brandKit.ts +++ b/libs/sdk-model/src/reports/brandKit.ts @@ -10,7 +10,7 @@ import { type IRgbColorValue } from "../colors/index.js"; export interface IReportsBrandKitImage { /** * Identifier the image is referenced by from report content, as the - * `{{image_}}` variable. Must match /^[a-zA-Z0-9_]+$/. + * `{image_}` variable. Must match /^[a-zA-Z0-9_]+$/. */ id: string; @@ -130,18 +130,18 @@ export interface IReportsBrandKitTypography { */ export interface IReportsBrandKitAssets { /** - * Primary logo URL; resolves the `{{logo}}` report variable, taking precedence over + * Primary logo URL; resolves the `{logo}` report variable, taking precedence over * the workspace white-labeling logo. */ logo?: string; /** - * Logo variant for dark backgrounds; resolves the `{{logoInverse}}` report variable. + * Logo variant for dark backgrounds; resolves the `{logoInverse}` report variable. */ logoInverse?: string; /** - * Additional brand images, each resolving its `{{image_}}` report variable. + * Additional brand images, each resolving its `{image_}` report variable. */ images?: IReportsBrandKitImage[]; } diff --git a/libs/sdk-model/src/reports/builtinPageLayouts.ts b/libs/sdk-model/src/reports/builtinPageLayouts.ts index f3519103212..eb07e2763e7 100644 --- a/libs/sdk-model/src/reports/builtinPageLayouts.ts +++ b/libs/sdk-model/src/reports/builtinPageLayouts.ts @@ -32,7 +32,7 @@ const vizSlot = (localIdentifier: string): IReportVisualizationSlot => ({ const logoSlot = (localIdentifier: string): IReportImageSlot => ({ type: "image", localIdentifier, - source: { type: "url", url: "{{logo}}" }, + source: { type: "url", url: "{logo}" }, fit: "contain", altText: "Logo", style: { horizontalAlign: "start", verticalAlign: "end" }, @@ -64,7 +64,7 @@ const footerSlots = () => [ ...textSlot({ localIdentifier: "footerPageNumber", kind: "custom", - content: "{{pageNumber}} / {{totalPages}}", + content: "{currentPageNumber} / {totalPages}", }), style: { horizontalAlign: "end", verticalAlign: "end" }, } satisfies IReportTextSlot, @@ -164,11 +164,11 @@ export const BuiltInReportPageLayoutCover: IReportPageLayout = builtInPage({ format: "widescreen", layout: column([slot("coverTitle", 2), slot("coverSubtitle", 1), footerRow()]), slots: [ - textSlot({ localIdentifier: "coverTitle", kind: "title", content: "{{reportTitle}}" }), + textSlot({ localIdentifier: "coverTitle", kind: "title", content: "{reportName}" }), textSlot({ localIdentifier: "coverSubtitle", kind: "subtitle", - content: "{{periodStart}} – {{periodEnd}}", + content: "{periodStart} – {periodEnd}", }), ...footerSlots(), ], @@ -338,11 +338,11 @@ export const BuiltInReportPageLayoutPortraitCover: IReportPageLayout = builtInPa format: PORTRAIT_FORMAT, layout: column([slot("coverTitle", 2), slot("coverSubtitle", 1), footerRow()]), slots: [ - textSlot({ localIdentifier: "coverTitle", kind: "title", content: "{{reportTitle}}" }), + textSlot({ localIdentifier: "coverTitle", kind: "title", content: "{reportName}" }), textSlot({ localIdentifier: "coverSubtitle", kind: "subtitle", - content: "{{periodStart}} – {{periodEnd}}", + content: "{periodStart} – {periodEnd}", }), ...footerSlots(), ], diff --git a/libs/sdk-model/src/reports/pageLayout.ts b/libs/sdk-model/src/reports/pageLayout.ts index db9e3c6b40c..c2f6ff455e9 100644 --- a/libs/sdk-model/src/reports/pageLayout.ts +++ b/libs/sdk-model/src/reports/pageLayout.ts @@ -19,8 +19,8 @@ import { type IReportBoxStyle, isReportImageBackground } from "./styling.js"; * embedded in template/report content ({@link IReportContentPage}). * * There is no header/footer chrome: page title, description, footer, page numbers and - * logos are ordinary slots in the layout tree (text slots with `{{pageNumber}}`/`{{totalPages}}`, - * an image slot with `{{logo}}`). Every slot fills its layout area completely. + * logos are ordinary slots in the layout tree (text slots with `{currentPageNumber}`/`{totalPages}`, + * an image slot with `{logo}`). Every slot fills its layout area completely. * * @alpha */ diff --git a/libs/sdk-model/src/reports/report.ts b/libs/sdk-model/src/reports/report.ts index a3f40266e8a..02dd535d52e 100644 --- a/libs/sdk-model/src/reports/report.ts +++ b/libs/sdk-model/src/reports/report.ts @@ -63,7 +63,7 @@ export interface IReportBase { * @remarks * At execution time the period materializes as an absolute date filter on each * visualization slot's dateDataSet (lowest precedence, per-slot opt-out via - * ignoreReportPeriod). Also available as `{{periodStart}}` in text. + * ignoreReportPeriod). Also available as `{periodStart}` in text. */ periodStart: ReportDateString; diff --git a/libs/sdk-model/src/reports/reports.test.ts b/libs/sdk-model/src/reports/reports.test.ts index e03cf5795ef..77b1ae7fe58 100644 --- a/libs/sdk-model/src/reports/reports.test.ts +++ b/libs/sdk-model/src/reports/reports.test.ts @@ -33,7 +33,11 @@ import { } from "./pageLayout.js"; import { isReport, isReportDefinition, isReportTemplate, isReportTemplateDefinition } from "./report.js"; import { isReportImageSlot, isReportSlot, isReportTextSlot, isReportVisualizationSlot } from "./slot.js"; -import { getReportTextPlaceholders, resolveReportTextPlaceholders } from "./variables.js"; +import { + getReportTextPlaceholders, + reportTextPlaceholder, + resolveReportTextPlaceholders, +} from "./variables.js"; const body: IReportPageBody = { kind: "content", @@ -53,22 +57,36 @@ const body: IReportPageBody = { describe("placeholder helpers", () => { it("collects distinct placeholders in order of first occurrence", () => { - expect(getReportTextPlaceholders("{{periodStart}} to {{periodEnd}} ({{periodStart}})")).toEqual([ + expect(getReportTextPlaceholders("{periodStart} to {periodEnd} ({periodStart})")).toEqual([ "periodStart", "periodEnd", ]); }); it("ignores malformed markers", () => { - expect(getReportTextPlaceholders("{{1bad}} {{ spaced }} {{}} {{good_1}}")).toEqual(["good_1"]); + expect(getReportTextPlaceholders("{1bad} { spaced } {} {good_1}")).toEqual(["good_1"]); + }); + + it("resolves a marker inside braces, leaving the braces in place", () => { + expect(getReportTextPlaceholders("{{periodStart}}")).toEqual(["periodStart"]); + expect(resolveReportTextPlaceholders("{{a}}", { a: "1" })).toBe("{1}"); + }); + + it("reads markers written back to back", () => { + expect(getReportTextPlaceholders("{a}{b}")).toEqual(["a", "b"]); + expect(resolveReportTextPlaceholders("{a}{b}", { a: "1", b: "2" })).toBe("12"); + }); + + it("builds a marker the collector reads back", () => { + expect(getReportTextPlaceholders(reportTextPlaceholder("periodStart"))).toEqual(["periodStart"]); }); it("resolves known values and keeps unknown markers", () => { - expect(resolveReportTextPlaceholders("{{a}} and {{b}}", { a: "1" })).toBe("1 and {{b}}"); + expect(resolveReportTextPlaceholders("{a} and {b}", { a: "1" })).toBe("1 and {b}"); }); it("does not expand values recursively", () => { - expect(resolveReportTextPlaceholders("{{a}}", { a: "{{b}}", b: "x" })).toBe("{{b}}"); + expect(resolveReportTextPlaceholders("{a}", { a: "{b}", b: "x" })).toBe("{b}"); }); }); diff --git a/libs/sdk-model/src/reports/slot.ts b/libs/sdk-model/src/reports/slot.ts index f7111391e8f..9f3527069c5 100644 --- a/libs/sdk-model/src/reports/slot.ts +++ b/libs/sdk-model/src/reports/slot.ts @@ -64,7 +64,7 @@ export interface IReportVisualizationSlot extends IReportSlotBase { insight?: ObjRef; /** - * Optional title rendered above the visualization. Supports `{{variables}}`. + * Optional title rendered above the visualization. Supports `{variables}`. */ title?: string; @@ -124,7 +124,7 @@ export interface IReportStaticTextSource { type: "static"; /** - * Markdown content with `{{variable}}` placeholders. Stored raw — placeholders are + * Markdown content with `{variable}` placeholders. Stored raw — placeholders are * resolved only at render/export time. */ content: string; @@ -139,7 +139,7 @@ export interface IReportAiTextSource { type: "ai"; /** - * Instruction for the generator. Supports `{{variables}}`. + * Instruction for the generator. Supports `{variables}`. */ prompt: string; @@ -193,7 +193,7 @@ export type ReportImageSource = type: "url"; /** - * Image URL. Supports `{{variables}}` — a logo slot uses `{{logo}}`. + * Image URL. Supports `{variables}` — a logo slot uses `{logo}`. */ url: string; } diff --git a/libs/sdk-model/src/reports/variables.ts b/libs/sdk-model/src/reports/variables.ts index 203c4f98c07..eb9de4922f7 100644 --- a/libs/sdk-model/src/reports/variables.ts +++ b/libs/sdk-model/src/reports/variables.ts @@ -11,19 +11,26 @@ export type ReportDateString = string; * Variables resolved by the rendering runtime; they are never declared or stored. * * @remarks - * Referenced from report text as `{{name}}`. Custom variable names must not collide with these; + * Referenced from report text as `{name}`. Custom variable names must not collide with these; * on collision the built-in wins. * * @alpha */ export type ReportBuiltInVariable = - | "reportTitle" + | "reportName" + | "reportDescription" | "periodStart" | "periodEnd" + | "reportDateRange" + | "reportAttributeFilters" + | "exportedAt" + | "exportedBy" + | "lastModifiedAt" + | "lastModifiedBy" | "workspaceName" - | "generatedAt" - | "pageNumber" + | "workspaceId" | "totalPages" + | "currentPageNumber" | "logo" | "logoInverse"; @@ -33,13 +40,20 @@ export type ReportBuiltInVariable = * @alpha */ export const ReportBuiltInVariables: ReportBuiltInVariable[] = [ - "reportTitle", + "reportName", + "reportDescription", "periodStart", "periodEnd", + "reportDateRange", + "reportAttributeFilters", + "exportedAt", + "exportedBy", + "lastModifiedAt", + "lastModifiedBy", "workspaceName", - "generatedAt", - "pageNumber", + "workspaceId", "totalPages", + "currentPageNumber", "logo", "logoInverse", ]; @@ -48,14 +62,14 @@ export const ReportBuiltInVariables: ReportBuiltInVariable[] = [ * A custom variable declared by report content and given a value by a report. * * @remarks - * Referenced from text as `{{name}}`. Values come from {@link IReportBase.variableValues}, + * Referenced from text as `{name}`. Values come from {@link IReportBase.variableValues}, * falling back to {@link IReportVariableDefinition.defaultValue}. * * @alpha */ export interface IReportVariableDefinition { /** - * Variable name as used inside the `{{...}}` marker. Must match /^[a-zA-Z][a-zA-Z0-9_]*$/. + * Variable name as used inside the `{...}` marker. Must match /^[a-zA-Z][a-zA-Z0-9_]*$/. * Built-in names ({@link ReportBuiltInVariable}) are reserved. */ name: string; @@ -74,10 +88,19 @@ export interface IReportVariableDefinition { defaultValue?: string; } -const placeholderRegex = /\{\{([a-zA-Z][a-zA-Z0-9_]*)\}\}/g; +const placeholderRegex = /\{([a-zA-Z][a-zA-Z0-9_]*)\}/g; /** - * Collects the distinct `{{variable}}` names referenced in the text, in order of first occurrence. + * The marker referencing the named variable from report text. + * + * @alpha + */ +export function reportTextPlaceholder(name: string): string { + return `{${name}}`; +} + +/** + * Collects the distinct `{variable}` names referenced in the text, in order of first occurrence. * * @alpha */ @@ -93,9 +116,13 @@ export function getReportTextPlaceholders(text: string): string[] { } /** - * Replaces `{{variable}}` markers with values. Markers with no value are left as-is. + * Replaces `{variable}` markers with values. Markers with no value are left as-is. * Values are inserted verbatim — there is no recursive expansion. * + * @remarks + * A marker resolves wherever it appears, braces around it included, so there is no escape for a + * literal `{name}` in report text. + * * @alpha */ export function resolveReportTextPlaceholders(text: string, values: Record): string { diff --git a/libs/sdk-model/vitest.config.ts b/libs/sdk-model/vitest.config.ts index 04ad74ff664..09371ab4118 100644 --- a/libs/sdk-model/vitest.config.ts +++ b/libs/sdk-model/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-pluggable-application-model/.dependency-cruiser.js b/libs/sdk-pluggable-application-model/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-pluggable-application-model/.dependency-cruiser.js +++ b/libs/sdk-pluggable-application-model/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-pluggable-application-model/api/sdk-pluggable-application-model.api.md b/libs/sdk-pluggable-application-model/api/sdk-pluggable-application-model.api.md index 3f271de8f8c..17bd8b7eb9e 100644 --- a/libs/sdk-pluggable-application-model/api/sdk-pluggable-application-model.api.md +++ b/libs/sdk-pluggable-application-model/api/sdk-pluggable-application-model.api.md @@ -157,6 +157,7 @@ export interface IHostUiMountOptions { navigate: (url: string) => void; onAskAiAssistant?: (question: string, agentId?: string, userContext?: IGenAIUserContext, appendToChat?: boolean, replaceUserContext?: boolean) => void; onChatToggleRequested?: () => void; + onError?: (error: string, context: string) => void; pathname: string; replace: (url: string) => void; resolvedApplications: PluggableApplicationRegistryItem[]; diff --git a/libs/sdk-pluggable-application-model/package.json b/libs/sdk-pluggable-application-model/package.json index 551eefbe068..e313f82e3b5 100644 --- a/libs/sdk-pluggable-application-model/package.json +++ b/libs/sdk-pluggable-application-model/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-pluggable-application-model", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK model contracts for pluggable applications", "license": "MIT", "author": "GoodData Corporation", @@ -59,8 +59,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2" } } diff --git a/libs/sdk-pluggable-application-model/src/hostUi.ts b/libs/sdk-pluggable-application-model/src/hostUi.ts index f389ccdfe9d..7a88e9e21f8 100644 --- a/libs/sdk-pluggable-application-model/src/hostUi.ts +++ b/libs/sdk-pluggable-application-model/src/hostUi.ts @@ -79,6 +79,16 @@ export interface IHostUiMountOptions { appendToChat?: boolean, replaceUserContext?: boolean, ) => void; + + /** + * Reports an error caught inside the host UI module's own React tree. + * + * @remarks + * The host UI renders in a React root separate from the host application's, so the + * host's own error boundary cannot observe failures here. Implementations should + * route caught errors through this callback to keep them visible in host telemetry. + */ + onError?: (error: string, context: string) => void; } /** diff --git a/libs/sdk-pluggable-application-model/src/index.ts b/libs/sdk-pluggable-application-model/src/index.ts index 9af8087766f..67bc6546f01 100644 --- a/libs/sdk-pluggable-application-model/src/index.ts +++ b/libs/sdk-pluggable-application-model/src/index.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * Model contracts for pluggable application integration. diff --git a/libs/sdk-ui-all/.dependency-cruiser.js b/libs/sdk-ui-all/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-all/.dependency-cruiser.js +++ b/libs/sdk-ui-all/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-all/package.json b/libs/sdk-ui-all/package.json index b6bcb95072f..14b7b1c3fe3 100644 --- a/libs/sdk-ui-all/package.json +++ b/libs/sdk-ui-all/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-all", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - All-In-One", "license": "LicenseRef-LICENSE", "author": "GoodData", @@ -66,8 +66,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2" } } diff --git a/libs/sdk-ui-all/src/index.ts b/libs/sdk-ui-all/src/index.ts index aab043854cd..a24e6b20f7e 100644 --- a/libs/sdk-ui-all/src/index.ts +++ b/libs/sdk-ui-all/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This is an all-in-one package that has all GoodData.UI packages as dependencies and re-exports their public API. @@ -12,7 +12,7 @@ * @packageDocumentation */ -/* eslint-disable no-duplicate-imports,no-restricted-syntax */ +// oxlint-disable eslint-js/no-duplicate-imports, eslint-js/no-restricted-syntax export * from "@gooddata/sdk-model"; export type { IExportResult as IExportResultModel } from "@gooddata/sdk-model"; diff --git a/libs/sdk-ui-application-header/package.json b/libs/sdk-ui-application-header/package.json index b7d1066102a..6d7dc3b3d6d 100644 --- a/libs/sdk-ui-application-header/package.json +++ b/libs/sdk-ui-application-header/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-application-header", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK application header components — workspace picker, notifications dropdown", "license": "MIT", "author": "GoodData Corporation", @@ -78,8 +78,9 @@ "jiti": "2.6.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "react-intl": "10.1.25", diff --git a/libs/sdk-ui-application-header/src/index.ts b/libs/sdk-ui-application-header/src/index.ts index f54d3abf733..9ad6e95ef95 100644 --- a/libs/sdk-ui-application-header/src/index.ts +++ b/libs/sdk-ui-application-header/src/index.ts @@ -1,6 +1,6 @@ // (C) 2022-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { AppHeaderNotifications } from "./AppHeaderNotifications/AppHeaderNotifications.js"; export { WorkspacePicker, type IWorkspacePickerProps } from "./WorkspacePicker/WorkspacePicker.js"; diff --git a/libs/sdk-ui-application-header/typings.d.ts b/libs/sdk-ui-application-header/typings.d.ts index e91815950af..cc84f2143ae 100644 --- a/libs/sdk-ui-application-header/typings.d.ts +++ b/libs/sdk-ui-application-header/typings.d.ts @@ -1,6 +1,6 @@ // (C) 2022-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports // eslint-disable-next-line @typescript-eslint/naming-convention interface Window { diff --git a/libs/sdk-ui-application-header/vitest.config.ts b/libs/sdk-ui-application-header/vitest.config.ts index d3caf083fd5..38ef4696597 100644 --- a/libs/sdk-ui-application-header/vitest.config.ts +++ b/libs/sdk-ui-application-header/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-catalog/.dependency-cruiser.js b/libs/sdk-ui-catalog/.dependency-cruiser.js index 2d46387557b..f5bb76853af 100644 --- a/libs/sdk-ui-catalog/.dependency-cruiser.js +++ b/libs/sdk-ui-catalog/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-catalog/.oxlintrc.json b/libs/sdk-ui-catalog/.oxlintrc.json index e231486cf68..abca5634928 100644 --- a/libs/sdk-ui-catalog/.oxlintrc.json +++ b/libs/sdk-ui-catalog/.oxlintrc.json @@ -3,5 +3,15 @@ "extends": ["node_modules/@gooddata/oxlint-config/dist/esm-react-vitest.json"], "options": { "typeAware": true - } + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + "rules": { + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/restrict-template-expressions": "warn" + } + } + ] } diff --git a/libs/sdk-ui-catalog/eslint.config.ts b/libs/sdk-ui-catalog/eslint.config.ts index c19ceedce04..47cfed3232f 100644 --- a/libs/sdk-ui-catalog/eslint.config.ts +++ b/libs/sdk-ui-catalog/eslint.config.ts @@ -1,15 +1,3 @@ // (C) 2020-2026 GoodData Corporation -import config from "@gooddata/eslint-config/oxlint-esm-react-vitest"; - -export default [ - ...config, - { - files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], - rules: { - "@typescript-eslint/no-unsafe-member-access": "warn", - "@typescript-eslint/no-unsafe-assignment": "warn", - "@typescript-eslint/restrict-template-expressions": "warn", - }, - }, -]; +export { default } from "@gooddata/eslint-config/oxlint-esm-react-vitest"; diff --git a/libs/sdk-ui-catalog/package.json b/libs/sdk-ui-catalog/package.json index 3fcdbf02903..e7bfd589d6b 100644 --- a/libs/sdk-ui-catalog/package.json +++ b/libs/sdk-ui-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-catalog", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - Analytics Catalog", "license": "MIT", "author": "GoodData Corporation", @@ -105,12 +105,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-catalog/src/catalogDetail/CatalogDetailTabMetadata.tsx b/libs/sdk-ui-catalog/src/catalogDetail/CatalogDetailTabMetadata.tsx index 42f6540452e..7893826d468 100644 --- a/libs/sdk-ui-catalog/src/catalogDetail/CatalogDetailTabMetadata.tsx +++ b/libs/sdk-ui-catalog/src/catalogDetail/CatalogDetailTabMetadata.tsx @@ -125,7 +125,6 @@ export function CatalogDetailTabMetadata({ arrowPlacement="left" optimalPlacement offset={10} - width={280} triggerBy={["hover", "click"]} /> @@ -162,7 +161,6 @@ export function CatalogDetailTabMetadata({ arrowPlacement="left" optimalPlacement offset={10} - width={280} triggerBy={["hover", "click"]} /> diff --git a/libs/sdk-ui-catalog/src/catalogItem/CatalogItemLock.tsx b/libs/sdk-ui-catalog/src/catalogItem/CatalogItemLock.tsx index 725d545af40..c2000f7c3a9 100644 --- a/libs/sdk-ui-catalog/src/catalogItem/CatalogItemLock.tsx +++ b/libs/sdk-ui-catalog/src/catalogItem/CatalogItemLock.tsx @@ -18,7 +18,6 @@ export function CatalogItemLock({ intl }: Props) { triggerBy={["hover"]} anchor={} content={intl.formatMessage({ id: "analyticsCatalog.catalogItem.lock" })} - width={255} /> ); } diff --git a/libs/sdk-ui-catalog/src/catalogItem/CatalogItemVisibilityIcon.tsx b/libs/sdk-ui-catalog/src/catalogItem/CatalogItemVisibilityIcon.tsx index b8dc0f4d98a..4211978b2e4 100644 --- a/libs/sdk-ui-catalog/src/catalogItem/CatalogItemVisibilityIcon.tsx +++ b/libs/sdk-ui-catalog/src/catalogItem/CatalogItemVisibilityIcon.tsx @@ -27,7 +27,6 @@ export function CatalogItemVisibilityIcon({ intl, item, ...htmlProps }: Props) { triggerBy={["hover", "focus"]} anchor={} content={intl.formatMessage({ id: "analyticsCatalog.column.isHidden.icon.tooltip" })} - width={255} /> ); diff --git a/libs/sdk-ui-catalog/src/header/Header.tsx b/libs/sdk-ui-catalog/src/header/Header.tsx index ad3f49c567e..02224baa146 100644 --- a/libs/sdk-ui-catalog/src/header/Header.tsx +++ b/libs/sdk-ui-catalog/src/header/Header.tsx @@ -32,7 +32,6 @@ export function Header({ searchNode, createNode }: Props) { arrowPlacement="left" optimalPlacement offset={10} - width={300} triggerBy={["hover", "click", "focus"]} />
diff --git a/libs/sdk-ui-catalog/src/index.ts b/libs/sdk-ui-catalog/src/index.ts index 0e993476586..a2fccf244bc 100644 --- a/libs/sdk-ui-catalog/src/index.ts +++ b/libs/sdk-ui-catalog/src/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /* * Analytics Catalog public API. diff --git a/libs/sdk-ui-catalog/src/localization/bundles/ru-RU.json b/libs/sdk-ui-catalog/src/localization/bundles/ru-RU.json index bc410011f62..ab5e3a24016 100644 --- a/libs/sdk-ui-catalog/src/localization/bundles/ru-RU.json +++ b/libs/sdk-ui-catalog/src/localization/bundles/ru-RU.json @@ -172,7 +172,7 @@ "analyticsCatalog.parameter.dialog.edit.title": "Изменить параметр", "analyticsCatalog.parameter.dialog.sectionHeader": "Определение параметра", "analyticsCatalog.parameter.dialog.sectionHeader.tooltip": "Создайте инструкцию YAML, которая определяет параметр.", - "analyticsCatalog.parameter.dialog.edit.duplicate": "Сохранить как новую", + "analyticsCatalog.parameter.dialog.edit.duplicate": "Сохранить как новый", "analyticsCatalog.parameter.dialog.edit.idImmutable": "Идентификатор параметра нельзя изменить при сохранении существующего параметра. Вместо этого используйте \"Сохранить как новый\".", "analyticsCatalog.parameter.dialog.help": "Как создать параметр?", "analyticsCatalog.parameter.dialog.submit.error": "Не удалось сохранить параметр.", @@ -201,7 +201,7 @@ "analyticsCatalog.parameter.delete.error": "Не удалось удалить параметр.", "analyticsCatalog.computedAttribute.dialog.create.title": "Создать вычисляемый атрибут", "analyticsCatalog.computedAttribute.dialog.edit.title": "Изменить вычисляемый атрибут", - "analyticsCatalog.computedAttribute.dialog.edit.duplicate": "Сохранить как новую", + "analyticsCatalog.computedAttribute.dialog.edit.duplicate": "Сохранить как новый", "analyticsCatalog.computedAttribute.dialog.edit.idImmutable": "Идентификатор вычисляемого атрибута нельзя изменить при сохранении существующего вычисляемого атрибута. Вместо этого используйте \"Сохранить как новую\".", "analyticsCatalog.computedAttribute.dialog.sectionHeader": "Определение вычисляемого атрибута", "analyticsCatalog.computedAttribute.dialog.sectionHeader.tooltip": "Напишите выражение MAQL, которое присваивает каждой строке значение, используя ветви CASE WHEN для метрики или факта.", diff --git a/libs/sdk-ui-catalog/src/localization/bundles/zh-HK.json b/libs/sdk-ui-catalog/src/localization/bundles/zh-HK.json index d5a837db6c8..c6c488c2320 100644 --- a/libs/sdk-ui-catalog/src/localization/bundles/zh-HK.json +++ b/libs/sdk-ui-catalog/src/localization/bundles/zh-HK.json @@ -12,7 +12,7 @@ "analyticsCatalog.objectType.dateDataset.button.ariaLabel": "日期數據集:{count}", "analyticsCatalog.objectType.dashboard.name": "儀表板", "analyticsCatalog.objectType.visualization.name": "可視化", - "analyticsCatalog.objectType.metric.name": "計量", + "analyticsCatalog.objectType.metric.name": "指標", "analyticsCatalog.objectType.attribute.name": "屬性", "analyticsCatalog.objectType.fact.name": "事實", "analyticsCatalog.objectType.dateDataset.name": "日期數據集", @@ -36,7 +36,7 @@ "analyticsCatalog.visualizationType.bullet.tooltip": "子彈圖", "analyticsCatalog.visualizationType.waterfall.tooltip": "瀑布圖", "analyticsCatalog.visualizationType.dependencywheel.tooltip": "依賴關係輪圖", - "analyticsCatalog.visualizationType.sankey.tooltip": "桑基图", + "analyticsCatalog.visualizationType.sankey.tooltip": "桑基圖", "analyticsCatalog.visualizationType.pushpin.tooltip": "大頭針", "analyticsCatalog.visualizationType.repeater.tooltip": "重複:", "analyticsCatalog.filter.title": "篩選器", @@ -81,7 +81,7 @@ "analyticsCatalog.certification.field.message.placeholder": "新增認證訊息", "analyticsCatalog.column.isHidden.field.tooltip": "在所有使用者的 Smart Search 和 AI Assistant 結果中包含。", "analyticsCatalog.column.title.isHiddenFromKda": "用於主要驅動因素分析", - "analyticsCatalog.column.isHiddenFromKda.field.tooltip": "分析哪些因素推動所選指標的變化。系統會按影響程度識別並排序主要驅動因素,協助你在無需手動操作的情況下理解績效變化。", + "analyticsCatalog.column.isHiddenFromKda.field.tooltip": "分析哪些因素推動所選指標的變化。系統會按影響程度識別並排序主要驅動因素,協助您在無需手動操作的情況下理解績效變化。", "analyticsCatalog.tags.manager.label.more": "更多標籤", "analyticsCatalog.tags.manager.label.noTags": "沒有標籤", "analyticsCatalog.tags.manager.label.close": "關閉", @@ -91,7 +91,7 @@ "analyticsCatalog.tags.manager.label.saveLabel": "儲存", "analyticsCatalog.tags.manager.label.removeLabel": "刪除", "analyticsCatalog.error.unknown.message": "未知錯誤", - "analyticsCatalog.error.unknown.description": "我哋不太肯定出咗乜嘢問題。 請稍後再試。", + "analyticsCatalog.error.unknown.description": "我們不太確定發生了什麼問題。 請稍後再試。", "analyticsCatalog.error.notFound.title": "我們找不到此項目", "analyticsCatalog.error.notFound.description": "它可能已被刪除,或者您沒有存取權限。", "analyticsCatalog.error.unauthorized.message": "未經授權", @@ -121,9 +121,9 @@ "analyticsCatalog.catalogItem.actionsMenu.ariaLabel": "{name} 的操作", "analyticsCatalog.tooltip.content": "集中式目錄涵蓋儀表板、指標、可視化、事實和屬性。在單一位置探索、管理和更新所有對象,以支援一致的管理、更快速的洞察和更佳的協作。", "analyticsCatalog.search.label": "按標題或描述搜尋…", - "analyticsCatalog.empty.search.title": "未搵到匹配項", + "analyticsCatalog.empty.search.title": "找不到相符項目", "analyticsCatalog.empty.search.description": "檢查拼寫或調整過濾器。", - "analyticsCatalog.empty.filters.title": "未搵到匹配項", + "analyticsCatalog.empty.filters.title": "找不到相符項目", "analyticsCatalog.empty.filters.description": "在所選篩選器下未找到相符的項目。", "analyticsCatalog.quality.tooltip.title": "驗證 {count, plural, other {發現}}:", "analyticsCatalog.quality.tooltip.identicalTitle": "有 {count, plural, other {# 個欄位}}使用相同標題", @@ -228,9 +228,9 @@ "analyticsCatalog.computedAttribute.dialog.delete.submit": "刪除", "analyticsCatalog.computedAttribute.delete.success": "已刪除計算屬性。", "analyticsCatalog.computedAttribute.delete.error": "無法刪除計算屬性。", - "analyticsCatalog.metric.dialog.create.title": "創建計量", + "analyticsCatalog.metric.dialog.create.title": "創建指標", "analyticsCatalog.metric.dialog.edit.title": "編輯指標", - "analyticsCatalog.metric.dialog.sectionHeader": "計量定義", + "analyticsCatalog.metric.dialog.sectionHeader": "指標定義", "analyticsCatalog.metric.dialog.sectionHeader.tooltip": "在 YAML 中定義指標,包括其 MAQL 表達式和格式。", "analyticsCatalog.metric.actions.openInEditor": "在指標編輯器中開啟", "analyticsCatalog.metric.dialog.edit.duplicate": "重複", diff --git a/libs/sdk-ui-catalog/vitest.config.ts b/libs/sdk-ui-catalog/vitest.config.ts index 4519132dd76..af977b0c330 100644 --- a/libs/sdk-ui-catalog/vitest.config.ts +++ b/libs/sdk-ui-catalog/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-charts/.dependency-cruiser.js b/libs/sdk-ui-charts/.dependency-cruiser.js index 2403da0962d..9f70c3bd649 100644 --- a/libs/sdk-ui-charts/.dependency-cruiser.js +++ b/libs/sdk-ui-charts/.dependency-cruiser.js @@ -9,7 +9,6 @@ import { moduleWithDependencies, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ ...DefaultRules, diff --git a/libs/sdk-ui-charts/package.json b/libs/sdk-ui-charts/package.json index 2d957f989d1..a9079ee2de3 100644 --- a/libs/sdk-ui-charts/package.json +++ b/libs/sdk-ui-charts/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-charts", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Charts", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -136,12 +136,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-charts/src/highcharts/adapter/highcharts.d.ts b/libs/sdk-ui-charts/src/highcharts/adapter/highcharts.d.ts index fefbdbb8529..6d343fb1127 100644 --- a/libs/sdk-ui-charts/src/highcharts/adapter/highcharts.d.ts +++ b/libs/sdk-ui-charts/src/highcharts/adapter/highcharts.d.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax export * from "highcharts"; diff --git a/libs/sdk-ui-charts/src/index.ts b/libs/sdk-ui-charts/src/index.ts index fecf5ac554f..5b1eef0d3e6 100644 --- a/libs/sdk-ui-charts/src/index.ts +++ b/libs/sdk-ui-charts/src/index.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides a set of React-based chart visualizations that you can use to visualize your data. diff --git a/libs/sdk-ui-charts/src/internal.ts b/libs/sdk-ui-charts/src/internal.ts index 5df5ac48dd5..2032308b91a 100644 --- a/libs/sdk-ui-charts/src/internal.ts +++ b/libs/sdk-ui-charts/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { type IRawChartProps, RawChart } from "./charts/_base/RawChart.js"; export { type IChartTransformationProps } from "./highcharts/ChartTransformation.js"; diff --git a/libs/sdk-ui-charts/src/typings.d.ts b/libs/sdk-ui-charts/src/typings.d.ts index 5caade09651..6f9aef68e99 100644 --- a/libs/sdk-ui-charts/src/typings.d.ts +++ b/libs/sdk-ui-charts/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/libs/sdk-ui-charts/vitest.config.ts b/libs/sdk-ui-charts/vitest.config.ts index 42f9309ff3b..2fd0204cf86 100644 --- a/libs/sdk-ui-charts/vitest.config.ts +++ b/libs/sdk-ui-charts/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-dashboard/.dependency-cruiser.js b/libs/sdk-ui-dashboard/.dependency-cruiser.js index 48b00b00bb5..95e745fa4ac 100644 --- a/libs/sdk-ui-dashboard/.dependency-cruiser.js +++ b/libs/sdk-ui-dashboard/.dependency-cruiser.js @@ -9,7 +9,6 @@ import { moduleWithDependencies, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ ...DefaultRules, diff --git a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md index 931d7989529..feacd50458a 100644 --- a/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md +++ b/libs/sdk-ui-dashboard/api/sdk-ui-dashboard.api.md @@ -410,9 +410,7 @@ export type AlertMetricComparator = { // @beta export enum AlertMetricComparatorType { - // (undocumented) PreviousPeriod = 0, - // (undocumented) SamePeriodPreviousYear = 1 } @@ -839,7 +837,7 @@ export function createScheduledEmail(scheduledEmail: IAutomationMetadataObjectDe // @beta export function crossFiltering(insight: IInsight, drillDefinition: ICrossFiltering_2, drillEvent: IDashboardDrillEvent, correlationId?: string): ICrossFiltering; -// @beta (undocumented) +// @beta export type CustomAlertingDialogComponent = ComponentType; // @beta @@ -847,7 +845,7 @@ export type CustomAlertingDialogContextDecoratorComponent = ComponentType<{ children?: ReactNode; }>; -// @beta (undocumented) +// @beta export type CustomAlertingManagementDialogComponent = ComponentType; // @beta @@ -993,7 +991,7 @@ export type CustomSaveAsNewButtonComponent = ComponentType; -// @beta (undocumented) +// @beta export type CustomScheduledEmailDialogComponent = ComponentType; // @beta @@ -1001,7 +999,7 @@ export type CustomScheduledEmailDialogContextDecoratorComponent = ComponentType< children?: ReactNode; }>; -// @beta (undocumented) +// @beta export type CustomScheduledEmailManagementDialogComponent = ComponentType; // @beta @@ -1105,10 +1103,10 @@ export type DashboardAttributeFilterSelectionChangedPayload = { export { dashboardAttributeFilterToAttributeFilter } // @public -export type DashboardCommands = InitializeDashboard | SaveDashboardAs | RequestAsyncRender | ResolveAsyncRender | ChangeFilterContextSelection | ChangeDateFilterSelection | ChangeAttributeFilterSelection | ReplaceAttributeFilterItemSelection | IChangeRenderMode | IChangeDashboardDensity | IRequestOpenDensityDialog | IChangeDashboardTimezoneOverride | ISaveDashboard | IRenameDashboard | IResetDashboard | IExportDashboardToPdf | IExportDashboardToExcel | IExportDashboardToPdfPresentation | IExportDashboardToPptPresentation | IDeleteDashboard | ITriggerEvent | IUpsertExecutionResult | IAddAttributeFilter | IAddTextAttributeFilter | IRemoveAttributeFilters | IMoveAttributeFilter | ISetAttributeFilterParents | ISetAttributeFilterDependentDateFilters | IAddLayoutSection | IMoveLayoutSection | IRemoveLayoutSection | ChangeLayoutSectionHeader | IResizeHeight | IResizeWidth | IAddSectionItems | IReplaceSectionItem | IMoveSectionItem | IMoveSectionItemToNewSection | IRemoveSectionItem | IRemoveSectionItemByWidgetRef | IUndoLayoutChanges | IChangeKpiWidgetHeader | IChangeKpiWidgetDescription | IChangeKpiWidgetConfiguration | IChangeKpiWidgetMeasure | IChangeKpiWidgetFilterSettings | IChangeKpiWidgetComparison | IRefreshKpiWidget | ISetDrillForKpiWidget | IRemoveDrillForKpiWidget | IChangeInsightWidgetHeader | IChangeInsightWidgetDescription | IChangeInsightWidgetIgnoreCrossFiltering | IChangeInsightWidgetFilterSettings | IChangeInsightWidgetVisProperties | ChangeInsightWidgetVisConfiguration | IChangeInsightWidgetInsight | IModifyDrillsForInsightWidget | IRemoveDrillsForInsightWidget | IRefreshInsightWidget | IExportInsightWidget | ICreateAlert | ISaveAlert | ICreateScheduledEmail | ISaveScheduledEmail | IChangeSharing | ISetAttributeFilterDisplayForm | ISetAttributeFilterTitle | ISetMeasureValueFilterTitle | ISetMeasureValueFilterDimensionality | ISetAttributeFilterSelectionMode | IChangeRichTextWidgetContent | IChangeRichTextWidgetFilterSettings | IAddVisualizationToVisualizationSwitcherWidgetContent | IUpdateVisualizationsFromVisualizationSwitcherWidgetContent | IChangeVisualizationSwitcherActiveVisualization | IDrill | IDrillDown | IDrillToAttributeUrl | IDrillToCustomUrl | IDrillToDashboard | IDrillToInsight | IDrillToLegacyDashboard | IChangeDrillableItems | IAddDrillTargets | ISetDashboardDateFilterConfigMode | ISetDashboardAttributeFilterConfigMode | ISetDashboardMeasureValueFilterConfigMode | ISetDashboardAttributeFilterSelectionType | ISetDashboardAttributeFilterConfigDisplayAsLabel | IRemoveDrillDownForInsightWidget | IRemoveDrillToUrlForInsightWidget | IAddDrillDownForInsightWidget | IModifyDrillDownForInsightWidget | ICrossFiltering | IKeyDriverAnalysis | IAttributeHierarchyModified | IAddDateFilter | IAddMeasureValueFilter | IRemoveMeasureValueFilters | IRemoveDateFilters | IMoveDateFilter | IChangeMeasureValueFilterCondition | IMoveMeasureValueFilter | ISetDashboardDateFilterWithDimensionConfigMode | ISetDateFilterConfigTitle | IInitializeAutomations | IRefreshAutomations | ISetAttributeFilterLimitingItems | ISaveFilterView | IDeleteFilterView | IApplyFilterView | ISetFilterViewAsDefault | IReloadFilterViews | IToggleLayoutSectionHeaders | IToggleLayoutDirection | IApplyFilterContextWorkingSelection | IResetFilterContextWorkingSelection | IChangeIgnoreExecutionTimestamp | ISwitchDashboardTab | ICreateDashboardTab | IRepositionDashboardTab | IDeleteDashboardTab | IStartRenamingDashboardTab | ICancelRenamingDashboardTab | IRenameDashboardTab | ISetScreenSize | IExportRawInsightWidget | IExportSlidesInsightWidget | IExportImageInsightWidget | ISetShowWidgetAsTable | IChangeParameterValues; +export type DashboardCommands = InitializeDashboard | SaveDashboardAs | RequestAsyncRender | ResolveAsyncRender | ChangeFilterContextSelection | ChangeDateFilterSelection | ChangeAttributeFilterSelection | ReplaceAttributeFilterItemSelection | IChangeRenderMode | IChangeDashboardDensity | IRequestOpenDensityDialog | IChangeDashboardTimezoneOverride | ISaveDashboard | IRenameDashboard | IResetDashboard | IExportDashboardToPdf | IExportDashboardToExcel | IExportDashboardToPdfPresentation | IExportDashboardToPptPresentation | IDeleteDashboard | ITriggerEvent | IUpsertExecutionResult | IAddAttributeFilter | IAddTextAttributeFilter | IRemoveAttributeFilters | IMoveAttributeFilter | ISetAttributeFilterParents | ISetAttributeFilterDependentDateFilters | IAddLayoutSection | IMoveLayoutSection | IRemoveLayoutSection | ChangeLayoutSectionHeader | IResizeHeight | IResizeWidth | IAddSectionItems | IReplaceSectionItem | IMoveSectionItem | IMoveSectionItemToNewSection | IRemoveSectionItem | IRemoveSectionItemByWidgetRef | IUndoLayoutChanges | IChangeKpiWidgetHeader | IChangeKpiWidgetDescription | IChangeKpiWidgetConfiguration | IChangeKpiWidgetMeasure | IChangeKpiWidgetFilterSettings | IChangeKpiWidgetComparison | IRefreshKpiWidget | ISetDrillForKpiWidget | IRemoveDrillForKpiWidget | IChangeInsightWidgetHeader | IChangeInsightWidgetDescription | IChangeInsightWidgetIgnoreCrossFiltering | IChangeInsightWidgetFilterSettings | IChangeInsightWidgetVisProperties | ChangeInsightWidgetVisConfiguration | IChangeInsightWidgetInsight | IModifyDrillsForInsightWidget | IRemoveDrillsForInsightWidget | IRefreshInsightWidget | IExportInsightWidget | ICreateAlert | ISaveAlert | ICreateScheduledEmail | ISaveScheduledEmail | IChangeSharing | ISetAttributeFilterDisplayForm | ISetAttributeFilterTitle | ISetMeasureValueFilterTitle | ISetMeasureValueFilterDimensionality | ISetAttributeFilterSelectionMode | IChangeRichTextWidgetContent | IChangeRichTextWidgetFilterSettings | IAddVisualizationToVisualizationSwitcherWidgetContent | IUpdateVisualizationsFromVisualizationSwitcherWidgetContent | IChangeVisualizationSwitcherActiveVisualization | IDrill | IDrillDown | IDrillToAttributeUrl | IDrillToCustomUrl | IDrillToDashboard | IDrillToInsight | IDrillToLegacyDashboard | IChangeDrillableItems | IAddDrillTargets | ISetDashboardDateFilterConfigMode | ISetDashboardAttributeFilterConfigMode | ISetDashboardMeasureValueFilterConfigMode | ISetDashboardAttributeFilterSelectionType | ISetDashboardAttributeFilterConfigDisplayAsLabel | IRemoveDrillDownForInsightWidget | IRemoveDrillToUrlForInsightWidget | IAddDrillDownForInsightWidget | IModifyDrillDownForInsightWidget | ICrossFiltering | IKeyDriverAnalysis | IAttributeHierarchyModified | IAddDateFilter | IAddMeasureValueFilter | IRemoveMeasureValueFilters | IRemoveRestrictedFilters | IRemoveDateFilters | IMoveDateFilter | IChangeMeasureValueFilterCondition | IMoveMeasureValueFilter | ISetDashboardDateFilterWithDimensionConfigMode | ISetDateFilterConfigTitle | IInitializeAutomations | IRefreshAutomations | ISetAttributeFilterLimitingItems | ISaveFilterView | IDeleteFilterView | IApplyFilterView | ISetFilterViewAsDefault | IReloadFilterViews | IToggleLayoutSectionHeaders | IToggleLayoutDirection | IApplyFilterContextWorkingSelection | IResetFilterContextWorkingSelection | IChangeIgnoreExecutionTimestamp | ISwitchDashboardTab | ICreateDashboardTab | IRepositionDashboardTab | IDeleteDashboardTab | IStartRenamingDashboardTab | ICancelRenamingDashboardTab | IRenameDashboardTab | ISetScreenSize | IExportRawInsightWidget | IExportSlidesInsightWidget | IExportImageInsightWidget | ISetShowWidgetAsTable | IChangeParameterValues; // @public -export type DashboardCommandType = "GDC.DASH/CMD.INITIALIZE" | "GDC.DASH/CMD.SAVE" | "GDC.DASH/CMD.SAVEAS" | "GDC.DASH/CMD.RESET" | "GDC.DASH/CMD.RENAME" | "GDC.DASH/CMD.DELETE" | "GDC.DASH/CMD.CHANGE_RENDER_MODE" | "GDC.DASH/CMD.SHARING.CHANGE" | "GDC.DASH/CMD.EXPORT.PDF" | "GDC.DASH/CMD.EXPORT.EXCEL" | "GDC.DASH/CMD.EXPORT.PDF_PRESENTATION" | "GDC.DASH/CMD.EXPORT.PPT_PRESENTATION" | "GDC.DASH/CMD.EVENT.TRIGGER" | "GDC.DASH/CMD.EXECUTION_RESULT.UPSERT" | "GDC.DASH/CMD.FILTER_CONTEXT.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.TEXT_ATTRIBUTE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER_ITEM.REPLACE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_PARENTS" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_DISPLAY_FORM" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_TITLE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_SELECTION_MODE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_DEPENDENT_DATE_FILTERS" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.CHANGE_CONDITION" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.SET_DIMENSIONALITY" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.SET_TITLE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.MEASURE_VALUE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_AVAILABLE_MODE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_LIMITING_ITEMS" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_DISPLAY_AS_LABEL" | "GDC.DASH/CMD.DATE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.DATE_FILTER_WITH_DIMENSION_CONFIG.SET_MODE" | "GDC.DASH/CMD.DATE_FILTER_CONFIG.SET_TITLE" | "GDC.DASH/CMD.FLUID_LAYOUT.ADD_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_ITEM_TO_NEW_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.CHANGE_SECTION_HEADER" | "GDC.DASH/CMD.FLUID_LAYOUT.ADD_ITEMS" | "GDC.DASH/CMD.FLUID_LAYOUT.REPLACE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_ITEM_BY_WIDGET_REF" | "GDC.DASH/CMD.FLUID_LAYOUT.UNDO" | "GDC.DASH/CMD.FLUID_LAYOUT.RESIZE_HEIGHT" | "GDC.DASH/CMD.FLUID_LAYOUT.RESIZE_WIDTH" | "GDC.DASH/CMD.FLUID_LAYOUT.SET_SCREEN_SIZE" | "GDC.DASH/CMD.FLEXIBLE_LAYOUT.TOGGLE_LAYOUT_SECTION_HEADERS" | "GDC.DASH/CMD.FLEXIBLE_LAYOUT.TOGGLE_LAYOUT_DIRECTION" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_HEADER" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_MEASURE" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_COMPARISON" | "GDC.DASH/CMD.KPI_WIDGET.REFRESH" | "GDC.DASH/CMD.KPI_WIDGET.SET_DRILL" | "GDC.DASH/CMD.KPI_WIDGET.REMOVE_DRILL" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_DESCRIPTION" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_CONFIGURATION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_HEADER" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_DESCRIPTION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_PROPERTIES" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_CONFIGURATION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_INSIGHT" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_IGNORE_CROSS_FILTERING" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT" | "GDC.DASH/CMD.INSIGHT_WIDGET.MODIFY_DRILLS" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILLS" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILL_TO_URL" | "GDC.DASH/CMD.INSIGHT_WIDGET.ADD_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.MODIFY_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.REFRESH" | "GDC.DASH/CMD.RICH_TEXT_WIDGET.CHANGE_CONTENT" | "GDC.DASH/CMD.RICH_TEXT_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.ADD_VISUALIZATION" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.UPDATE_VISUALIZATIONS" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.CHANGE_ACTIVE_VISUALIZATION" | "GDC.DASH/CMD.ALERT.CREATE" | "GDC.DASH/CMD.ALERT.SAVE" | "GDC.DASH/CMD.SCHEDULED_EMAIL.CREATE" | "GDC.DASH/CMD.SCHEDULED_EMAIL.SAVE" | "GDC.DASH/CMD.DRILL" | "GDC.DASH/CMD.DRILL.DRILL_DOWN" | "GDC.DASH/CMD.DRILL.DRILL_TO_INSIGHT" | "GDC.DASH/CMD.DRILL.DRILL_TO_DASHBOARD" | "GDC.DASH/CMD.DRILL.DRILL_TO_ATTRIBUTE_URL" | "GDC.DASH/CMD.DRILL.DRILL_TO_CUSTOM_URL" | "GDC.DASH/CMD.DRILL.DRILL_TO_LEGACY_DASHBOARD" | "GDC.DASH/CMD.DRILL.DRILLABLE_ITEMS.CHANGE" | "GDC.DASH/CMD.DRILL.CROSS_FILTERING" | "GDC.DASH/CMD.DRILL.KEY_DRIVER_ANALYSIS" | "GDC.DASH/CMD.DRILL_TARGETS.ADD" | "GDC.DASH/CMD.RENDER.ASYNC.REQUEST" | "GDC.DASH/CMD.RENDER.ASYNC.RESOLVE" | "GDC.DASH/CMD.ATTRIBUTE_HIERARCHY_MODIFIED" | "GDC.DASH/CMD.AUTOMATIONS.INITIALIZE" | "GDC.DASH/CMD.AUTOMATIONS.REFRESH" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.SAVE" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.DELETE" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.APPLY" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.CHANGE_DEFAULT_STATUS" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.RELOAD" | "GDC.DASH/CMD.FILTER_CONTEXT.APPLY_WORKING_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.RESET_WORKING_SELECTION" | "GDC.DASH/CMD.CHANGE_IGNORE_EXECUTION_TIMESTAMP" | "GDC.DASH/CMD.TAB.SWITCH" | "GDC.DASH/CMD.TAB.REPOSITION" | "GDC.DASH/CMD.TAB.CREATE" | "GDC.DASH/CMD.TAB.DELETE" | "GDC.DASH/CMD.TAB.RENAME_MODE.START" | "GDC.DASH/CMD.TAB.RENAME_MODE.CANCEL" | "GDC.DASH/CMD.TAB.RENAME" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_RAW" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_SLIDES" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_IMAGE" | "GDC.DASH/CMD.SHOW_WIDGET_AS_TABLE.SET" | "GDC.DASH/CMD.CHANGE_DENSITY" | "GDC.DASH/CMD.OPEN_DENSITY_DIALOG" | "GDC.DASH/CMD.CHANGE_TIMEZONE_OVERRIDE" | "GDC.DASH/CMD.PARAMETERS.CHANGE_VALUES"; +export type DashboardCommandType = "GDC.DASH/CMD.INITIALIZE" | "GDC.DASH/CMD.SAVE" | "GDC.DASH/CMD.SAVEAS" | "GDC.DASH/CMD.RESET" | "GDC.DASH/CMD.RENAME" | "GDC.DASH/CMD.DELETE" | "GDC.DASH/CMD.CHANGE_RENDER_MODE" | "GDC.DASH/CMD.SHARING.CHANGE" | "GDC.DASH/CMD.EXPORT.PDF" | "GDC.DASH/CMD.EXPORT.EXCEL" | "GDC.DASH/CMD.EXPORT.PDF_PRESENTATION" | "GDC.DASH/CMD.EXPORT.PPT_PRESENTATION" | "GDC.DASH/CMD.EVENT.TRIGGER" | "GDC.DASH/CMD.EXECUTION_RESULT.UPSERT" | "GDC.DASH/CMD.FILTER_CONTEXT.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.TEXT_ATTRIBUTE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.CHANGE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER_ITEM.REPLACE_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_PARENTS" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_DISPLAY_FORM" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_TITLE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_SELECTION_MODE" | "GDC.DASH/CMD.FILTER_CONTEXT.ATTRIBUTE_FILTER.SET_DEPENDENT_DATE_FILTERS" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.CHANGE_CONDITION" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.SET_DIMENSIONALITY" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.SET_TITLE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.MEASURE_VALUE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_AVAILABLE_MODE" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_LIMITING_ITEMS" | "GDC.DASH/CMD.ATTRIBUTE_FILTER_CONFIG.SET_DISPLAY_AS_LABEL" | "GDC.DASH/CMD.DATE_FILTER_CONFIG.SET_MODE" | "GDC.DASH/CMD.DATE_FILTER_WITH_DIMENSION_CONFIG.SET_MODE" | "GDC.DASH/CMD.DATE_FILTER_CONFIG.SET_TITLE" | "GDC.DASH/CMD.FLUID_LAYOUT.ADD_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_ITEM_TO_NEW_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_SECTION" | "GDC.DASH/CMD.FLUID_LAYOUT.CHANGE_SECTION_HEADER" | "GDC.DASH/CMD.FLUID_LAYOUT.ADD_ITEMS" | "GDC.DASH/CMD.FLUID_LAYOUT.REPLACE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.MOVE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_ITEM" | "GDC.DASH/CMD.FLUID_LAYOUT.REMOVE_ITEM_BY_WIDGET_REF" | "GDC.DASH/CMD.FLUID_LAYOUT.UNDO" | "GDC.DASH/CMD.FLUID_LAYOUT.RESIZE_HEIGHT" | "GDC.DASH/CMD.FLUID_LAYOUT.RESIZE_WIDTH" | "GDC.DASH/CMD.FLUID_LAYOUT.SET_SCREEN_SIZE" | "GDC.DASH/CMD.FLEXIBLE_LAYOUT.TOGGLE_LAYOUT_SECTION_HEADERS" | "GDC.DASH/CMD.FLEXIBLE_LAYOUT.TOGGLE_LAYOUT_DIRECTION" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_HEADER" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_MEASURE" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_COMPARISON" | "GDC.DASH/CMD.KPI_WIDGET.REFRESH" | "GDC.DASH/CMD.KPI_WIDGET.SET_DRILL" | "GDC.DASH/CMD.KPI_WIDGET.REMOVE_DRILL" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_DESCRIPTION" | "GDC.DASH/CMD.KPI_WIDGET.CHANGE_CONFIGURATION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_HEADER" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_DESCRIPTION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_PROPERTIES" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_CONFIGURATION" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_INSIGHT" | "GDC.DASH/CMD.INSIGHT_WIDGET.CHANGE_IGNORE_CROSS_FILTERING" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT" | "GDC.DASH/CMD.INSIGHT_WIDGET.MODIFY_DRILLS" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILLS" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.REMOVE_DRILL_TO_URL" | "GDC.DASH/CMD.INSIGHT_WIDGET.ADD_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.MODIFY_DRILL_DOWN" | "GDC.DASH/CMD.INSIGHT_WIDGET.REFRESH" | "GDC.DASH/CMD.RICH_TEXT_WIDGET.CHANGE_CONTENT" | "GDC.DASH/CMD.RICH_TEXT_WIDGET.CHANGE_FILTER_SETTINGS" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.ADD_VISUALIZATION" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.UPDATE_VISUALIZATIONS" | "GDC.DASH/CMD.VISUALIZATION_SWITCHER_WIDGET.CHANGE_ACTIVE_VISUALIZATION" | "GDC.DASH/CMD.ALERT.CREATE" | "GDC.DASH/CMD.ALERT.SAVE" | "GDC.DASH/CMD.SCHEDULED_EMAIL.CREATE" | "GDC.DASH/CMD.SCHEDULED_EMAIL.SAVE" | "GDC.DASH/CMD.DRILL" | "GDC.DASH/CMD.DRILL.DRILL_DOWN" | "GDC.DASH/CMD.DRILL.DRILL_TO_INSIGHT" | "GDC.DASH/CMD.DRILL.DRILL_TO_DASHBOARD" | "GDC.DASH/CMD.DRILL.DRILL_TO_ATTRIBUTE_URL" | "GDC.DASH/CMD.DRILL.DRILL_TO_CUSTOM_URL" | "GDC.DASH/CMD.DRILL.DRILL_TO_LEGACY_DASHBOARD" | "GDC.DASH/CMD.DRILL.DRILLABLE_ITEMS.CHANGE" | "GDC.DASH/CMD.DRILL.CROSS_FILTERING" | "GDC.DASH/CMD.DRILL.KEY_DRIVER_ANALYSIS" | "GDC.DASH/CMD.DRILL_TARGETS.ADD" | "GDC.DASH/CMD.RENDER.ASYNC.REQUEST" | "GDC.DASH/CMD.RENDER.ASYNC.RESOLVE" | "GDC.DASH/CMD.ATTRIBUTE_HIERARCHY_MODIFIED" | "GDC.DASH/CMD.AUTOMATIONS.INITIALIZE" | "GDC.DASH/CMD.AUTOMATIONS.REFRESH" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.SAVE" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.DELETE" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.APPLY" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.CHANGE_DEFAULT_STATUS" | "GDC.DASH/CMD.FILTER_CONTEXT.FILTER_VIEW.RELOAD" | "GDC.DASH/CMD.FILTER_CONTEXT.APPLY_WORKING_SELECTION" | "GDC.DASH/CMD.FILTER_CONTEXT.RESET_WORKING_SELECTION" | "GDC.DASH/CMD.CHANGE_IGNORE_EXECUTION_TIMESTAMP" | "GDC.DASH/CMD.TAB.SWITCH" | "GDC.DASH/CMD.TAB.REPOSITION" | "GDC.DASH/CMD.TAB.CREATE" | "GDC.DASH/CMD.TAB.DELETE" | "GDC.DASH/CMD.TAB.RENAME_MODE.START" | "GDC.DASH/CMD.TAB.RENAME_MODE.CANCEL" | "GDC.DASH/CMD.TAB.RENAME" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_RAW" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_SLIDES" | "GDC.DASH/CMD.INSIGHT_WIDGET.EXPORT_IMAGE" | "GDC.DASH/CMD.SHOW_WIDGET_AS_TABLE.SET" | "GDC.DASH/CMD.CHANGE_DENSITY" | "GDC.DASH/CMD.OPEN_DENSITY_DIALOG" | "GDC.DASH/CMD.CHANGE_TIMEZONE_OVERRIDE" | "GDC.DASH/CMD.PARAMETERS.CHANGE_VALUES"; // @public export type DashboardConfig = { @@ -1849,7 +1847,7 @@ export function DefaultScheduledEmailDialogTimezone(input: IScheduledEmailDialog // @beta export function DefaultScheduledEmailDialogWidgetAttachments(props: IScheduledEmailDialogWidgetAttachmentsProps): ReactElement; -// @beta (undocumented) +// @beta export function DefaultScheduledEmailManagementDialog(input: IScheduledEmailManagementDialogProps): JSX.Element; // @internal (undocumented) @@ -2680,37 +2678,24 @@ export interface IAlertingDialogComparisonPeriodProps { // @beta export interface IAlertingDialogContextValue { alertToEdit?: IAutomationMetadataObject; - // (undocumented) commonDateFilterId?: string; - // (undocumented) createAlert(alert: IAutomationMetadataObjectDefinition): Promise; - // (undocumented) dashboardEvaluationFrequency?: string; - // (undocumented) dashboardFilters: FilterContextItem[]; - // (undocumented) dashboardId?: string; dashboardParameters: IDashboardParameter[]; - // (undocumented) deleteAlert(alert: IAutomationMetadataObject): Promise; - // (undocumented) executionResultByRef: (ref: ObjRef | undefined) => { executionResult?: IExecutionResult; } | undefined; - // (undocumented) hiddenFilters: FilterContextItem[]; - // (undocumented) insight?: IInsight; isLoading: boolean; - // (undocumented) mode: "create" | "edit"; notificationChannels: INotificationChannelIdentifier[] | INotificationChannelMetadataObject[]; parameterValues: IInsightParameterValue[]; - // (undocumented) saveAlert(alert: IAutomationMetadataObject): Promise; - // (undocumented) widget?: IWidget; - // (undocumented) widgetTitle?: string; } @@ -2751,7 +2736,7 @@ export interface IAlertingDialogMeasureProps { selectedMeasure: AlertMetric | undefined; } -// @beta (undocumented) +// @beta export interface IAlertingDialogProps extends IAutomationDialogCallbacks { // @deprecated alertToEdit?: IAutomationMetadataObject; @@ -2824,41 +2809,27 @@ export interface IAlertingDialogTriggerModeProps { // @alpha export interface IAlertingManagementDialogContextValue { - // (undocumented) alertingDialogReturnFocusTo?: string; automations: IAutomationMetadataObject[]; - // (undocumented) automationsInvalidationId?: number; - // (undocumented) canManageWorkspace: boolean; - // (undocumented) currentUser?: IUser; - // (undocumented) dashboardId?: string; - // (undocumented) dashboardTitle?: string; - // (undocumented) enableAccessibilityMode: boolean; - // (undocumented) getInsightByWidgetRef: (ref: ObjRef | undefined) => IInsight | undefined; - // (undocumented) getWidgetByRef: (ref: ObjRef | undefined) => IWidget | undefined; - // (undocumented) isAlertDialogOpen: boolean; - // (undocumented) isEmbedded: boolean; isLoading: boolean; - // (undocumented) managementDialogContext: { widgetRef?: ObjRef; }; - // (undocumented) pauseAlert(alert: IAutomationMetadataObject): Promise; - // (undocumented) resumeAlert(alert: IAutomationMetadataObject): Promise; } -// @beta (undocumented) +// @beta export interface IAlertingManagementDialogProps extends IAutomationManagementDialogCallbacks { // @deprecated alertDataError?: GoodDataSdkError; @@ -3053,43 +3024,24 @@ export interface IAutomationParameter { // @beta export interface IAutomationsContextValue { - // (undocumented) allowHourlyRecurrence: boolean; - // (undocumented) attributeFilterConfigs: IDashboardAttributeFilterConfig[]; - // (undocumented) attributeFilterConfigsByTab: Record; - // (undocumented) attributeFilterSelectionTypeMap: Map; - // (undocumented) attributeFilterSelectionTypeMapByTab: Record>; - // (undocumented) automationAvailableFilters: FilterContextItem[]; - // (undocumented) automationFiltersByTab: IAutomationFiltersTab[]; - // (undocumented) availableFilters: FilterContextItem[]; - // (undocumented) catalogAttributes: ICatalogAttribute[]; - // (undocumented) catalogDateDatasets: ICatalogDateDataset[]; - // (undocumented) catalogMeasures: ICatalogMeasure[]; - // (undocumented) commonDateFilterId: string | undefined; - // (undocumented) currentUser: IUser; - // (undocumented) dateFilterConfig: IAutomationsDateFilterConfig; - // (undocumented) dateFilterConfigOverridesByTab: Record; - // (undocumented) dateFilterConfigs: IDashboardDateFilterConfigItem[]; - // (undocumented) dateFilterConfigsByTab: Record; - // (undocumented) dateFilterContextConfig: IDashboardDateFilterConfig_2 | undefined; - // (undocumented) defaultSelectedFilters: FilterContextItem[]; exportTimezones?: { isTimezoneFeatureEnabled: boolean; @@ -3099,9 +3051,7 @@ export interface IAutomationsContextValue { effectiveTimezone: string | undefined; scheduledExportTimezone: string | undefined; }; - // (undocumented) externalRecipient: string | undefined; - // (undocumented) features: { canCreateAutomation: boolean; enableAlertOncePerInterval: boolean; @@ -3111,38 +3061,23 @@ export interface IAutomationsContextValue { enableSlideshowExports: boolean; enableAutomationEvaluationMode: boolean; }; - // (undocumented) getAttributeFilterDisplayForm: (displayForm: ObjRef) => IAttributeDisplayFormMetadataObject | undefined; - // (undocumented) getCatalogAttributeByRef: (ref: ObjRef) => ICatalogAttribute | ICatalogDateAttribute | undefined; - // (undocumented) hiddenFilters: FilterContextItem[]; - // (undocumented) isExecutionTimestampMode: boolean; - // (undocumented) isSecondaryTitleVisible: boolean; - // (undocumented) isWhiteLabeled: boolean; - // (undocumented) locale: ILocale; - // (undocumented) lockedFilters: FilterContextItem[]; - // (undocumented) maxAutomationsRecipients: number; - // (undocumented) measureValueFilterConfigs: IDashboardMeasureValueFilterConfig[]; - // (undocumented) measureValueFilterConfigsByTab: Record; - // (undocumented) parameters: IAutomationsParameters; scheduleEmailDialogReturnFocusTo?: string; - // (undocumented) separators: ISeparators; - // (undocumented) settings?: ISettings; tabIds: string[]; timezone: string | undefined; - // (undocumented) weekStart: WeekStart; widgetExistsByRef: (ref: ObjRef | undefined) => boolean; widgetLocalIdToTabIdMap: Record; @@ -3150,13 +3085,9 @@ export interface IAutomationsContextValue { // @beta export interface IAutomationsDateFilterConfig { - // (undocumented) availableGranularities: DateFilterGranularity[]; - // (undocumented) dateFilterOptions: IDateFilterOptionsByType; - // (undocumented) getGranularitiesForTab: (tabId: string) => DateFilterGranularity[]; - // (undocumented) getOptionsForTab: (tabId: string) => IDateFilterOptionsByType | undefined; } @@ -8094,6 +8025,12 @@ export interface IRemoveParameterPayload { ref: ObjRef; } +// @beta +export interface IRemoveRestrictedFilters extends IDashboardCommand { + // (undocumented) + readonly type: "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE"; +} + // @beta (undocumented) export interface IRemoveSectionItem extends IDashboardCommand { // (undocumented) @@ -8296,6 +8233,7 @@ export interface IResolvedFilterValues { // @alpha export interface IRestrictedFiltersPlaceholderProps { count: number; + onRemove?: () => void; } // @alpha @@ -8498,34 +8436,25 @@ export type IScheduledEmailDialogActionBarBlockProps = IUseScheduledEmailDialogA // @beta export interface IScheduledEmailDialogContextValue { attributeFiltersModeMap: Map; - // (undocumented) commonDateFilterId?: string; commonDateFilterMode: DashboardDateFilterConfigMode; - // (undocumented) createScheduledEmail(se: IAutomationMetadataObjectDefinition): Promise; dashboardFilters?: FilterContextItem[]; - // (undocumented) dashboardId?: string; - // (undocumented) dashboardTitle: string; dateFiltersModeMap: Map; dateFormat: string | undefined; - // (undocumented) deleteScheduledEmail(se: IAutomationMetadataObject): Promise; exportParametersByTab: Record; exportTemplates: IExportTemplate[]; hiddenFilters: FilterContextItem[]; - // (undocumented) insight?: IInsight; isCrossFiltering: boolean; isLoading: boolean; notificationChannels: INotificationChannelIdentifier[] | INotificationChannelMetadataObject[]; - // (undocumented) saveScheduledEmail(se: IAutomationMetadataObject): Promise; scheduledExportToEdit?: IAutomationMetadataObject; - // (undocumented) widget?: IWidget; - // (undocumented) widgetTitle?: string; } @@ -8581,7 +8510,7 @@ export interface IScheduledEmailDialogMessageProps { value: string; } -// @beta (undocumented) +// @beta export interface IScheduledEmailDialogProps extends IAutomationDialogCallbacks { // @deprecated dashboardFilters?: FilterContextItem[]; @@ -8697,7 +8626,7 @@ export interface IScheduledEmailManagementDialogContextValue { unlimitedAutomations: boolean; } -// @beta (undocumented) +// @beta export interface IScheduledEmailManagementDialogProps extends IAutomationManagementDialogCallbacks { // @deprecated automations?: IAutomationMetadataObject[]; @@ -10049,6 +9978,11 @@ export interface IUiState { // (undocumented) insightListLastUpdateRequested: number; // (undocumented) + insightNotSavedDialog: { + open: boolean; + saveConfirmed: boolean; + }; + // (undocumented) kpiAlerts: { openedWidgetRef: ObjRef | undefined; highlightedWidgetRef: ObjRef | undefined; @@ -11250,6 +11184,9 @@ export function removeNestedLayoutSection(index: ILayoutSectionPath, stashIdenti // @alpha export function removeNestedLayoutSectionItem(itemPath: ILayoutItemPath, stashIdentifier?: StashedDashboardItemsId, correlationId?: string): IRemoveSectionItem; +// @beta +export function removeRestrictedFilters(correlationId?: string): IRemoveRestrictedFilters; + // @beta export function removeSectionItem(sectionIndex: number, itemIndex: number, stashIdentifier?: StashedDashboardItemsId, correlationId?: string): IRemoveSectionItem; @@ -13802,6 +13739,18 @@ newSize?: IVisualizationSizeInfo; }; type: string; }) => void | ITabsState | WritableDraft; +readonly replaceInsightWidgetInsightForTab: (state: WritableDraft, action: { +payload: { +ref: ObjRef; +insightRef: ObjRef; +properties: VisualizationProperties | undefined; +header: IWidgetHeader | undefined; +newSize?: IVisualizationSizeInfo; +} & IUndoPayload & { +tabId: string; +}; +type: string; +}) => void | ITabsState | WritableDraft; readonly replaceWidgetFilterSettings: (state: WritableDraft, action: { payload: IUndoPayload & { ref: ObjRef; @@ -14009,6 +13958,9 @@ export const uiActions: { closeAlertingDialog: ActionCreatorWithoutPayload<"uiSlice/closeAlertingDialog">; openSaveAsDialog: ActionCreatorWithoutPayload<"uiSlice/openSaveAsDialog">; closeSaveAsDialog: ActionCreatorWithoutPayload<"uiSlice/closeSaveAsDialog">; + openInsightNotSavedDialog: ActionCreatorWithoutPayload<"uiSlice/openInsightNotSavedDialog">; + closeInsightNotSavedDialog: ActionCreatorWithoutPayload<"uiSlice/closeInsightNotSavedDialog">; + confirmInsightNotSavedDialogSubmit: ActionCreatorWithoutPayload<"uiSlice/confirmInsightNotSavedDialogSubmit">; setFilterBarExpanded: ActionCreatorWithPayload; closeKpiAlertDialog: ActionCreatorWithoutPayload<"uiSlice/closeKpiAlertDialog">; openKpiAlertDialog: ActionCreatorWithPayload; diff --git a/libs/sdk-ui-dashboard/package.json b/libs/sdk-ui-dashboard/package.json index dae7a2212a7..a6b5405c664 100644 --- a/libs/sdk-ui-dashboard/package.json +++ b/libs/sdk-ui-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-dashboard", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - Dashboard Component", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -138,12 +138,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "svgo": "2.8.3", "typescript": "7.0.2", diff --git a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterConfigConverters.ts b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterConfigConverters.ts index 0f5318aeb5c..716847cb797 100644 --- a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterConfigConverters.ts +++ b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterConfigConverters.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files // Moved to sdk-ui-filters (shared with the CF date picker); re-exported for existing import sites. export { convertDateFilterConfigToDateFilterOptions } from "@gooddata/sdk-ui-filters"; diff --git a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterOptionMapping.ts b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterOptionMapping.ts index eee5dbad9cc..de7121b5e98 100644 --- a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterOptionMapping.ts +++ b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/dateFilterOptionMapping.ts @@ -1,6 +1,6 @@ // (C) 2021-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files // Moved to sdk-ui-filters (shared with the CF date picker); re-exported for existing import sites. export { diff --git a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/defaultConfig.ts b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/defaultConfig.ts index e683cfd89c5..edb0913a73b 100644 --- a/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/defaultConfig.ts +++ b/libs/sdk-ui-dashboard/src/_staging/dateFilterConfig/defaultConfig.ts @@ -1,6 +1,6 @@ // (C) 2021-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files // Moved to @gooddata/sdk-ui-filters (shared with the CF date pickers); re-exported for existing import sites. export { diff --git a/libs/sdk-ui-dashboard/src/_staging/metadata/objRefMap.ts b/libs/sdk-ui-dashboard/src/_staging/metadata/objRefMap.ts index e286d0e47d1..674c7a71594 100644 --- a/libs/sdk-ui-dashboard/src/_staging/metadata/objRefMap.ts +++ b/libs/sdk-ui-dashboard/src/_staging/metadata/objRefMap.ts @@ -144,7 +144,7 @@ export class ObjRefMap { const strictMatch = this.itemsByIdentifier[this.idRefToKey(key.identifier, key.type)]; if (!strictMatch && !key.type && this.config.strictTypeCheck) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( "You are working with an analytical backend which can only match entities by idRefs that contain both identifier and type. However, while trying to find match an object by ref your code supplied just the identifier without type.", ); diff --git a/libs/sdk-ui-dashboard/src/index.ts b/libs/sdk-ui-dashboard/src/index.ts index a7bac5c5735..be3826b6b35 100644 --- a/libs/sdk-ui-dashboard/src/index.ts +++ b/libs/sdk-ui-dashboard/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides a Dashboard component that enables you to embed dashboards into your application as React components. @@ -348,6 +348,8 @@ export { removeMeasureValueFilter, removeMeasureValueFilters, removeDateFilter, + removeRestrictedFilters, + type IRemoveRestrictedFilters, moveMeasureValueFilter, moveDateFilter, changeMeasureValueFilterCondition, diff --git a/libs/sdk-ui-dashboard/src/internal.ts b/libs/sdk-ui-dashboard/src/internal.ts index 6b8d30436b3..34a9ee5f374 100644 --- a/libs/sdk-ui-dashboard/src/internal.ts +++ b/libs/sdk-ui-dashboard/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2022-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This file is used to re-export internal parts of the package that are used in other GoodData applications. diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/common/validateDrillToCustomUrlParams.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/common/validateDrillToCustomUrlParams.ts index a6ce658051a..6041309c606 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/common/validateDrillToCustomUrlParams.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/common/validateDrillToCustomUrlParams.ts @@ -12,7 +12,6 @@ import { areObjRefsEqual, dashboardAttributeFilterItemToAttributeFilter, filterObjRef, - idRef, isArbitraryAttributeFilter, isAttributeFilter, isDrillToCustomUrl, @@ -20,6 +19,7 @@ import { isNegativeAttributeFilter, widgetRef, } from "@gooddata/sdk-model"; +import { displayFormPlaceholderRef } from "@gooddata/sdk-model/internal"; import { type ObjRefMap } from "../../../_staging/metadata/objRefMap.js"; import { queryWidgetFilters } from "../../queries/widgets.js"; @@ -160,7 +160,7 @@ function sanitizeAttributeFilter( ...filter, negativeAttributeFilter: { ...filter.negativeAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } else if (isArbitraryAttributeFilter(filter)) { @@ -168,7 +168,7 @@ function sanitizeAttributeFilter( ...filter, arbitraryAttributeFilter: { ...filter.arbitraryAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } else if (isMatchAttributeFilter(filter)) { @@ -176,7 +176,7 @@ function sanitizeAttributeFilter( ...filter, matchAttributeFilter: { ...filter.matchAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } else { @@ -184,7 +184,7 @@ function sanitizeAttributeFilter( ...filter, positiveAttributeFilter: { ...filter.positiveAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.test.ts index 195ecb3ff79..a3b03f17922 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.test.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it } from "vitest"; -import { type IDashboardDefinition } from "@gooddata/sdk-model"; +import { type IDashboardDefinition, areObjRefsEqual, isInsightWidget } from "@gooddata/sdk-model"; import { TestCorrelation } from "../../../tests/Dashboard.test.helpers.js"; import { @@ -11,11 +11,16 @@ import { } from "../../../tests/SimpleDashboard.test.helpers.js"; import { saveDashboard } from "../../commands/dashboard.js"; import { addLayoutSection } from "../../commands/layout.js"; +import { createDashboardTab, switchDashboardTab } from "../../commands/tabs.js"; import { type DashboardTester, preloadedTesterFactory } from "../../DashboardTester.js"; import { type DashboardSaved } from "../../events/dashboard.js"; +import { selectInsights } from "../../store/insights/insightsSelectors.js"; import { selectPersistedDashboard } from "../../store/meta/metaSelectors.js"; import { selectFilterContextIdentity } from "../../store/tabs/filterContext/filterContextSelectors.js"; import { selectBasicLayout } from "../../store/tabs/layout/layoutSelectors.js"; +import { selectActiveTabLocalIdentifier } from "../../store/tabs/tabsSelectors.js"; +import { uiActions } from "../../store/ui/index.js"; +import { selectIsInsightNotSavedDialogOpen } from "../../store/ui/uiSelectors.js"; import { TestInsightItem } from "../../tests/Layout.test.helpers.js"; import { isTemporaryIdentity } from "../../utils/dashboardItemUtils.js"; @@ -109,6 +114,263 @@ describe("save dashboard handler", () => { expect(Tester.emittedEventsDigest()).toMatchSnapshot(); }); + + it("should open warning dialog and block save when dashboard contains draft insight", async () => { + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithSection = Tester.state(); + const firstWidget = selectBasicLayout(stateWithSection).sections[0].items[0].widget; + expect(firstWidget && isInsightWidget(firstWidget)).toBe(true); + + if (!firstWidget || !isInsightWidget(firstWidget)) { + throw new Error("Expected first dashboard widget to be an insight widget."); + } + + Tester.dispatch({ + type: "insights/setInsights", + payload: [ + { + insight: { + ref: firstWidget.insight, + isDraft: true, + }, + }, + ], + }); + + Tester.dispatch(saveDashboard()); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(true); + expect(Tester.emittedEvents().some((event) => event.type === "GDC.DASH/EVT.SAVED")).toBe(false); + }); + + it("should open warning dialog when draft insight exists only on an inactive tab", async () => { + const firstTabId = Tester.select(selectActiveTabLocalIdentifier); + expect(firstTabId).toBeDefined(); + + await Tester.dispatchAndWaitFor(createDashboardTab("Tab 2"), "GDC.DASH/EVT.TAB.SWITCHED"); + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithSection = Tester.state(); + const firstWidget = selectBasicLayout(stateWithSection).sections[0].items[0].widget; + expect(firstWidget && isInsightWidget(firstWidget)).toBe(true); + + if (!firstWidget || !isInsightWidget(firstWidget) || !firstTabId) { + throw new Error( + "Expected first dashboard widget to be an insight widget and first tab id set.", + ); + } + + Tester.dispatch({ + type: "insights/setInsights", + payload: [ + { + insight: { + ref: firstWidget.insight, + isDraft: true, + }, + }, + ], + }); + + await Tester.dispatchAndWaitFor(switchDashboardTab(firstTabId), "GDC.DASH/EVT.TAB.SWITCHED"); + + Tester.dispatch(saveDashboard()); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(true); + expect(Tester.emittedEvents().some((event) => event.type === "GDC.DASH/EVT.SAVED")).toBe(false); + }); + + it("should save draft insight first and then save dashboard after confirmation", async () => { + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithSection = Tester.state(); + const firstWidget = selectBasicLayout(stateWithSection).sections[0].items[0].widget; + expect(firstWidget && isInsightWidget(firstWidget)).toBe(true); + + if (!firstWidget || !isInsightWidget(firstWidget)) { + throw new Error("Expected first dashboard widget to be an insight widget."); + } + + Tester.dispatch({ + type: "insights/setInsights", + payload: [ + { + insight: { + ref: firstWidget.insight, + title: "Draft insight", + isDraft: true, + }, + }, + ], + }); + + const oldInsightRef = firstWidget.insight; + + Tester.dispatch(saveDashboard(undefined, TestCorrelation)); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(true); + expect(Tester.emittedEvents().some((event) => event.type === "GDC.DASH/EVT.SAVED")).toBe(false); + + Tester.dispatch(uiActions.confirmInsightNotSavedDialogSubmit()); + const savedEvent: DashboardSaved = await Tester.waitFor("GDC.DASH/EVT.SAVED", 5000); + + const stateAfterSave = Tester.state(); + const insightWidgetAfterSave = selectBasicLayout(stateAfterSave).sections[0].items[0].widget; + expect(insightWidgetAfterSave && isInsightWidget(insightWidgetAfterSave)).toBe(true); + + if (!insightWidgetAfterSave || !isInsightWidget(insightWidgetAfterSave)) { + throw new Error("Expected first dashboard widget to be an insight widget."); + } + + const insightsAfterSave = selectInsights(stateAfterSave); + + expect(selectIsInsightNotSavedDialogOpen(stateAfterSave)).toBe(false); + expect(savedEvent.correlationId).toBe(TestCorrelation); + expect(areObjRefsEqual(insightWidgetAfterSave.insight, oldInsightRef)).toBe(true); + expect(insightsAfterSave.some((insight) => insight.insight.isDraft)).toBe(false); + }); + + it("should ignore pending save when draft insight save is canceled", async () => { + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithSection = Tester.state(); + const firstWidget = selectBasicLayout(stateWithSection).sections[0].items[0].widget; + expect(firstWidget && isInsightWidget(firstWidget)).toBe(true); + + if (!firstWidget || !isInsightWidget(firstWidget)) { + throw new Error("Expected first dashboard widget to be an insight widget."); + } + + Tester.dispatch({ + type: "insights/setInsights", + payload: [ + { + insight: { + ref: firstWidget.insight, + title: "Draft insight", + isDraft: true, + }, + }, + ], + }); + + Tester.dispatch(saveDashboard(undefined, TestCorrelation)); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(true); + + Tester.dispatch(uiActions.closeInsightNotSavedDialog()); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(false); + expect( + Tester.emittedEvents().some( + (event) => + event.type === "GDC.DASH/EVT.COMMAND.FAILED" && + event.correlationId === TestCorrelation, + ), + ).toBe(false); + expect( + Tester.emittedEvents().some( + (event) => event.type === "GDC.DASH/EVT.SAVED" && event.correlationId === TestCorrelation, + ), + ).toBe(false); + }); + + it("should save dashboard after confirmation when draft insight is used on active and inactive tabs", async () => { + const firstTabId = Tester.select(selectActiveTabLocalIdentifier); + expect(firstTabId).toBeDefined(); + + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithFirstTabSection = Tester.state(); + const firstTabWidget = selectBasicLayout(stateWithFirstTabSection).sections[0].items[0].widget; + + expect(firstTabWidget && isInsightWidget(firstTabWidget)).toBe(true); + + if (!firstTabWidget || !isInsightWidget(firstTabWidget) || !firstTabId) { + throw new Error("Expected first tab widget to be an insight widget and first tab id set."); + } + + const draftInsightRef = firstTabWidget.insight; + + await Tester.dispatchAndWaitFor(createDashboardTab("Tab 2"), "GDC.DASH/EVT.TAB.SWITCHED"); + const secondTabId = Tester.select(selectActiveTabLocalIdentifier); + + await Tester.dispatchAndWaitFor( + addLayoutSection(0, {}, [TestInsightItem], false, TestCorrelation), + "GDC.DASH/EVT.FLUID_LAYOUT.SECTION_ADDED", + ); + + const stateWithSecondTabSection = Tester.state(); + const secondTabWidget = selectBasicLayout(stateWithSecondTabSection).sections[0].items[0].widget; + + expect(secondTabWidget && isInsightWidget(secondTabWidget)).toBe(true); + + if (!secondTabWidget || !isInsightWidget(secondTabWidget) || !secondTabId) { + throw new Error("Expected second tab widget to be an insight widget and second tab id set."); + } + + expect(areObjRefsEqual(secondTabWidget.insight, draftInsightRef)).toBe(true); + + Tester.dispatch({ + type: "insights/setInsights", + payload: [ + { + insight: { + ref: draftInsightRef, + title: "Draft insight", + isDraft: true, + }, + }, + ], + }); + + await Tester.dispatchAndWaitFor(switchDashboardTab(firstTabId), "GDC.DASH/EVT.TAB.SWITCHED"); + + Tester.dispatch(saveDashboard(undefined, TestCorrelation)); + await Tester.wait(200); + + expect(selectIsInsightNotSavedDialogOpen(Tester.state())).toBe(true); + expect(Tester.emittedEvents().some((event) => event.type === "GDC.DASH/EVT.SAVED")).toBe(false); + + Tester.dispatch(uiActions.confirmInsightNotSavedDialogSubmit()); + const savedEvent: DashboardSaved = await Tester.waitFor("GDC.DASH/EVT.SAVED", 5000); + + await Tester.dispatchAndWaitFor(switchDashboardTab(secondTabId), "GDC.DASH/EVT.TAB.SWITCHED"); + + const stateAfterSave = Tester.state(); + const secondTabWidgetAfterSave = selectBasicLayout(stateAfterSave).sections[0].items[0].widget; + + expect(secondTabWidgetAfterSave && isInsightWidget(secondTabWidgetAfterSave)).toBe(true); + + if (!secondTabWidgetAfterSave || !isInsightWidget(secondTabWidgetAfterSave)) { + throw new Error("Expected second tab widget to be an insight widget."); + } + + expect(areObjRefsEqual(secondTabWidgetAfterSave.insight, draftInsightRef)).toBe(true); + expect(selectIsInsightNotSavedDialogOpen(stateAfterSave)).toBe(false); + expect(savedEvent.correlationId).toBe(TestCorrelation); + }); }); }); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.ts index 6a20f594de0..221735d0585 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/dashboard/saveDashboardHandler.ts @@ -3,7 +3,7 @@ import { type AnyAction } from "@reduxjs/toolkit"; import { type BatchAction, batchActions } from "redux-batched-actions"; import { type SagaIterator } from "redux-saga"; -import { type SagaReturnType, call, put, select } from "redux-saga/effects"; +import { type SagaReturnType, call, put, select, take } from "redux-saga/effects"; import { invariant } from "ts-invariant"; import { isUnexpectedResponseError } from "@gooddata/sdk-backend-spi"; @@ -17,7 +17,10 @@ import { type IDashboardTab, type IFilterContext, type IFilterContextDefinition, + type IInsight, type ITempFilterContext, + areObjRefsEqual, + insightRef, isTempFilterContext, } from "@gooddata/sdk-model"; @@ -36,6 +39,11 @@ import { dispatchDashboardEvent } from "../../store/_infra/eventDispatcher.js"; import { accessibleDashboardsActions } from "../../store/accessibleDashboards/index.js"; import { selectBackendCapabilities } from "../../store/backendCapabilities/backendCapabilitiesSelectors.js"; import { selectLocale } from "../../store/config/configSelectors.js"; +import { insightsActions } from "../../store/insights/index.js"; +import { + selectDraftInsightsUsedOnDashboard, + selectInsights, +} from "../../store/insights/insightsSelectors.js"; import { listedDashboardsActions } from "../../store/listedDashboards/index.js"; import { metaActions } from "../../store/meta/index.js"; import { selectDashboardDescriptor, selectPersistedDashboard } from "../../store/meta/metaSelectors.js"; @@ -46,11 +54,20 @@ import { selectDateFilterConfigOverridesByTab } from "../../store/tabs/dateFilte import { selectDateFilterConfigsOverridesByTab } from "../../store/tabs/dateFilterConfigs/dateFilterConfigsSelectors.js"; import { selectFilterContextStatesByTab } from "../../store/tabs/filterContext/filterContextSelectors.js"; import { tabsActions } from "../../store/tabs/index.js"; -import { filterOutCustomWidgets, selectBasicLayoutByTab } from "../../store/tabs/layout/layoutSelectors.js"; +import { + filterOutCustomWidgets, + selectAllInsightWidgetRefsByTab, + selectBasicLayoutByTab, +} from "../../store/tabs/layout/layoutSelectors.js"; import { selectMeasureValueFilterConfigsOverridesByTab } from "../../store/tabs/measureValueFilterConfigs/measureValueFilterConfigsSelectors.js"; import { selectSmartPersistedTabsParameters } from "../../store/tabs/parameters/parametersSelectors.js"; import { selectTabs } from "../../store/tabs/tabsSelectors.js"; import { DEFAULT_TAB_ID, type ITabState } from "../../store/tabs/tabsState.js"; +import { uiActions } from "../../store/ui/index.js"; +import { + selectIsInsightNotSavedDialogOpen, + selectIsInsightNotSavedDialogSaveConfirmed, +} from "../../store/ui/uiSelectors.js"; import { type DashboardContext } from "../../types/commonTypes.js"; import { type ExtendedDashboardWidget } from "../../types/layoutTypes.js"; import { type PromiseFnReturnType } from "../../types/sagas.js"; @@ -95,6 +112,56 @@ type DashboardSaveResult = { created: boolean; }; +function* persistDraftInsights( + ctx: DashboardContext, + cmd: ISaveDashboard, + insights: IInsight[], +): SagaIterator { + for (const draftInsight of insights) { + const persistedInsight = yield call(async () => { + return ctx.backend.workspace(ctx.workspace).insights().createInsight(draftInsight, false); + }); + + const persistedInsightWithDraftState: IInsight = { + ...persistedInsight, + insight: { + ...persistedInsight.insight, + isDraft: false, + }, + }; + + const currentInsights: ReturnType = yield select(selectInsights); + const updatedInsights = currentInsights.map((insight) => { + if (areObjRefsEqual(insightRef(insight), insightRef(draftInsight))) { + return persistedInsightWithDraftState; + } + + return insight; + }); + + const widgetRefsWithDraftInsightByTab: ReturnType< + ReturnType + > = yield select(selectAllInsightWidgetRefsByTab(insightRef(draftInsight))); + + for (const { tabId, widgetRef } of widgetRefsWithDraftInsightByTab) { + yield put( + tabsActions.replaceInsightWidgetInsightForTab({ + tabId, + ref: widgetRef, + insightRef: insightRef(persistedInsightWithDraftState), + properties: undefined, + header: undefined, + undo: { + cmd, + }, + }), + ); + } + + yield put(insightsActions.setInsights(updatedInsights)); + } +} + type DashboardSaveFn = ( ctx: DashboardContext, saveCtx: DashboardSaveContext, @@ -149,7 +216,7 @@ function saveDashboard( return ctx.backend .workspace(ctx.workspace) .dashboards() - .createDashboard(dashboardToSave) + .createDashboard(dashboardToSave, !ctx.config?.isAiMode) .then((dashboard) => ({ dashboard, created: true })); } throw error; @@ -581,10 +648,48 @@ function* save( export function* saveDashboardHandler( ctx: DashboardContext, cmd: ISaveDashboard, -): SagaIterator { +): SagaIterator { try { + const isInsightNotSavedDialogSaveConfirmed: ReturnType< + typeof selectIsInsightNotSavedDialogSaveConfirmed + > = yield select(selectIsInsightNotSavedDialogSaveConfirmed); + + const draftInsightsOnDashboard: ReturnType = yield select( + selectDraftInsightsUsedOnDashboard, + ); + + let shouldPersistDraftInsights = isInsightNotSavedDialogSaveConfirmed; + + if (draftInsightsOnDashboard.length > 0 && !shouldPersistDraftInsights) { + yield put(uiActions.openInsightNotSavedDialog()); + + const draftInsightDialogAction: + | ReturnType + | ReturnType = yield take([ + uiActions.confirmInsightNotSavedDialogSubmit.type, + uiActions.closeInsightNotSavedDialog.type, + ]); + + if (draftInsightDialogAction.type === uiActions.closeInsightNotSavedDialog.type) { + return undefined; + } + + shouldPersistDraftInsights = true; + } + yield put(savingActions.setSavingStart()); + const isInsightNotSavedDialogOpen: ReturnType = + yield select(selectIsInsightNotSavedDialogOpen); + + if (isInsightNotSavedDialogOpen) { + yield put(uiActions.closeInsightNotSavedDialog()); + } + + if (shouldPersistDraftInsights && draftInsightsOnDashboard.length > 0) { + yield call(persistDraftInsights, ctx, cmd, draftInsightsOnDashboard); + } + const persistedDashboard: ReturnType = yield select(selectPersistedDashboard); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/displayFormPlaceholderRef.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/displayFormPlaceholderRef.test.ts new file mode 100644 index 00000000000..3ab93488517 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/displayFormPlaceholderRef.test.ts @@ -0,0 +1,47 @@ +// (C) 2026 GoodData Corporation + +import { describe, expect, it } from "vitest"; + +import { type IAttributeDisplayFormMetadataObject, areObjRefsEqual, idRef } from "@gooddata/sdk-model"; +import { + displayFormPlaceholderRef, + getDashboardAttributeFilterPlaceholdersFromUrl, +} from "@gooddata/sdk-model/internal"; + +const label = { + type: "displayForm", + id: "shared_id", + ref: idRef("shared_id", "displayForm"), + attribute: idRef("attr1", "attribute"), +} as IAttributeDisplayFormMetadataObject; + +// A computed attribute's display form is fabricated client-side and its ref carries the computed +// attribute type; here it deliberately shares its id with the label above. +const computedAttribute = { + type: "displayForm", + id: "shared_id", + ref: idRef("shared_id", "computedAttribute"), + attribute: idRef("shared_id", "computedAttribute"), +} as IAttributeDisplayFormMetadataObject; + +function placeholderRef(url: string) { + return getDashboardAttributeFilterPlaceholdersFromUrl(url)[0].ref; +} + +describe("displayFormPlaceholderRef", () => { + it("matches a placeholder stored before computed attributes existed to the label", () => { + const ref = placeholderRef("https://example.com/?f={dash_attribute_filter_selection(shared_id)}"); + + expect(areObjRefsEqual(displayFormPlaceholderRef(label), ref)).toBe(true); + expect(areObjRefsEqual(displayFormPlaceholderRef(computedAttribute), ref)).toBe(false); + }); + + it("matches a computed attribute placeholder to the computed attribute", () => { + const ref = placeholderRef( + "https://example.com/?f={dash_attribute_filter_selection(computed_attribute/shared_id)}", + ); + + expect(areObjRefsEqual(displayFormPlaceholderRef(computedAttribute), ref)).toBe(true); + expect(areObjRefsEqual(displayFormPlaceholderRef(label), ref)).toBe(false); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/getAttributeIdentifiersReplacements.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/getAttributeIdentifiersReplacements.test.ts new file mode 100644 index 00000000000..10b6e40b948 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/getAttributeIdentifiersReplacements.test.ts @@ -0,0 +1,160 @@ +// (C) 2026 GoodData Corporation + +// @vitest-environment node + +import { runSaga } from "redux-saga"; +import { describe, expect, it, vi } from "vitest"; + +import { type IAttributeDisplayFormMetadataObject, idRef } from "@gooddata/sdk-model"; +import { type IDrillEventIntersectionElement } from "@gooddata/sdk-ui"; + +import { + selectAllCatalogDisplayFormsMap, + selectCatalogDateAttributes, +} from "../../store/catalog/catalogSelectors.js"; +import { type DashboardContext } from "../../types/commonTypes.js"; + +import { getAttributeIdentifiersReplacements } from "./resolveDrillToCustomUrl.js"; + +// A computed attribute's display form is fabricated client-side; its ref carries the computed +// attribute type rather than "displayForm". +const computedAttributeDisplayForm = { + type: "displayForm", + id: "ca1", + ref: idRef("ca1", "computedAttribute"), + attribute: idRef("ca1", "computedAttribute"), + title: "Performance band", +} as IAttributeDisplayFormMetadataObject; + +function intersection(labelId: string, primaryValue: string): IDrillEventIntersectionElement[] { + return [ + { + header: { + attributeHeader: { + identifier: labelId, + ref: idRef(labelId, "computedAttribute"), + localIdentifier: "a1", + uri: "", + name: "Performance band", + formOf: { + identifier: labelId, + ref: idRef(labelId, "computedAttribute"), + uri: "", + name: "Performance band", + }, + }, + attributeHeaderItem: { uri: primaryValue, name: primaryValue }, + }, + } as unknown as IDrillEventIntersectionElement, + ]; +} + +function createContext(elementTitle: string) { + const queryBuilder = { + withLimit: () => queryBuilder, + withOptions: () => queryBuilder, + query: () => Promise.resolve({ items: [{ title: elementTitle }] }), + }; + + const getAttributeDisplayForms = vi.fn().mockResolvedValue([]); + const getComputedAttribute = vi.fn().mockResolvedValue({ + type: "computedAttribute", + id: "ca1", + ref: idRef("ca1", "computedAttribute"), + displayForms: [computedAttributeDisplayForm], + }); + + const backend = { + capabilities: { supportsElementUris: false }, + workspace: () => ({ + attributes: () => ({ + getAttributeDisplayForms, + elements: () => ({ forDisplayForm: () => queryBuilder }), + }), + computedAttributes: () => ({ getComputedAttribute }), + }), + }; + + return { + ctx: { workspace: "ws", backend } as unknown as DashboardContext, + getAttributeDisplayForms, + getComputedAttribute, + }; +} + +/** + * @param catalogHit - whether the catalog carries the computed attribute's display form + */ +function run(saga: any, catalogHit: boolean, ...args: any[]) { + const displayFormsMap = { + get: (ref: any) => + catalogHit && ref?.identifier === "ca1" ? computedAttributeDisplayForm : undefined, + }; + + return runSaga( + { + dispatch: () => {}, + getState: () => ({}), + channel: undefined as any, + effectMiddlewares: [ + (next: any) => (effect: any) => { + if (effect?.type === "SELECT") { + if (effect.payload.selector === selectAllCatalogDisplayFormsMap) { + return next({ + ...effect, + payload: { ...effect.payload, selector: () => displayFormsMap }, + }); + } + if (effect.payload.selector === selectCatalogDateAttributes) { + return next({ ...effect, payload: { ...effect.payload, selector: () => [] } }); + } + } + return next(effect); + }, + ], + }, + saga, + ...args, + ).toPromise(); +} + +const URL = "https://example.com/?q={attribute_title(computed_attribute/ca1)}"; +const EXPECTED = [{ toBeReplaced: "{attribute_title(computed_attribute/ca1)}", replacement: "High" }]; + +describe("getAttributeIdentifiersReplacements", () => { + it("resolves a computed attribute placeholder from the catalog", async () => { + const { ctx, getAttributeDisplayForms, getComputedAttribute } = createContext("High"); + + const replacements = await run( + getAttributeIdentifiersReplacements, + true, + URL, + intersection("ca1", "high"), + ctx, + ); + + expect(replacements).toEqual(EXPECTED); + // the catalog already had it, so nothing needs loading + expect(getAttributeDisplayForms).not.toHaveBeenCalled(); + expect(getComputedAttribute).not.toHaveBeenCalled(); + }); + + it("resolves a computed attribute the catalog does not carry", async () => { + // A computed attribute excluded from the catalog by tags, or created after the dashboard was + // initialised, still turns up in the drill intersection and has to resolve. + const { ctx, getAttributeDisplayForms, getComputedAttribute } = createContext("High"); + + const replacements = await run( + getAttributeIdentifiersReplacements, + false, + URL, + intersection("ca1", "high"), + ctx, + ); + + expect(replacements).toEqual(EXPECTED); + // it must be loaded through the computedAttributes service, never asked for as a label + expect(getComputedAttribute).toHaveBeenCalledWith(idRef("ca1", "computedAttribute")); + expect(getAttributeDisplayForms).not.toHaveBeenCalled(); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/resolveDrillToCustomUrl.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/resolveDrillToCustomUrl.ts index 92b1f4f2d7a..1cb36df81f2 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/resolveDrillToCustomUrl.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/drill/resolveDrillToCustomUrl.ts @@ -22,7 +22,6 @@ import { dashboardFilterObjRef, filterAttributeElements, filterObjRef, - idRef, insightFilters as insightDefinitionFilters, insightId, isArbitraryAttributeFilter, @@ -44,6 +43,7 @@ import { } from "@gooddata/sdk-model"; import { type IDrillToUrlPlaceholder, + displayFormPlaceholderRef, getAttributeIdentifiersPlaceholdersFromUrl, getDashboardAttributeFilterPlaceholdersFromUrl, getDashboardMeasureValueFilterPlaceholdersFromUrl, @@ -75,6 +75,7 @@ import { selectAnalyticalWidgetByRef } from "../../store/tabs/layout/layoutSelec import { type DashboardContext } from "../../types/commonTypes.js"; import { DRILL_TO_URL_PLACEHOLDER } from "../../types/drillTypes.js"; import { type PromiseFnReturnType } from "../../types/sagas.js"; +import { resolveDisplayFormMetadata } from "../../utils/displayFormResolver.js"; import { dashboardMeasureValueFilterMatchesIdentifier, insightMeasureValueFilterMatchesIdentifier, @@ -89,13 +90,13 @@ interface IDrillToUrlPlaceholderReplacement { } interface IDrillToUrlElement { - identifier: string; + ref: ObjRef; elementTitle: string | null; } export function* loadElementTitle( dfRef: ObjRef, - dfIdentifier: string, + placeholderRef: ObjRef, attrElementUri: string, ctx: DashboardContext, ): SagaIterator { @@ -107,7 +108,7 @@ export function* loadElementTitle( ctx, ); return { - identifier: dfIdentifier, + ref: placeholderRef, elementTitle, }; } @@ -276,7 +277,7 @@ export function* loadAttributeElementsForDrillIntersection( } acc.push({ - identifier: displayForm.id, + ref: displayFormPlaceholderRef(displayForm), elementTitle: attributeHeaderItem.uri, }); return acc; @@ -284,8 +285,6 @@ export function* loadAttributeElementsForDrillIntersection( const loadedElement: IDrillToUrlElement[] = yield all( displayFormForValueLoad.reduce((acc: CallEffect[], displayForm) => { - const { id: dfIdentifier, ref: dfRef } = displayForm; - const attributeHeaderItem = findDrillIntersectionAttributeHeaderItem( drillIntersectionElements, displayForm, @@ -294,7 +293,15 @@ export function* loadAttributeElementsForDrillIntersection( return acc; } - acc.push(call(loadElementTitle, dfRef, dfIdentifier, attributeHeaderItem.uri, ctx)); + acc.push( + call( + loadElementTitle, + displayForm.ref, + displayFormPlaceholderRef(displayForm), + attributeHeaderItem.uri, + ctx, + ), + ); return acc; }, []), @@ -306,14 +313,6 @@ export function* loadAttributeElementsForDrillIntersection( const encodeParameterIfSet = (parameter: string | undefined | null): string | undefined | null => parameter === null || parameter === undefined ? parameter : encodeURIComponent(parameter); -export function getAttributeDisplayForms( - projectId: string, - objRefs: ObjRef[], - ctx: DashboardContext, -): Promise { - return ctx.backend.workspace(projectId).attributes().getAttributeDisplayForms(objRefs); -} - export function* getAttributeIdentifiersReplacements( url: string, drillIntersectionElements: IDrillEventIntersectionElement[], @@ -325,24 +324,36 @@ export function* getAttributeIdentifiersReplacements( return []; } - const displayForms: PromiseFnReturnType = yield call( - getAttributeDisplayForms, - ctx.workspace, - attributeIdentifiersPlaceholders.map((placeholder) => idRef(placeholder.identifier)), + // The placeholder refs carry their own type, so this resolves a label through the labels API and a + // computed attribute through the computedAttributes service - the labels API cannot describe one. + // It reads the catalog first but falls back to loading, so a computed attribute that the catalog + // does not carry (excluded by tags, or created after the dashboard was initialised) still resolves. + const { resolved, missing }: SagaReturnType = yield call( + resolveDisplayFormMetadata, ctx, + attributeIdentifiersPlaceholders.map(({ ref }) => ref), ); + if (missing.length > 0) { + console.warn( + "Drill to custom URL: no metadata found for attributes referenced by attribute_title placeholders; those placeholders cannot be resolved.", + { missing }, + ); + } + const elements: SagaReturnType = yield call( loadAttributeElementsForDrillIntersection, drillIntersectionElements, - displayForms, + [...resolved.values()], ctx, ); return attributeIdentifiersPlaceholders.map( - ({ placeholder: toBeReplaced, identifier, toBeEncoded }): IDrillToUrlPlaceholderReplacement => { - const elementTitle = elements.find( - (element: IDrillToUrlElement) => element.identifier === identifier, + ({ placeholder: toBeReplaced, ref, toBeEncoded }): IDrillToUrlPlaceholderReplacement => { + // Matched by reference, not by identifier alone: a computed attribute and a label can + // share an identifier, and they must not resolve to each other's element titles. + const elementTitle = elements.find((element: IDrillToUrlElement) => + areObjRefsEqual(element.ref, ref), )?.elementTitle; const replacement = toBeEncoded ? encodeParameterIfSet(elementTitle) : elementTitle; @@ -367,7 +378,7 @@ function* resolveDashboardAttributeFilterReplacement( return false; } const df = catalogDisplayForms.get(dashboardAttributeFilterItemDisplayForm(filter)); - return df && areObjRefsEqual(idRef(df.id), ref); + return df && areObjRefsEqual(displayFormPlaceholderRef(df), ref); }); if (textFilter && isDashboardTextAttributeFilter(textFilter)) { @@ -384,7 +395,7 @@ function* resolveDashboardAttributeFilterReplacement( let usedFilter = standardFilters.find((filter) => { const df = catalogDisplayForms.get(filter.attributeFilter.displayForm); - return df && areObjRefsEqual(idRef(df.id), ref); + return df && areObjRefsEqual(displayFormPlaceholderRef(df), ref); }); let attributeElementsValues = []; @@ -570,7 +581,7 @@ export function* getInsightAttributeFilterReplacements( const usedFilter = insightAttributeFilters.find((filter) => { const filterRef = filterObjRef(filter); const df = filterRef && catalogDisplayForms.get(filterRef); - return df && areObjRefsEqual(idRef(df.id), ref); + return df && areObjRefsEqual(displayFormPlaceholderRef(df), ref); }); let parsedFilter: string | undefined; diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.test.ts new file mode 100644 index 00000000000..4f714bd0d29 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.test.ts @@ -0,0 +1,59 @@ +// (C) 2026 GoodData Corporation + +// @vitest-environment node + +import { describe, expect, it } from "vitest"; + +import { idRef } from "@gooddata/sdk-model"; + +import { removeAttributeFilters } from "../../../commands/filters.js"; +import { tabsActions } from "../../../store/tabs/index.js"; +import { type DashboardContext } from "../../../types/commonTypes.js"; + +import { removeAttributeFiltersHandler } from "./removeAttributeFiltersHandler.js"; + +const ctx = { workspace: "ws" } as DashboardContext; + +const filterItem = (localIdentifier: string, parents: string[] = []) => ({ + attributeFilter: { + displayForm: idRef(localIdentifier), + localIdentifier, + filterElementsBy: parents.map((filterLocalIdentifier) => ({ + filterLocalIdentifier, + over: { attributes: [] }, + })), + }, +}); + +describe("removeAttributeFiltersHandler", () => { + it("leaves a child with no reference to any of the parents removed with it", () => { + const generator = removeAttributeFiltersHandler( + ctx, + removeAttributeFilters(["parent-a", "parent-b"]), + ); + generator.next(); + + const effects = []; + let step = generator.next([ + filterItem("parent-a"), + filterItem("parent-b"), + filterItem("child", ["parent-a", "parent-b"]), + ]); + while (!step.done) { + effects.push(step.value); + step = generator.next([]); + } + + const parentUpdates = effects + .map((effect) => (effect as any)?.payload?.action?.payload) + .filter(Array.isArray) + .flat() + .filter((action) => action.type === tabsActions.setAttributeFilterParents.type) + .map((action) => action.payload); + + expect(parentUpdates).toEqual([ + { filterLocalId: "child", parentFilters: [] }, + { filterLocalId: "child", parentFilters: [] }, + ]); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.ts index 3e70fe47f75..85fc5806e15 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/attributeFilter/removeAttributeFiltersHandler.ts @@ -82,8 +82,9 @@ export function* removeAttributeFiltersHandler( ...affectedChildren.map((item) => tabsActions.setAttributeFilterParents({ filterLocalId: dashboardAttributeFilterItemLocalIdentifier(item)!, + // affectedChildren is a pre-loop snapshot, so drop every id being removed parentFilters: (dashboardAttributeFilterItemFilterElementsBy(item) ?? []).filter( - (parent) => parent.filterLocalIdentifier !== removedLocalId, + (parent) => !filterLocalIds.includes(parent.filterLocalIdentifier), ), }), ), diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.test.ts new file mode 100644 index 00000000000..ff967798b58 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.test.ts @@ -0,0 +1,71 @@ +// (C) 2026 GoodData Corporation + +// @vitest-environment node + +import { call, select } from "redux-saga/effects"; +import { describe, expect, it } from "vitest"; + +import { type FilterContextItem, idRef } from "@gooddata/sdk-model"; + +import { + type IRemoveDateFilters, + removeAttributeFilters, + removeMeasureValueFilters, + removeRestrictedFilters, +} from "../../commands/filters.js"; +import { selectReportedRestrictedDashboardFilters } from "../../store/filtering/dashboardFilterSelectors.js"; +import { type DashboardContext } from "../../types/commonTypes.js"; + +import { removeAttributeFiltersHandler } from "./attributeFilter/removeAttributeFiltersHandler.js"; +import { removeDateFiltersHandler } from "./dateFilter/removeDateFiltersHandler.js"; +import { removeMeasureValueFilterHandler } from "./measureValueFilter/removeMeasureValueFilterHandler.js"; +import { removeRestrictedFiltersHandler } from "./removeRestrictedFiltersHandler.js"; + +const ctx = { workspace: "ws" } as DashboardContext; +const dataSet = idRef("dataset", "dataSet"); + +const asFilter = (filter: unknown) => filter as FilterContextItem; +const attributeFilter = asFilter({ + attributeFilter: { displayForm: idRef("label", "displayForm"), localIdentifier: "attribute" }, +}); +const dateFilter = asFilter({ + dateFilter: { type: "relative", dataSet, localIdentifier: "date" }, +}); +const measureValueFilter = asFilter({ + dashboardMeasureValueFilter: { measure: idRef("metric"), localIdentifier: "measure-value" }, +}); + +describe("removeRestrictedFiltersHandler", () => { + it("removes each reported type through its own handler, which cleans up after it", () => { + const generator = removeRestrictedFiltersHandler(ctx, removeRestrictedFilters("corr")); + + expect(generator.next().value).toEqual(select(selectReportedRestrictedDashboardFilters)); + + const restricted = [attributeFilter, dateFilter, measureValueFilter]; + expect(generator.next(restricted).value).toEqual( + call(removeAttributeFiltersHandler, ctx, removeAttributeFilters(["attribute"], "corr")), + ); + + expect(generator.next().value).toEqual( + call(removeDateFiltersHandler, ctx, { + type: "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.REMOVE", + correlationId: "corr", + payload: { dataSets: [dataSet] }, + } as IRemoveDateFilters), + ); + + expect(generator.next().value).toEqual( + call(removeMeasureValueFilterHandler, ctx, removeMeasureValueFilters(["measure-value"], "corr")), + ); + + expect(generator.next().done).toBe(true); + }); + + it("asks for no removal when nothing is restricted", () => { + const generator = removeRestrictedFiltersHandler(ctx, removeRestrictedFilters()); + + generator.next(); + + expect(generator.next([]).done).toBe(true); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.ts new file mode 100644 index 00000000000..6e99034cd7c --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/filterContext/removeRestrictedFiltersHandler.ts @@ -0,0 +1,75 @@ +// (C) 2026 GoodData Corporation + +import { type SagaIterator } from "redux-saga"; +import { call, select } from "redux-saga/effects"; + +import { + isDashboardAttributeFilterItem, + isDashboardDateFilterWithDimension, + isDashboardMeasureValueFilter, +} from "@gooddata/sdk-model"; + +import { + type IRemoveDateFilters, + type IRemoveRestrictedFilters, + removeAttributeFilters, + removeMeasureValueFilters, +} from "../../commands/filters.js"; +import { + filterLocalIdentifiers, + selectReportedRestrictedDashboardFilters, +} from "../../store/filtering/dashboardFilterSelectors.js"; +import { type DashboardContext } from "../../types/commonTypes.js"; + +import { removeAttributeFiltersHandler } from "./attributeFilter/removeAttributeFiltersHandler.js"; +import { removeDateFiltersHandler } from "./dateFilter/removeDateFiltersHandler.js"; +import { removeMeasureValueFilterHandler } from "./measureValueFilter/removeMeasureValueFilterHandler.js"; + +/** + * Removes the filters the active tab's notice reports. Each type goes through its own removal handler, + * which is what cleans up the dependencies, configs and widget ignore lists the filter leaves behind. + */ +export function* removeRestrictedFiltersHandler( + ctx: DashboardContext, + cmd: IRemoveRestrictedFilters, +): SagaIterator { + const restricted: ReturnType = yield select( + selectReportedRestrictedDashboardFilters, + ); + + const attributeFilterLocalIds = [ + ...filterLocalIdentifiers(restricted.filter(isDashboardAttributeFilterItem)), + ]; + const measureValueFilterLocalIds = [ + ...filterLocalIdentifiers(restricted.filter(isDashboardMeasureValueFilter)), + ]; + const dateFilterDataSets = restricted + .filter(isDashboardDateFilterWithDimension) + .map((filter) => filter.dateFilter.dataSet!); + + if (attributeFilterLocalIds.length) { + yield call( + removeAttributeFiltersHandler, + ctx, + removeAttributeFilters(attributeFilterLocalIds, cmd.correlationId), + ); + } + + if (dateFilterDataSets.length) { + // there is no factory taking more than one data set + const command: IRemoveDateFilters = { + type: "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.REMOVE", + correlationId: cmd.correlationId, + payload: { dataSets: dateFilterDataSets }, + }; + yield call(removeDateFiltersHandler, ctx, command); + } + + if (measureValueFilterLocalIds.length) { + yield call( + removeMeasureValueFilterHandler, + ctx, + removeMeasureValueFilters(measureValueFilterLocalIds, cmd.correlationId), + ); + } +} diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/index.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/index.ts index d083227717d..1ebe2eb1310 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/index.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/index.ts @@ -75,6 +75,7 @@ import { changeMeasureValueFilterDimensionalityHandler } from "./filterContext/m import { changeMeasureValueFilterTitleHandler } from "./filterContext/measureValueFilter/changeMeasureValueFilterTitleHandler.js"; import { moveMeasureValueFilterHandler } from "./filterContext/measureValueFilter/moveMeasureValueFilterHandler.js"; import { removeMeasureValueFilterHandler } from "./filterContext/measureValueFilter/removeMeasureValueFilterHandler.js"; +import { removeRestrictedFiltersHandler } from "./filterContext/removeRestrictedFiltersHandler.js"; import { addLayoutSectionHandler } from "./layout/addLayoutSectionHandler.js"; import { addSectionItemsHandler } from "./layout/addSectionItemsHandler.js"; import { changeLayoutSectionHeaderHandler } from "./layout/changeLayoutSectionHeaderHandler.js"; @@ -186,6 +187,7 @@ export const DefaultCommandHandlers: { "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.MOVE": moveDateFilterHandler, "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.ADD": addMeasureValueFilterHandler, "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.REMOVE": removeMeasureValueFilterHandler, + "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE": removeRestrictedFiltersHandler, "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.MOVE": moveMeasureValueFilterHandler, "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.CHANGE_CONDITION": changeMeasureValueFilterConditionHandler, diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/drillDisplayFormIdentifiers.test.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/drillDisplayFormIdentifiers.test.ts new file mode 100644 index 00000000000..436bbdd2278 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/drillDisplayFormIdentifiers.test.ts @@ -0,0 +1,50 @@ +// (C) 2026 GoodData Corporation + +import { describe, expect, it } from "vitest"; + +import { type IDrillToCustomUrl, idRef } from "@gooddata/sdk-model"; + +import { + extractDashboardFilterDisplayFormIdentifiers, + extractDisplayFormIdentifiers, + extractInsightFilterDisplayFormIdentifiers, +} from "./insightDrillDefinitionUtils.js"; + +/** + * These refs are fed to resolveDisplayFormMetadata when a drill is saved. A computed attribute + * rebuilt as a display form resolves as missing there, and the drill is rejected - so every extractor + * has to keep the type the placeholder carried. + */ +function drillWithUrl(url: string): IDrillToCustomUrl { + return { + type: "drillToCustomUrl", + transition: "new-window", + origin: { type: "drillFromAttribute", attribute: idRef("label1", "displayForm") }, + target: { url }, + } as IDrillToCustomUrl; +} + +describe("drill display form identifier extraction", () => { + it.each([ + [ + "attribute_title", + extractDisplayFormIdentifiers, + "https://e.com/?a={attribute_title(computed_attribute/ca1)}&b={attribute_title(label1)}", + ], + [ + "dash_attribute_filter_selection", + extractDashboardFilterDisplayFormIdentifiers, + "https://e.com/?a={dash_attribute_filter_selection(computed_attribute/ca1)}&b={dash_attribute_filter_selection(label1)}", + ], + [ + "attribute_filter_selection", + extractInsightFilterDisplayFormIdentifiers, + "https://e.com/?a={attribute_filter_selection(computed_attribute/ca1)}&b={attribute_filter_selection(label1)}", + ], + ])("keeps the computed attribute type for %s placeholders", (_name, extract, url) => { + expect(extract([drillWithUrl(url)])).toEqual([ + idRef("ca1", "computedAttribute"), + idRef("label1", "displayForm"), + ]); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/insightDrillDefinitionUtils.ts b/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/insightDrillDefinitionUtils.ts index 64dee061c40..19eaf61a7fa 100644 --- a/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/insightDrillDefinitionUtils.ts +++ b/libs/sdk-ui-dashboard/src/model/commandHandlers/widgets/validation/insightDrillDefinitionUtils.ts @@ -161,9 +161,9 @@ export function extractDisplayFormIdentifiers(drillDefinitions: InsightDrillDefi .flatMap((drillItem) => { if (isDrillToCustomUrl(drillItem)) { const params = getAttributeIdentifiersPlaceholdersFromUrl(drillItem.target.url); - // normalize ref take the value from state ... - // md object has to be identifier - return params.map((param) => idRef(param.identifier, "displayForm")); + // the placeholder's own ref already names the object by identifier and carries its + // type, which a computed attribute needs to resolve as anything other than a label + return params.map((param) => param.ref); } else { return [drillItem.target.displayForm, drillItem.target.hyperlinkDisplayForm]; } @@ -177,11 +177,7 @@ export function extractDashboardFilterDisplayFormIdentifiers( const dashboardAttributeFilterPlaceholdersFromUrl = getDashboardAttributeFilterPlaceholdersFromUrl( drillItem.target.url, ); - // normalize ref take the value from state ... - // md object has to be identifier - return dashboardAttributeFilterPlaceholdersFromUrl.map((param) => - idRef(param.identifier, "displayForm"), - ); + return dashboardAttributeFilterPlaceholdersFromUrl.map((param) => param.ref); }); } @@ -192,9 +188,7 @@ export function extractInsightFilterDisplayFormIdentifiers( const insightAttributeFilterPlaceholders = getInsightAttributeFilterPlaceholdersFromUrl( drillItem.target.url, ); - // normalize ref take the value from state ... - // md object has to be identifier - return insightAttributeFilterPlaceholders.map((param) => idRef(param.identifier, "displayForm")); + return insightAttributeFilterPlaceholders.map((param) => param.ref); }); } diff --git a/libs/sdk-ui-dashboard/src/model/commands/base.ts b/libs/sdk-ui-dashboard/src/model/commands/base.ts index d38d97b83ea..89ff4d886dd 100644 --- a/libs/sdk-ui-dashboard/src/model/commands/base.ts +++ b/libs/sdk-ui-dashboard/src/model/commands/base.ts @@ -44,6 +44,7 @@ export type DashboardCommandType = | "GDC.DASH/CMD.FILTER_CONTEXT.DATE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.ADD" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.REMOVE" + | "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.MOVE" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.CHANGE_CONDITION" | "GDC.DASH/CMD.FILTER_CONTEXT.MEASURE_VALUE_FILTER.SET_DIMENSIONALITY" diff --git a/libs/sdk-ui-dashboard/src/model/commands/filters.ts b/libs/sdk-ui-dashboard/src/model/commands/filters.ts index 5d73b8fedfd..d25032eb25d 100644 --- a/libs/sdk-ui-dashboard/src/model/commands/filters.ts +++ b/libs/sdk-ui-dashboard/src/model/commands/filters.ts @@ -2218,3 +2218,30 @@ export function setMeasureValueFilterTitle( }, }; } + +/** + * Removes the filters the active tab's filter bar reports as not applied. + * + * @remarks + * Filters the author hid are kept: they are already left out of every execution. Saving the dashboard + * makes the removal permanent, for everyone. + * + * @beta + */ +export interface IRemoveRestrictedFilters extends IDashboardCommand { + readonly type: "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE"; +} + +/** + * Creates the {@link IRemoveRestrictedFilters} command. + * + * @param correlationId - specify correlation id to use for this command. this will be included in all + * events that will be emitted during the command processing + * @beta + */ +export function removeRestrictedFilters(correlationId?: string): IRemoveRestrictedFilters { + return { + type: "GDC.DASH/CMD.FILTER_CONTEXT.RESTRICTED_FILTERS.REMOVE", + correlationId, + }; +} diff --git a/libs/sdk-ui-dashboard/src/model/commands/index.ts b/libs/sdk-ui-dashboard/src/model/commands/index.ts index ded4eb30286..28d26199955 100644 --- a/libs/sdk-ui-dashboard/src/model/commands/index.ts +++ b/libs/sdk-ui-dashboard/src/model/commands/index.ts @@ -58,6 +58,7 @@ import { type IRemoveAttributeFilters, type IRemoveDateFilters, type IRemoveMeasureValueFilters, + type IRemoveRestrictedFilters, type IResetFilterContextWorkingSelection, type ISaveFilterView, type ISetAttributeFilterDependentDateFilters, @@ -261,6 +262,7 @@ export type DashboardCommands = | IAddDateFilter | IAddMeasureValueFilter | IRemoveMeasureValueFilters + | IRemoveRestrictedFilters | IRemoveDateFilters | IMoveDateFilter | IChangeMeasureValueFilterCondition diff --git a/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.test.ts b/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.test.ts index 08e70426dfa..38c7c356b5b 100644 --- a/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.test.ts +++ b/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.test.ts @@ -9,6 +9,7 @@ import { type FilterContextItem, idRef } from "@gooddata/sdk-model"; import { selectExecutableDashboardFilters, + selectReportedRestrictedDashboardFilters, selectRestrictedDashboardFilterCount, selectRestrictedDashboardFilterLocalIdentifiers, selectRestrictedDashboardFilterLocalIdentifiersForTab, @@ -59,9 +60,9 @@ describe("restricted filter selectors", () => { expect([...forTab("unknown-tab")]).toEqual([]); }); - it("counts the restricted filters the viewer would otherwise see, but not the hidden ones", () => { - const count = (attributeFilterConfigs: { localIdentifier: string; mode: string }[]) => - combinerOf(selectRestrictedDashboardFilterCount)( + it("reports the restricted filters the viewer would otherwise see, but not the hidden ones", () => { + const reported = (attributeFilterConfigs: { localIdentifier: string; mode: string }[]) => + combinerOf(selectReportedRestrictedDashboardFilters)( [forbiddenFilter], undefined, [], @@ -69,17 +70,17 @@ describe("restricted filter selectors", () => { [], ); - expect(count([])).toBe(1); - expect(count([{ localIdentifier: "forbidden", mode: "active" }])).toBe(1); - expect(count([{ localIdentifier: "forbidden", mode: "hidden" }])).toBe(0); + expect(reported([])).toEqual([forbiddenFilter]); + expect(reported([{ localIdentifier: "forbidden", mode: "active" }])).toEqual([forbiddenFilter]); + expect(reported([{ localIdentifier: "forbidden", mode: "hidden" }])).toEqual([]); }); - it("counts a restricted measure value filter unless the author hid that one too", () => { + it("reports a restricted measure value filter unless the author hid that one too", () => { const forbiddenMeasureValueFilter = { dashboardMeasureValueFilter: { measure: idRef("metric"), localIdentifier: "forbidden-mvf" }, } as FilterContextItem; - const count = (measureValueFilterConfigs: { localIdentifier: string; mode: string }[]) => - combinerOf(selectRestrictedDashboardFilterCount)( + const reported = (measureValueFilterConfigs: { localIdentifier: string; mode: string }[]) => + combinerOf(selectReportedRestrictedDashboardFilters)( [forbiddenMeasureValueFilter], undefined, [], @@ -87,7 +88,12 @@ describe("restricted filter selectors", () => { measureValueFilterConfigs, ); - expect(count([])).toBe(1); - expect(count([{ localIdentifier: "forbidden-mvf", mode: "hidden" }])).toBe(0); + expect(reported([])).toEqual([forbiddenMeasureValueFilter]); + expect(reported([{ localIdentifier: "forbidden-mvf", mode: "hidden" }])).toEqual([]); + }); + + it("counts what it reports", () => { + expect(combinerOf(selectRestrictedDashboardFilterCount)([forbiddenFilter])).toBe(1); + expect(combinerOf(selectRestrictedDashboardFilterCount)([])).toBe(0); }); }); diff --git a/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.ts index 15a82ff5a81..b0d0fb060da 100644 --- a/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/filtering/dashboardFilterSelectors.ts @@ -53,7 +53,7 @@ const commonDateFilter: FilterContextItem = newAllTimeDashboardDateFilter( generateDateFilterLocalIdentifier(0), ); -function filterLocalIdentifiers(filters: FilterContextItem[]): ReadonlySet { +export function filterLocalIdentifiers(filters: FilterContextItem[]): ReadonlySet { return new Set( filters .map(dashboardFilterLocalIdentifier) @@ -76,36 +76,46 @@ const selectRestrictedDashboardFilters: DashboardSelector = export const selectRestrictedDashboardFilterLocalIdentifiers: DashboardSelector> = createSelector(selectRestrictedDashboardFilters, filterLocalIdentifiers); +/** + * Selects the filters the current user is forbidden to access that the filter bar reports: the ones + * the author hid are left out, so they stay in the filter context and out of every execution. + */ +export const selectReportedRestrictedDashboardFilters: DashboardSelector = + createSelector( + selectRestrictedDashboardFilters, + selectDateFilterConfigOverrides, + selectDateFilterConfigsOverrides, + selectAttributeFilterConfigsOverrides, + selectMeasureValueFilterConfigsOverrides, + ( + restricted, + commonDateFilterConfig, + dateFilterWithDimensionConfigs, + attributeFilterConfigs, + measureValueFilterConfigs, + ) => + restricted.filter((filter) => + // isFilterContextItemHidden covers attribute and date filters only + isDashboardMeasureValueFilter(filter) + ? measureValueFilterConfigs.find( + (config) => config.localIdentifier === dashboardFilterLocalIdentifier(filter), + )?.mode !== "hidden" + : !isFilterContextItemHidden(filter, { + commonDateFilterConfig, + dateFilterWithDimensionConfigs, + attributeFilterConfigs, + }), + ), + ); + /** * Selects how many filters the current user is forbidden to access, excluding those the author hid. * * @alpha */ export const selectRestrictedDashboardFilterCount: DashboardSelector = createSelector( - selectRestrictedDashboardFilters, - selectDateFilterConfigOverrides, - selectDateFilterConfigsOverrides, - selectAttributeFilterConfigsOverrides, - selectMeasureValueFilterConfigsOverrides, - ( - restricted, - commonDateFilterConfig, - dateFilterWithDimensionConfigs, - attributeFilterConfigs, - measureValueFilterConfigs, - ) => - restricted.filter((filter) => - // isFilterContextItemHidden covers attribute and date filters only - isDashboardMeasureValueFilter(filter) - ? measureValueFilterConfigs.find( - (config) => config.localIdentifier === dashboardFilterLocalIdentifier(filter), - )?.mode !== "hidden" - : !isFilterContextItemHidden(filter, { - commonDateFilterConfig, - dateFilterWithDimensionConfigs, - attributeFilterConfigs, - }), - ).length, + selectReportedRestrictedDashboardFilters, + (reported) => reported.length, ); /** diff --git a/libs/sdk-ui-dashboard/src/model/store/insights/insightsSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/insights/insightsSelectors.ts index d580dd8f621..fb16064d451 100644 --- a/libs/sdk-ui-dashboard/src/model/store/insights/insightsSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/insights/insightsSelectors.ts @@ -16,6 +16,7 @@ import { insightAttributes, insightMeasures, insightRef, + isDrillToInsight, measureAlias, measureItem, measureLocalId, @@ -32,7 +33,7 @@ import { selectCatalogMeasures, } from "../catalog/catalogSelectors.js"; import { selectLocale } from "../config/configSelectors.js"; -import { selectWidgetByRef } from "../tabs/layout/layoutSelectors.js"; +import { selectAllTabsInsightWidgetContexts, selectWidgetByRef } from "../tabs/layout/layoutSelectors.js"; import { type DashboardSelector, type DashboardState } from "../types.js"; import { insightsAdapter } from "./insightsEntityAdapter.js"; @@ -63,6 +64,35 @@ export const selectInsightRefs: DashboardSelector = createSelector(sel return insights.map(insightRef); }); +const addUniqueInsightRef = (insightRefs: ObjRef[], ref: ObjRef) => { + if (!insightRefs.some((existingRef) => areObjRefsEqual(existingRef, ref))) { + insightRefs.push(ref); + } +}; + +/** + * Selects refs of insights referenced by dashboard state (widgets, visualization switcher visualizations, and drills). + * + * @internal + */ +export const selectDashboardInsightRefs: DashboardSelector = createSelector( + selectAllTabsInsightWidgetContexts, + (contexts) => { + const insightRefs: ObjRef[] = []; + + contexts.forEach(({ widget }) => { + addUniqueInsightRef(insightRefs, widget.insight); + widget.drills.forEach((drill) => { + if (isDrillToInsight(drill)) { + addUniqueInsightRef(insightRefs, drill.target); + } + }); + }); + + return insightRefs; + }, +); + /** * Selects all insights and returns them in a mapping of obj ref to the insight object. * @@ -76,6 +106,45 @@ export const selectInsightsMap: DashboardSelector> = createS }, ); +/** + * Selects insights referenced by dashboard state (widgets, visualization switcher visualizations, and drills). + * + * @internal + */ +export const selectInsightsUsedOnDashboard: DashboardSelector = createSelector( + selectDashboardInsightRefs, + selectInsightsMap, + selectInsights, + (insightRefs, insightsMap, insights) => { + return insightRefs + .map((ref) => { + return ( + insightsMap.get(ref) ?? + insights.find((currentInsight) => { + return currentInsight.insight.ref + ? areObjRefsEqual(currentInsight.insight.ref, ref) + : false; + }) + ); + }) + .filter((insight): insight is IInsight => { + return Boolean(insight); + }); + }, +); + +/** + * Selects draft insights referenced by dashboard state (widgets, visualization switcher visualizations, and drills). + * + * @internal + */ +export const selectDraftInsightsUsedOnDashboard: DashboardSelector = createSelector( + selectInsightsUsedOnDashboard, + (insights) => { + return insights.filter((insight) => insight.insight.isDraft); + }, +); + /** * Selects insight used on a dashboard by its ref. * diff --git a/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutReducers.ts b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutReducers.ts index 322fd1cf8bf..d9e110ebcdb 100644 --- a/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutReducers.ts +++ b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutReducers.ts @@ -56,7 +56,7 @@ import { } from "../../../types/layoutTypes.js"; import { type IWidgetDescription, type IWidgetHeader } from "../../../types/widgetTypes.js"; import { addArrayElements, removeArrayElement } from "../../../utils/arrayOps.js"; -import { resetUndoReducer, undoReducer, withUndo } from "../../_infra/undoEnhancer.js"; +import { type IUndoPayload, resetUndoReducer, undoReducer, withUndo } from "../../_infra/undoEnhancer.js"; import { type ITabsState } from "../tabsState.js"; import { type ILayoutState, layoutInitialState } from "./layoutState.js"; @@ -868,6 +868,12 @@ type ReplaceWidgetInsight = { newSize?: IVisualizationSizeInfo; }; +type ReplaceWidgetInsightWithUndo = ReplaceWidgetInsight & IUndoPayload; + +type ReplaceWidgetInsightForTab = ReplaceWidgetInsightWithUndo & { + tabId: string; +}; + const replaceInsightWidgetInsightCore: CoreLayoutReducer = (state, action) => { invariant(state.layout); @@ -894,6 +900,29 @@ const replaceInsightWidgetInsightCore: CoreLayoutReducer = // Wrap with undo and adapt to TabsState const replaceInsightWidgetInsight = adaptLayoutReducer(withUndo(replaceInsightWidgetInsightCore)); + +const replaceInsightWidgetInsightForTab: LayoutReducer = (state, action) => { + if (!state.tabs) { + return; + } + + const { tabId, ...replacementPayload } = action.payload; + const tab = state.tabs.find((item) => item.localIdentifier === tabId); + + if (!tab?.layout) { + return; + } + + const reduceForTab = withUndo(replaceInsightWidgetInsightCore); + const result = reduceForTab(tab.layout, { + ...action, + payload: replacementPayload, + }); + + if (result !== undefined) { + tab.layout = result; + } +}; // // @@ -1331,6 +1360,7 @@ export const layoutReducers = { replaceInsightWidgetVisProperties, replaceInsightWidgetVisConfiguration, replaceInsightWidgetInsight, + replaceInsightWidgetInsightForTab, replaceWidgetFilterSettings, replaceWidgetDateDataset, replaceKpiWidgetMeasure, diff --git a/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.test.ts b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.test.ts new file mode 100644 index 00000000000..500910575e3 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.test.ts @@ -0,0 +1,101 @@ +// (C) 2021-2026 GoodData Corporation + +import { describe, expect, it } from "vitest"; + +import { areObjRefsEqual, idRef } from "@gooddata/sdk-model"; + +import { type DashboardState } from "../../types.js"; + +import { selectAllInsightWidgetRefs } from "./layoutSelectors.js"; + +describe("layoutSelectors", () => { + describe("selectAllInsightWidgetRefs", () => { + it("should match insight widgets in nested layouts and visualization switcher visualizations", () => { + const targetInsightRef = idRef("target-insight", "insight"); + const topLevelWidgetRef = idRef("top-level-widget", "insight"); + const nestedWidgetRef = idRef("nested-widget", "insight"); + const switcherChildWidgetRef = idRef("switcher-child-widget", "insight"); + + const state = { + tabs: { + tabs: [ + { + localIdentifier: "tab-1", + layout: { + layout: { + type: "IDashboardLayout", + sections: [ + { + type: "IDashboardLayoutSection", + items: [ + { + type: "IDashboardLayoutItem", + size: { xl: { gridWidth: 12 } }, + widget: { + type: "insight", + identifier: "top-level-widget", + ref: topLevelWidgetRef, + insight: targetInsightRef, + }, + }, + { + type: "IDashboardLayoutItem", + size: { xl: { gridWidth: 12 } }, + widget: { + type: "IDashboardLayout", + sections: [ + { + type: "IDashboardLayoutSection", + items: [ + { + type: "IDashboardLayoutItem", + size: { xl: { gridWidth: 12 } }, + widget: { + type: "insight", + identifier: "nested-widget", + ref: nestedWidgetRef, + insight: targetInsightRef, + }, + }, + ], + }, + ], + }, + }, + { + type: "IDashboardLayoutItem", + size: { xl: { gridWidth: 12 } }, + widget: { + type: "visualizationSwitcher", + identifier: "switcher-widget", + ref: idRef("switcher-widget", "insight"), + visualizations: [ + { + type: "insight", + identifier: "switcher-child-widget", + ref: switcherChildWidgetRef, + insight: targetInsightRef, + }, + ], + }, + }, + ], + }, + ], + }, + }, + }, + ], + activeTabLocalIdentifier: "tab-1", + }, + } as unknown as DashboardState; + + const refs = selectAllInsightWidgetRefs(targetInsightRef)(state); + + expect(refs).toHaveLength(3); + expect(refs.some((ref) => areObjRefsEqual(ref, topLevelWidgetRef))).toBe(true); + expect(refs.some((ref) => areObjRefsEqual(ref, nestedWidgetRef))).toBe(true); + expect(refs.some((ref) => areObjRefsEqual(ref, switcherChildWidgetRef))).toBe(true); + }); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.ts index 306eaf59f42..741040a5473 100644 --- a/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/tabs/layout/layoutSelectors.ts @@ -15,6 +15,7 @@ import { type InsightDrillDefinition, type ObjRef, type ScreenSize, + areObjRefsEqual, dashboardAttributeFilterItemLocalIdentifier, filterContextItemsToDashboardFiltersByWidget, isDashboardAttributeFilterItem, @@ -109,6 +110,55 @@ export const selectBasicLayoutByTab: DashboardSelector< ); }); +export type IInsightWidgetRefWithTab = { + tabId: string; + widgetRef: ObjRef; +}; + +/** + * Selects refs of all insight widgets with matching insight reference across all tabs, + * including owning tab identifiers. + * + * @internal + */ +export const selectAllInsightWidgetRefsByTab: ( + insight: ObjRef | undefined, +) => DashboardSelector = createMemoizedSelector((insight: ObjRef | undefined) => + createSelector(selectAllTabsInsightWidgetContexts, (contexts): IInsightWidgetRefWithTab[] => { + if (!insight) { + return []; + } + + return contexts + .filter((context) => areObjRefsEqual(context.widget.insight, insight)) + .map((context) => ({ + tabId: context.tab.localIdentifier ?? DEFAULT_TAB_ID, + widgetRef: context.widget.ref, + })) + .filter((item, index, allItems) => { + return ( + allItems.findIndex( + (candidate) => + candidate.tabId === item.tabId && + areObjRefsEqual(candidate.widgetRef, item.widgetRef), + ) === index + ); + }); + }), +); + +/** + * Selects refs of all insight widgets with matching insight reference across all tabs. + * + * @internal + */ +export const selectAllInsightWidgetRefs: (insight: ObjRef | undefined) => DashboardSelector = + createMemoizedSelector((insight: ObjRef | undefined) => + createSelector(selectAllInsightWidgetRefsByTab(insight), (widgetRefsByTab): ObjRef[] => { + return widgetRefsByTab.map(({ widgetRef }) => widgetRef); + }), + ); + /** * This selector returns current layout's stash. This stash can contain items that were removed from the layout with the * intent of further using the item elsewhere on the layout. The stash is a mapping of stashIdentifier to an array diff --git a/libs/sdk-ui-dashboard/src/model/store/ui/uiReducers.ts b/libs/sdk-ui-dashboard/src/model/store/ui/uiReducers.ts index 36ec26ab6b6..3efd21726fc 100644 --- a/libs/sdk-ui-dashboard/src/model/store/ui/uiReducers.ts +++ b/libs/sdk-ui-dashboard/src/model/store/ui/uiReducers.ts @@ -154,6 +154,19 @@ const closeSaveAsDialog: UiReducer = (state) => { state.saveAsDialog.open = false; }; +const openInsightNotSavedDialog: UiReducer = (state) => { + state.insightNotSavedDialog.open = true; +}; + +const closeInsightNotSavedDialog: UiReducer = (state) => { + state.insightNotSavedDialog.open = false; + state.insightNotSavedDialog.saveConfirmed = false; +}; + +const confirmInsightNotSavedDialogSubmit: UiReducer = (state) => { + state.insightNotSavedDialog.saveConfirmed = true; +}; + const openSettingsDialog: UiReducer = (state) => { state.settingsDialog.open = true; }; @@ -525,6 +538,9 @@ export const uiReducers = { closeAlertingDialog, openSaveAsDialog, closeSaveAsDialog, + openInsightNotSavedDialog, + closeInsightNotSavedDialog, + confirmInsightNotSavedDialogSubmit, setFilterBarExpanded, closeKpiAlertDialog, openKpiAlertDialog, diff --git a/libs/sdk-ui-dashboard/src/model/store/ui/uiSelectors.ts b/libs/sdk-ui-dashboard/src/model/store/ui/uiSelectors.ts index 2b82da0e3eb..18b7bb5feeb 100644 --- a/libs/sdk-ui-dashboard/src/model/store/ui/uiSelectors.ts +++ b/libs/sdk-ui-dashboard/src/model/store/ui/uiSelectors.ts @@ -122,6 +122,22 @@ export const selectIsSaveAsDialogOpen: DashboardSelector = createSelect (state) => state.saveAsDialog.open, ); +/** + * @internal + */ +export const selectIsInsightNotSavedDialogOpen: DashboardSelector = createSelector( + selectSelf, + (state) => state.insightNotSavedDialog.open, +); + +/** + * @internal + */ +export const selectIsInsightNotSavedDialogSaveConfirmed: DashboardSelector = createSelector( + selectSelf, + (state) => state.insightNotSavedDialog.saveConfirmed, +); + /** * @alpha */ diff --git a/libs/sdk-ui-dashboard/src/model/store/ui/uiState.ts b/libs/sdk-ui-dashboard/src/model/store/ui/uiState.ts index 475d3876bc7..d2e4f9bc5ac 100644 --- a/libs/sdk-ui-dashboard/src/model/store/ui/uiState.ts +++ b/libs/sdk-ui-dashboard/src/model/store/ui/uiState.ts @@ -59,6 +59,10 @@ export interface IUiState { saveAsDialog: { open: boolean; }; + insightNotSavedDialog: { + open: boolean; + saveConfirmed: boolean; + }; settingsDialog: { open: boolean; }; @@ -165,6 +169,10 @@ export const uiInitialState: IUiState = { saveAsDialog: { open: false, }, + insightNotSavedDialog: { + open: false, + saveConfirmed: false, + }, settingsDialog: { open: false, }, diff --git a/libs/sdk-ui-dashboard/src/plugins/customizationApis/customizationLogging.ts b/libs/sdk-ui-dashboard/src/plugins/customizationApis/customizationLogging.ts index f5bda9e02bf..cf68af34317 100644 --- a/libs/sdk-ui-dashboard/src/plugins/customizationApis/customizationLogging.ts +++ b/libs/sdk-ui-dashboard/src/plugins/customizationApis/customizationLogging.ts @@ -28,11 +28,11 @@ export class DashboardCustomizationLogger implements IDashboardCustomizationLogg this.currentPlugin = plugin; }; public debug = (message: string, ...optionalParams: any[]): void => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug(addPluginInfoToMessage(this.currentPlugin, message), optionalParams); }; public log = (message: string, ...optionalParams: any[]): void => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(addPluginInfoToMessage(this.currentPlugin, message), optionalParams); }; public warn = (message: string, ...optionalParams: any[]): void => { diff --git a/libs/sdk-ui-dashboard/src/plugins/engine.ts b/libs/sdk-ui-dashboard/src/plugins/engine.ts index ce6cb70e8c7..22303a20fe7 100644 --- a/libs/sdk-ui-dashboard/src/plugins/engine.ts +++ b/libs/sdk-ui-dashboard/src/plugins/engine.ts @@ -68,7 +68,7 @@ export function newDashboardEngine(): IDashboardEngine { const customizationBuilder = new DashboardCustomizationBuilder(); const eventRegistration = new DefaultDashboardEventHandling(); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( `DashboardEngine ${this.version} initializing with plugins: ${plugins .map(pluginDebugStr) diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/alerting/blocks/AlertingDialogFieldLabelWithTooltip.tsx b/libs/sdk-ui-dashboard/src/presentation/automations/alerting/blocks/AlertingDialogFieldLabelWithTooltip.tsx index 31604048520..6dc7cec70b3 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/alerting/blocks/AlertingDialogFieldLabelWithTooltip.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/automations/alerting/blocks/AlertingDialogFieldLabelWithTooltip.tsx @@ -33,7 +33,6 @@ export function AlertingDialogFieldLabelWithTooltip({ arrowPlacement="left" optimalPlacement offset={10} - width={280} triggerBy={["hover", "click"]} />
diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/alerting/types.ts b/libs/sdk-ui-dashboard/src/presentation/automations/alerting/types.ts index 34dd94214fd..cdc567a2821 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/alerting/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/alerting/types.ts @@ -47,6 +47,11 @@ import { type AttributeValue } from "./hooks/useAttributeValuesFromExecResults.j /// /** + * Props of the alerting create/edit dialog, accepted by `AlertingDialogComponent`. + * + * Extends {@link IAutomationDialogCallbacks} with data props that are all deprecated — the dialog + * reads its data from `useAlertingDialogContext()` instead. + * * @beta */ export interface IAlertingDialogProps extends IAutomationDialogCallbacks { @@ -625,6 +630,11 @@ export interface IAlertingDialogFormFieldGroupProps { } /** + * Props of the alerting management dialog, accepted by `AlertingManagementDialogComponent`. + * + * Extends {@link IAutomationManagementDialogCallbacks} with data props that are all deprecated — + * the dialog reads its data from `useAlertingManagementDialogContext()` instead. + * * @beta */ export interface IAlertingManagementDialogProps extends IAutomationManagementDialogCallbacks { @@ -664,6 +674,9 @@ export interface IAlertingManagementDialogProps extends IAutomationManagementDia /// /** + * Type of a replacement for the alerting create/edit dialog — the shape expected by the + * `AlertingDialogComponent` dashboard prop. + * * @beta */ export type CustomAlertingDialogComponent = ComponentType; @@ -703,6 +716,9 @@ export type CustomAlertingDialogComponent = ComponentType; export type CustomAlertingDialogContextDecoratorComponent = ComponentType<{ children?: ReactNode }>; /** + * Type of a replacement for the alerting management dialog — the shape expected by the + * `AlertingManagementDialogComponent` dashboard prop. + * * @beta */ export type CustomAlertingManagementDialogComponent = ComponentType; @@ -776,7 +792,14 @@ export interface IAlertDropdownProps { * @beta */ export enum AlertMetricComparatorType { + /** + * Compares against the immediately preceding period at the alert's selected granularity + * (e.g. previous week, previous month). + */ PreviousPeriod, + /** + * Compares against the same period one year earlier. + */ SamePeriodPreviousYear, } diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingDialogContext.ts b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingDialogContext.ts index cd455704c4e..c1da628570b 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingDialogContext.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingDialogContext.ts @@ -26,13 +26,21 @@ import type { * @beta */ export interface IAlertingDialogContextValue { + /** Whether the dialog creates a new alert or edits an existing one. */ mode: "create" | "edit"; + /** Widget the alert targets; undefined when alerting for a dashboard rather than a widget. */ widget?: IWidget; + /** Insight of {@link IAlertingDialogContextValue.widget}; undefined when alerting for a dashboard. */ insight?: IInsight; + /** Title of {@link IAlertingDialogContextValue.widget}; undefined when alerting for a dashboard. */ widgetTitle?: string; + /** Id of the dashboard the alert belongs to. */ dashboardId?: string; + /** The dashboard's applied filters available to the alert — cross-filtering, hidden, and empty filters excluded. */ dashboardFilters: FilterContextItem[]; + /** Raw dashboard hidden filters (`selectDashboardHiddenFilters`); combined with the edited filters via `getAppliedWidgetFilters` inside the dialog. */ hiddenFilters: FilterContextItem[]; + /** Looks up a widget's execution result by ref; used to resolve attribute values for the condition's "for" field. */ executionResultByRef: (ref: ObjRef | undefined) => { executionResult?: IExecutionResult } | undefined; /** Effective widget parameter values for the dialog's widget (replaces direct selectEffectiveParameterValuesForWidget read) */ parameterValues: IInsightParameterValue[]; @@ -42,10 +50,15 @@ export interface IAlertingDialogContextValue { * read in useAutomationAlertParameters). */ dashboardParameters: IDashboardParameter[]; + /** Local identifier of the dashboard's common (dashboard-level) date filter, when the automation-available filters include one. */ commonDateFilterId?: string; + /** The dashboard's configured evaluation frequency (cron expression); seeds the new alert's trigger interval when set. */ dashboardEvaluationFrequency?: string; + /** Creates a new alert on the backend. */ createAlert(alert: IAutomationMetadataObjectDefinition): Promise; + /** Saves changes to an existing alert on the backend. */ saveAlert(alert: IAutomationMetadataObject): Promise; + /** Deletes the alert from the backend. */ deleteAlert(alert: IAutomationMetadataObject): Promise; /** The alert being edited; undefined when creating a new one. */ alertToEdit?: IAutomationMetadataObject; diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingManagementDialogContext.ts b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingManagementDialogContext.ts index 33350f26582..0d8b742dcef 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingManagementDialogContext.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AlertingManagementDialogContext.ts @@ -12,15 +12,36 @@ import type { IAutomationMetadataObject, IInsight, IUser, IWidget, ObjRef } from * @alpha */ export interface IAlertingManagementDialogContextValue { + /** The logged-in user; from selectCurrentUser. Used to decide whether the user may edit/delete an alert they didn't create. */ currentUser?: IUser; + /** Id of the dashboard the management dialog is rendered on. Used to preselect the dashboard filter in the enhanced automations list. */ dashboardId?: string; + /** Title of the dashboard the management dialog is rendered on. Used as the label of the preselected dashboard filter in the enhanced automations list. */ dashboardTitle?: string; + /** Whether the current user may manage the workspace; from selectCanManageWorkspace. Combined with alert ownership to gate edit/delete. */ canManageWorkspace: boolean; + /** + * Whether the alert create/edit dialog is currently open (from store UI slice). + * Used by the management dialog's focus-return logic (refocus when the edit dialog closes). + */ isAlertDialogOpen: boolean; + /** Element ID to return focus to when the alerting dialog closes; from selectAlertingDialogReturnFocusTo, read via useAlertingDialogAccessibility. */ alertingDialogReturnFocusTo?: string; + /** Widget the management dialog is scoped to, when opened for a single widget rather than the whole dashboard; from selectIsAlertingManagementDialogContext. */ managementDialogContext: { widgetRef?: ObjRef }; + /** + * Cache-bust counter for the automation list: reset to 0 when a management dialog opens, + * incremented on invalidation. 0 means nothing has been invalidated since the dialog opened; + * consumers reload when the value changes and is not 0. + */ automationsInvalidationId?: number; + /** + * Whether the dashboard is rendered in an embedded context. + */ isEmbedded: boolean; + /** + * Whether accessibility mode is active (for aria/keyboard focus management). + */ enableAccessibilityMode: boolean; /** * Alerts visible in the management dialog, scoped to the current dashboard context. @@ -34,9 +55,13 @@ export interface IAlertingManagementDialogContextValue { * management list. */ isLoading: boolean; + /** Looks up a dashboard widget (KPI, insight, rich text, or visualization-switcher) by ref, filtering out non-widget layout containers; bridges identifier↔URI ref mismatches the same way as selectWidgetByRef. */ getWidgetByRef: (ref: ObjRef | undefined) => IWidget | undefined; + /** Looks up the insight of an insight widget by the widget's ref; undefined for a non-insight widget. */ getInsightByWidgetRef: (ref: ObjRef | undefined) => IInsight | undefined; + /** Persists the given alert via the alerts save command; resolves with the saved alert, rejects with the command's error. */ pauseAlert(alert: IAutomationMetadataObject): Promise; + /** Persists the given alert via the alerts save command; resolves with the saved alert, rejects with the command's error. Functionally identical to {@link IAlertingManagementDialogContextValue.pauseAlert} — distinguished only by name for call-site clarity. */ resumeAlert(alert: IAutomationMetadataObject): Promise; } diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AutomationsContext.ts b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AutomationsContext.ts index b6834a4dcd0..bf68cea3bb0 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AutomationsContext.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/AutomationsContext.ts @@ -35,9 +35,23 @@ import type { IAutomationFiltersTab } from "../../../model/store/filtering/types * @beta */ export interface IAutomationsDateFilterConfig { + /** Granularities from the dashboard's effective (resolved) date-filter configuration; the fallback when no tab-scoped value is available. */ availableGranularities: DateFilterGranularity[]; + /** Date-filter options (presets) from the dashboard's effective (resolved) date-filter configuration; the fallback when no tab-scoped value is available. */ dateFilterOptions: IDateFilterOptionsByType; + /** + * Granularities from the given dashboard tab's effective (resolved) date-filter configuration — + * inherited settings included, not only tab-specific overrides. Empty when the tab has no + * effective configuration, in which case the caller falls back to + * {@link IAutomationsDateFilterConfig.availableGranularities}. + */ getGranularitiesForTab: (tabId: string) => DateFilterGranularity[]; + /** + * Date-filter options from the given dashboard tab's effective (resolved) date-filter + * configuration — inherited settings included, not only tab-specific overrides. Undefined when + * the tab has no effective configuration, in which case the caller falls back to + * {@link IAutomationsDateFilterConfig.dateFilterOptions}. + */ getOptionsForTab: (tabId: string) => IDateFilterOptionsByType | undefined; } @@ -70,37 +84,76 @@ export interface IAutomationsParameters { * @beta */ export interface IAutomationsContextValue { + /** Workspace locale for message formatting and number/date localization; from selectLocale. */ locale: ILocale; + /** Number formatting separators (decimal/thousands); from selectSeparators. */ separators: ISeparators; + /** Workspace/dashboard feature settings object; from selectSettings. */ settings?: ISettings; + /** Workspace attribute catalog, including computed attributes; from selectCatalogAttributesWithComputed. */ catalogAttributes: ICatalogAttribute[]; + /** Workspace date dataset catalog; from selectCatalogDateDatasets. */ catalogDateDatasets: ICatalogDateDataset[]; + /** Workspace measure catalog; from selectCatalogMeasures. */ catalogMeasures: ICatalogMeasure[]; + /** Date filter granularities and presets for automation date filter chips; see {@link IAutomationsDateFilterConfig}. */ dateFilterConfig: IAutomationsDateFilterConfig; + /** + * Persisted common date filter config for the active tab, used for its custom title; + * from selectPersistedDashboardFilterContextDateFilterConfig. + */ dateFilterContextConfig: IDashboardDateFilterConfig | undefined; + /** Dashboard-level attribute filter config overrides for the active tab; from selectAttributeFilterConfigsOverrides. */ attributeFilterConfigs: IDashboardAttributeFilterConfig[]; + /** Same as {@link IAutomationsContextValue.attributeFilterConfigs}, keyed by tab identifier; from selectAttributeFilterConfigsOverridesByTab. */ attributeFilterConfigsByTab: Record; + /** Attribute filter selection type (single/multi) by local identifier for the active tab; from selectAttributeFilterConfigsSelectionTypeMap. */ attributeFilterSelectionTypeMap: Map; + /** Same as {@link IAutomationsContextValue.attributeFilterSelectionTypeMap}, keyed by tab identifier; from selectAttributeFilterConfigsSelectionTypeMapByTab. */ attributeFilterSelectionTypeMapByTab: Record< string, Map >; + /** Per-dataset (dimension) date filter config overrides for the active tab; from selectDateFilterConfigsOverrides. */ dateFilterConfigs: IDashboardDateFilterConfigItem[]; + /** Same as {@link IAutomationsContextValue.dateFilterConfigs}, keyed by tab identifier; from selectDateFilterConfigsOverridesByTab. */ dateFilterConfigsByTab: Record; + /** Dashboard-level common (whole-dashboard) date filter config override, keyed by tab identifier; from selectDateFilterConfigOverridesByTab. */ dateFilterConfigOverridesByTab: Record; + /** Dashboard-level measure value filter config overrides for the active tab; from selectMeasureValueFilterConfigsOverrides. */ measureValueFilterConfigs: IDashboardMeasureValueFilterConfig[]; + /** Same as {@link IAutomationsContextValue.measureValueFilterConfigs}, keyed by tab identifier; from selectMeasureValueFilterConfigsOverridesByTab. */ measureValueFilterConfigsByTab: Record; + /** Local identifier of the dashboard's common date filter, when the automation-available filters include one; from selectAutomationCommonDateFilterId. */ commonDateFilterId: string | undefined; + /** Dashboard filters configured as read-only (locked); from selectDashboardLockedFilters. */ lockedFilters: FilterContextItem[]; + /** Dashboard filters configured as hidden; from selectDashboardHiddenFilters. */ hiddenFilters: FilterContextItem[]; + /** Dashboard's applied filters with cross-filtering filters removed; from selectDashboardFiltersWithoutCrossFiltering. */ availableFilters: FilterContextItem[]; + /** Automation-available filters structured per dashboard tab, for whole-dashboard automations on a tabbed dashboard; from selectAutomationFiltersByTab. */ automationFiltersByTab: IAutomationFiltersTab[]; + /** Automation-available filters with empty (all-values) filters removed; preselects filters for a new automation. From selectAutomationDefaultSelectedFilters. */ defaultSelectedFilters: FilterContextItem[]; + /** Dashboard filters available to an automation — hidden filters removed, common date filter included; from selectAutomationAvailableDashboardFilters. */ automationAvailableFilters: FilterContextItem[]; + /** + * Maximum number of recipients allowed on an automation, derived from the recipient-count + * entitlement (`Infinity` when the unlimited-recipients entitlement is present); from + * selectMaxAutomationRecipients. + */ maxAutomationsRecipients: number; + /** + * Whether the dashboard is opened at a past execution timestamp (a scheduled run's historical + * snapshot); disables create/save actions in the dialogs. Derived from selectExecutionTimestamp. + */ isExecutionTimestampMode: boolean; + /** Whether the workspace's minimum-recurrence entitlement permits hourly schedules; derived from selectEntitlementMinimumRecurrenceMinutes. */ allowHourlyRecurrence: boolean; + /** The logged-in user; from selectCurrentUser. */ currentUser: IUser; + /** The workspace's configured first day of the week; from selectWeekStart. */ weekStart: WeekStart; /** * Automation timezone to display and compute schedule ("Starts on", alert crons, next-run): @@ -142,18 +195,30 @@ export interface IAutomationsContextValue { */ scheduledExportTimezone: string | undefined; }; + /** Whether the dashboard is white-labeled (hides GoodData branding and help links); from selectIsWhiteLabeled. */ isWhiteLabeled: boolean; + /** Whether the dialog header renders a secondary title row below the main title; always true in the current hydration. */ isSecondaryTitleVisible: boolean; + /** External (non-workspace) recipient the dialog is scoped to, when opened via an external share link; from selectExternalRecipient. */ externalRecipient: string | undefined; + /** Feature flags and permissions gating automation dialog behavior. */ features: { + /** Whether the current user may create new automations; gates the management dialogs' create button. From selectCanCreateAutomation. */ canCreateAutomation: boolean; + /** Whether the "once per interval" alert trigger option is offered; from selectEnableAlertOncePerInterval. */ enableAlertOncePerInterval: boolean; + /** Whether anomaly-detection alert conditions are offered; from selectEnableAnomalyDetectionAlert. */ enableAnomalyDetectionAlert: boolean; + /** Whether the current user may use the AI assistant; combined with enableAnomalyDetectionAlert to gate anomaly-detection alert conditions. From selectCanUseAiAssistant. */ canUseAiAssistant: boolean; + /** Whether the current user may manage the workspace; from selectCanManageWorkspace. */ canManageWorkspace: boolean; + /** Whether scheduled slideshow (PDF slides) exports are offered; from selectEnableSlideshowExports. */ enableSlideshowExports: boolean; + /** Whether the automation evaluation-mode setting is enabled; from selectEnableAutomationEvaluationMode. */ enableAutomationEvaluationMode: boolean; }; + /** Workspace parameter catalog and effective dashboard parameter values for automations; see {@link IAutomationsParameters}. */ parameters: IAutomationsParameters; /** * Local identifiers of the dashboard's tabs, in layout order; empty when the dashboard has none. @@ -163,7 +228,13 @@ export interface IAutomationsContextValue { tabIds: string[]; /** Maps a widget's localIdentifier to the localIdentifier of the tab that owns it. */ widgetLocalIdToTabIdMap: Record; + /** Looks up a catalog attribute (or date attribute) by ref; backed by selectAllCatalogAttributesMap. Used to resolve attribute names for filter labels. */ getCatalogAttributeByRef: (ref: ObjRef) => ICatalogAttribute | ICatalogDateAttribute | undefined; + /** + * Looks up an attribute filter's display form by ref, preferring the dashboard's own attribute + * filter display forms before falling back to the workspace catalog; combines + * selectAttributeFilterDisplayFormsMap and selectAllCatalogDisplayFormsMap. + */ getAttributeFilterDisplayForm: (displayForm: ObjRef) => IAttributeDisplayFormMetadataObject | undefined; /** * Element ID to return focus to when the scheduled-email dialog closes. diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailDialogContext.ts b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailDialogContext.ts index f16839f123e..15bce45c00c 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailDialogContext.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailDialogContext.ts @@ -26,10 +26,15 @@ import type { * @beta */ export interface IScheduledEmailDialogContextValue { + /** Widget the scheduled export targets; undefined when scheduling an export for a dashboard rather than a widget. */ widget?: IWidget; + /** Insight of {@link IScheduledEmailDialogContextValue.widget}; undefined when scheduling for a dashboard. */ insight?: IInsight; + /** Title of {@link IScheduledEmailDialogContextValue.widget}; undefined when scheduling for a dashboard. */ widgetTitle?: string; + /** Id of the dashboard the scheduled export belongs to. */ dashboardId?: string; + /** Dashboard title; used as the subject placeholder. */ dashboardTitle: string; /** * Dashboard filters the export will use: the saved filters when editing, otherwise the currently @@ -41,6 +46,7 @@ export interface IScheduledEmailDialogContextValue { * getAppliedDashboardFilters/getAppliedWidgetFilters inside the dialog, mirroring the alerting context. */ hiddenFilters: FilterContextItem[]; + /** Local identifier of the dashboard's common (dashboard-level) date filter, when the automation-available filters include one. */ commonDateFilterId?: string; /** * Effective export parameter overrides keyed by tab, scoped to the dialog's widget when present. @@ -63,8 +69,11 @@ export interface IScheduledEmailDialogContextValue { /** Effective mode map for attribute filters (localIdentifier → mode). */ attributeFiltersModeMap: Map; // CRUD — observer-hook semantics preserved by the dialog; these are the backend calls. + /** Creates a new scheduled export on the backend. */ createScheduledEmail(se: IAutomationMetadataObjectDefinition): Promise; + /** Saves changes to an existing scheduled export on the backend. */ saveScheduledEmail(se: IAutomationMetadataObject): Promise; + /** Deletes the scheduled export from the backend. */ deleteScheduledEmail(se: IAutomationMetadataObject): Promise; /** The scheduled export being edited; undefined when creating a new one. */ scheduledExportToEdit?: IAutomationMetadataObject; diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailManagementDialogContext.ts b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailManagementDialogContext.ts index a189625ce42..69015df3960 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailManagementDialogContext.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/contexts/ScheduledEmailManagementDialogContext.ts @@ -21,8 +21,9 @@ export interface IScheduledEmailManagementDialogContextValue { */ isScheduleEmailDialogOpen: boolean; /** - * Monotonic counter that increments whenever the automation list is invalidated. - * Consumers can use this as a cache-bust key for re-fetching. + * Cache-bust counter for the automation list: reset to 0 when a management dialog opens, + * incremented on invalidation. 0 means nothing has been invalidated since the dialog opened; + * consumers reload when the value changes and is not 0. */ automationsInvalidationId?: number; /** diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/DefaultScheduledEmailManagementDialog/DefaultScheduledEmailManagementDialog.tsx b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/DefaultScheduledEmailManagementDialog/DefaultScheduledEmailManagementDialog.tsx index 561d1242189..2bb4991a576 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/DefaultScheduledEmailManagementDialog/DefaultScheduledEmailManagementDialog.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/DefaultScheduledEmailManagementDialog/DefaultScheduledEmailManagementDialog.tsx @@ -9,6 +9,9 @@ import { type IScheduledEmailManagementDialogProps } from "../types.js"; import { DefaultScheduledEmailManagementDialogContent } from "./DefaultScheduledEmailManagementDialogContent.js"; /** + * Default implementation of the scheduled-email management dialog: the list of the dashboard's + * scheduled exports, with actions to add, edit, or delete one. + * * @beta */ export function ScheduledEmailManagementDialog({ diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/state/scheduledExportDialogState.shape.test.tsx b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/state/scheduledExportDialogState.shape.test.tsx index 3c04019d3eb..ea0ab40278c 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/state/scheduledExportDialogState.shape.test.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/state/scheduledExportDialogState.shape.test.tsx @@ -14,7 +14,6 @@ import { } from "@gooddata/sdk-model"; import type { IAutomationFiltersTab } from "../../../../model/store/filtering/types.js"; -import type * as AutomationFiltersSelectModule from "../../shared/automationFilters/useAutomationFiltersSelect.js"; // --------------------------------------------------------------------------- // Mocks — vi.mock calls are hoisted; factories must not reference top-level @@ -47,8 +46,8 @@ vi.hoisted(() => { vi.resetModules(); }); -vi.mock("../../shared/automationFilters/useAutomationFiltersSelect.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock(import("../../shared/automationFilters/useAutomationFiltersSelect.js"), async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, useAutomationFiltersSelect: mockUseAutomationFiltersSelect }; }); diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/types.ts b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/types.ts index de44f492313..ba7b8770b50 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/automations/scheduledEmail/types.ts @@ -41,6 +41,11 @@ import { /// /** + * Props of the scheduled-email create/edit dialog, accepted by `ScheduledEmailDialogComponent`. + * + * Extends {@link IAutomationDialogCallbacks} with data props that are all deprecated — the dialog + * reads its data from `useScheduledEmailDialogContext()` instead. + * * @beta */ export interface IScheduledEmailDialogProps extends IAutomationDialogCallbacks { @@ -769,6 +774,10 @@ export interface IScheduledEmailDialogShellProps extends Pick< } /** + * Props of the scheduled-email management dialog, accepted by `ScheduledEmailManagementDialogComponent`. + * + * Extends {@link IAutomationManagementDialogCallbacks} with data props that are all deprecated — + * the dialog reads its data from `useScheduledEmailManagementDialogContext()` instead. * * @beta */ @@ -809,6 +818,9 @@ export interface IScheduledEmailManagementDialogProps extends IAutomationManagem /// /** + * Type of a replacement for the scheduled-email create/edit dialog — the shape expected by the + * `ScheduledEmailDialogComponent` dashboard prop. + * * @beta */ export type CustomScheduledEmailDialogComponent = ComponentType; @@ -855,6 +867,9 @@ export type CustomScheduledEmailDialogContextDecoratorComponent = ComponentType< }>; /** + * Type of a replacement for the scheduled-email management dialog — the shape expected by the + * `ScheduledEmailManagementDialogComponent` dashboard prop. + * * @beta */ export type CustomScheduledEmailManagementDialogComponent = diff --git a/libs/sdk-ui-dashboard/src/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.tsx b/libs/sdk-ui-dashboard/src/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.tsx index 789ef5ba586..0927eb6e7e9 100644 --- a/libs/sdk-ui-dashboard/src/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/automations/shared/automationFilters/components/AutomationFiltersSelect.tsx @@ -204,7 +204,6 @@ function AutomationCheckboxOrNote({ arrowPlacement="left" triggerBy={["hover", "focus"]} optimalPlacement - width={300} content={useFiltersTooltip} anchor={ ; } +function InsightNotSavedDialogWrapper() { + return ; +} + function TopBarWrapper() { const topBarProps = useTopBarProps(); return ; @@ -107,6 +112,7 @@ export function DashboardHeader(): ReactElement { + diff --git a/libs/sdk-ui-dashboard/src/presentation/dashboardSettingsDialog/DefaultDashboardSettingsDialog.tsx b/libs/sdk-ui-dashboard/src/presentation/dashboardSettingsDialog/DefaultDashboardSettingsDialog.tsx index 5612ca16b18..cc0fc8b628e 100644 --- a/libs/sdk-ui-dashboard/src/presentation/dashboardSettingsDialog/DefaultDashboardSettingsDialog.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/dashboardSettingsDialog/DefaultDashboardSettingsDialog.tsx @@ -46,6 +46,9 @@ import { TimezoneOption } from "./TimezoneOption.js"; import { type IDashboardSettingsDialogProps } from "./types.js"; import { useDialogData } from "./useDialogData.js"; +const DASHBOARD_TIMEZONE_DOC_LINK = + "https://www.gooddata.ai/docs/cloud/customize-appearance/manage-timezones/#change-the-time-zone-on-a-dashboard"; + /** * @alpha */ @@ -269,9 +272,24 @@ export function DefaultDashboardSettingsDialog({ label={intl.formatMessage({ id: "settingsDashboardDialog.section.timezone.defaultTimezone", })} - tooltip={intl.formatMessage({ - id: "settingsDashboardDialog.section.timezone.defaultTimezone.tooltip", - })} + tooltip={ + + isWhiteLabeled ? null : ( + + {chunks} + + ), + }} + /> + } timezoneConfig={currentData.timezoneConfig} onChange={(timezoneId: string | undefined) => { setCurrentData({ diff --git a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditor.tsx b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditor.tsx index 8b2ca22df6c..8716f48fb3a 100644 --- a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditor.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditor.tsx @@ -4,7 +4,7 @@ import { type ReactElement, useCallback, useMemo, useState } from "react"; import { HighlightStyle, StreamLanguage, type StringStream, syntaxHighlighting } from "@codemirror/language"; import { Tag } from "@lezer/highlight"; -import { compact, uniqBy } from "lodash-es"; +import { compact, escapeRegExp, uniqBy } from "lodash-es"; import { FormattedMessage, type IntlShape, useIntl } from "react-intl"; import { @@ -12,11 +12,9 @@ import { type IDashboardAttributeFilterConfig, type IDashboardMeasureValueFilter, type IMeasureValueFilter, - type IdentifierRef, type ObjRef, dashboardAttributeFilterItemToAttributeFilter, filterObjRef, - idRef, insightFilters as insightDefinitionFilters, isArbitraryAttributeFilter, isAttributeFilter, @@ -27,6 +25,12 @@ import { objRefToString, serializeObjRef, } from "@gooddata/sdk-model"; +import { + attributeIdentifierToPlaceholder, + dashboardAttributeFilterToPlaceholder, + displayFormPlaceholderRef, + insightAttributeFilterToPlaceholder, +} from "@gooddata/sdk-model/internal"; import { ConfirmDialogBase, FullScreenOverlay, @@ -167,7 +171,9 @@ const buildValidDisplayFormsFormattingRule = (attributeDisplayForms: IAttributeW return undefined; } const validAttributePlaceholders = attributeDisplayForms - .map(({ displayForm }) => `{attribute_title\\(${displayForm.id}\\)}`) + .map(({ displayForm }) => + escapeRegExp(attributeIdentifierToPlaceholder(displayFormPlaceholderRef(displayForm))), + ) .join("|"); return { regex: new RegExp(`^${validAttributePlaceholders}`), token: customTags.attribute.toString() }; }; @@ -178,12 +184,11 @@ const buildValidInsightFiltersFormattingRule = (attributeFilters: IAttributeFilt } const validInsightAttributeFilterPlaceholders = attributeFilters - .map((filter) => { - // these filters always come from useSanitizedInsightFilters, which rewrites the display form - // ref to idRef(displayForm.id, "displayForm") or drops the filter -> never a uri ref here - const { identifier } = filterObjRef(filter) as IdentifierRef; - return `{attribute_filter_selection\\(${identifier}\\)}`; - }) + .map((filter) => + // these filters always come from useSanitizedInsightFilters, which rewrites the display + // form ref to the canonical placeholder ref or drops the filter -> never a uri ref here + escapeRegExp(insightAttributeFilterToPlaceholder(filterObjRef(filter))), + ) .join("|"); return { regex: new RegExp(`^${validInsightAttributeFilterPlaceholders}`), @@ -199,17 +204,13 @@ const buildValidDashboardFiltersFormattingRule = ( return undefined; } - const filterPlaceholders = attributeFilters.map((filter) => { - const ref = filterObjRef(filter); - const id = objRefToString(ref); - return `{dash_attribute_filter_selection\\(${id}\\)}`; - }); + const filterPlaceholders = attributeFilters.map((filter) => + escapeRegExp(dashboardAttributeFilterToPlaceholder(filterObjRef(filter))), + ); const configPlaceholders = attributeFilterConfigs - .filter((config) => !!config.displayAsLabel) - .map((config) => { - const id = !!config.displayAsLabel && objRefToString(config.displayAsLabel); - return `{dash_attribute_filter_selection\\(${id}\\)}`; - }); + .map((config) => config.displayAsLabel) + .filter((displayAsLabel): displayAsLabel is ObjRef => !!displayAsLabel) + .map((displayAsLabel) => escapeRegExp(dashboardAttributeFilterToPlaceholder(displayAsLabel))); const validDashboardAttributeFilterPlaceholders = [...filterPlaceholders, ...configPlaceholders].join( "|", @@ -635,7 +636,7 @@ function useSanitizeAttributeFilter() { ...filter, negativeAttributeFilter: { ...filter.negativeAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } else if (isPositiveAttributeFilter(filter)) { @@ -643,7 +644,7 @@ function useSanitizeAttributeFilter() { ...filter, positiveAttributeFilter: { ...filter.positiveAttributeFilter, - displayForm: idRef(displayForm.id, "displayForm"), + displayForm: displayFormPlaceholderRef(displayForm), }, }; } else if (isArbitraryAttributeFilter(filter)) { @@ -651,7 +652,7 @@ function useSanitizeAttributeFilter() { ...filter, arbitraryAttributeFilter: { ...filter.arbitraryAttributeFilter, - label: idRef(displayForm.id, "displayForm"), + label: displayFormPlaceholderRef(displayForm), }, }; } else if (isMatchAttributeFilter(filter)) { @@ -659,7 +660,7 @@ function useSanitizeAttributeFilter() { ...filter, matchAttributeFilter: { ...filter.matchAttributeFilter, - label: idRef(displayForm.id, "displayForm"), + label: displayFormPlaceholderRef(displayForm), }, }; } diff --git a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DashboardParametersSection.tsx b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DashboardParametersSection.tsx index 0bbef51a7a5..fafa172ab69 100644 --- a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DashboardParametersSection.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DashboardParametersSection.tsx @@ -12,6 +12,7 @@ import { filterObjRef, objRefToString, } from "@gooddata/sdk-model"; +import { dashboardAttributeFilterToPlaceholder } from "@gooddata/sdk-model/internal"; import { MeasureValueFilterDetailsBubble } from "@gooddata/sdk-ui-filters/internal"; import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js"; @@ -72,7 +73,7 @@ export function DashboardParametersSection({ item={df} iconClassName="gd-icon-filter" onAdd={() => { - onAdd(`{dash_attribute_filter_selection(${filterIdentifier})}`); + onAdd(dashboardAttributeFilterToPlaceholder(df.ref)); }} isFilter /> @@ -82,7 +83,7 @@ export function DashboardParametersSection({ item={secondaryDf} iconClassName="gd-icon-filter" onAdd={() => { - onAdd(`{dash_attribute_filter_selection(${filterSecondaryIdentifier})}`); + onAdd(dashboardAttributeFilterToPlaceholder(secondaryDf.ref)); }} isFilter /> diff --git a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DisplayFormParam.tsx b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DisplayFormParam.tsx index 64bf54f83d3..0fa982bc122 100644 --- a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DisplayFormParam.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/DisplayFormParam.tsx @@ -7,6 +7,7 @@ import { type IAttributeDisplayFormMetadataObject, dashboardAttributeFilterItemTitle, } from "@gooddata/sdk-model"; +import { attributeIdentifierToPlaceholder, displayFormPlaceholderRef } from "@gooddata/sdk-model/internal"; import { useWorkspaceStrict } from "@gooddata/sdk-ui"; import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js"; @@ -52,7 +53,7 @@ export function DisplayFormParam({ item, onAdd, iconClassName, isFilter }: IXPro iconClassName={ iconClassName ?? getDisplayFormIcon(item.displayFormType as AttributeDisplayFormType) } - onAdd={() => onAdd(`{attribute_title(${item.id})}`)} + onAdd={() => onAdd(attributeIdentifierToPlaceholder(displayFormPlaceholderRef(item)))} intl={intl} /> ); diff --git a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/InsightParametersSection.tsx b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/InsightParametersSection.tsx index 0c2438b018c..0e7fe0bebb5 100644 --- a/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/InsightParametersSection.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/drill/DrillConfigPanel/DrillToUrl/CustomUrlEditorParametersSections/InsightParametersSection.tsx @@ -18,6 +18,7 @@ import { measureTitle, objRefToString, } from "@gooddata/sdk-model"; +import { insightAttributeFilterToPlaceholder } from "@gooddata/sdk-model/internal"; import { MeasureValueFilterDetailsBubble } from "@gooddata/sdk-ui-filters/internal"; import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js"; @@ -85,7 +86,7 @@ export function InsightParametersSection({ df && ( onAdd(`{attribute_filter_selection(${df.id})}`)} + onAdd={() => onAdd(insightAttributeFilterToPlaceholder(df.ref))} iconClassName="gd-icon-filter" key={index} isFilter diff --git a/libs/sdk-ui-dashboard/src/presentation/drill/utils/drillToCustomUrlUtils.ts b/libs/sdk-ui-dashboard/src/presentation/drill/utils/drillToCustomUrlUtils.ts index aacc94b5e56..48b48802710 100644 --- a/libs/sdk-ui-dashboard/src/presentation/drill/utils/drillToCustomUrlUtils.ts +++ b/libs/sdk-ui-dashboard/src/presentation/drill/utils/drillToCustomUrlUtils.ts @@ -5,7 +5,10 @@ import { type IDrillToCustomUrl, areObjRefsEqual, } from "@gooddata/sdk-model"; -import { getAttributeIdentifiersPlaceholdersFromUrl } from "@gooddata/sdk-model/internal"; +import { + displayFormPlaceholderRef, + getAttributeIdentifiersPlaceholdersFromUrl, +} from "@gooddata/sdk-model/internal"; import { type IDrillEvent, isDrillIntersectionAttributeItem } from "@gooddata/sdk-ui"; /** @@ -38,8 +41,10 @@ export function getDrillToCustomUrlMissingAttributes( for (const placeholder of attributePlaceholders) { const displayForm = displayFormsMap[placeholder.identifier]; - // Configuration error handled elsewhere - if (!displayForm) { + // Configuration error handled elsewhere. The map is keyed by bare identifier, which a computed + // attribute can share with a label, so the kind of object is checked too rather than reporting + // whichever of the two happens to be in the map. + if (!displayForm || !areObjRefsEqual(displayFormPlaceholderRef(displayForm), placeholder.ref)) { continue; } diff --git a/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/DefaultFilterBar.tsx b/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/DefaultFilterBar.tsx index 072c8ae0391..e6ab3bcf87a 100644 --- a/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/DefaultFilterBar.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/DefaultFilterBar.tsx @@ -36,11 +36,13 @@ import { changeWorkingAttributeFilterSelection, changeWorkingMeasureValueFilterCondition, clearDateFilterSelection, + removeRestrictedFilters, replaceAttributeFilterItemSelection, replaceWorkingAttributeFilterItemSelection, setAttributeFilterDisplayForm, } from "../../../model/commands/filters.js"; import { useDashboardDispatch, useDashboardSelector } from "../../../model/react/DashboardStoreProvider.js"; +import { useDispatchDashboardCommand } from "../../../model/react/useDispatchDashboardCommand.js"; import { selectSupportsElementUris } from "../../../model/store/backendCapabilities/backendCapabilitiesSelectors.js"; import { selectEnableDashboardFilterGroups, @@ -360,6 +362,11 @@ export function DefaultFilterBar(props: IFilterBarProps): ReactElement { const { DashboardDateFilterComponentProvider, RestrictedFiltersPlaceholderComponentProvider } = useDashboardComponentsContext(); const restrictedFilterCount = useDashboardSelector(selectRestrictedDashboardFilterCount); + const dispatchRemoveRestrictedFilters = useDispatchDashboardCommand(removeRestrictedFilters); + const removeRestrictedFiltersFromDashboard = useCallback( + () => dispatchRemoveRestrictedFilters(), + [dispatchRemoveRestrictedFilters], + ); const canAddMoreFilters = useDashboardSelector(selectCanAddMoreFilters); const haveAllFiltersHidden = areAllFiltersHidden( draggableFiltersWithPlaceholder, @@ -396,7 +403,10 @@ export function DefaultFilterBar(props: IFilterBarProps): ReactElement { return ( {restrictedFilterCount > 0 ? ( - + ) : null}
void) { return render( - + , ); } @@ -26,6 +26,54 @@ describe("RestrictedFiltersPlaceholder", () => { expect(screen.getByText("3 filters weren't applied")).toBeInTheDocument(); }); + it("reaches the remove action by keyboard alone", async () => { + const onRemove = vi.fn(); + renderPlaceholder(1, onRemove); + const user = userEvent.setup(); + + await user.tab(); + await user.keyboard("{Enter}"); + + expect(screen.getByRole("button", { name: "Remove restricted filters" })).toHaveFocus(); + + await user.keyboard("{Enter}"); + + expect(onRemove).toHaveBeenCalledTimes(1); + }); + + it("hands the focus to the next control before it disappears", async () => { + const onRemove = vi.fn(); + render( + + + + , + ); + const user = userEvent.setup(); + + await user.tab(); + await user.keyboard("{Enter}"); + await user.keyboard("{Enter}"); + + expect(onRemove).toHaveBeenCalledTimes(1); + expect(screen.getByRole("button", { name: "Date filter" })).toHaveFocus(); + }); + + it("becomes a dialog only when the action is asked for, and lets the keyboard leave again", async () => { + renderPlaceholder(1, vi.fn()); + const user = userEvent.setup(); + + await user.tab(); + expect(screen.getByRole("tooltip")).toBeInTheDocument(); + + await user.keyboard("{Enter}"); + expect(screen.getByRole("dialog", { name: "1 filter wasn't applied" })).toBeInTheDocument(); + + await user.tab(); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + expect(screen.getByTestId("restricted-filters")).toHaveFocus(); + }); + it("gives the reason on focus, so it is reachable without a pointer", async () => { renderPlaceholder(2); diff --git a/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/RestrictedFiltersPlaceholder.tsx b/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/RestrictedFiltersPlaceholder.tsx index d3f7425c1a2..f32e3543d00 100644 --- a/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/RestrictedFiltersPlaceholder.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/filterBar/filterBar/RestrictedFiltersPlaceholder.tsx @@ -1,8 +1,10 @@ // (C) 2026 GoodData Corporation +import { type ReactNode, useRef, useState } from "react"; + import { FormattedMessage, useIntl } from "react-intl"; -import { UiTooltip } from "@gooddata/sdk-ui-kit"; +import { UiButton, UiFocusManager, UiTooltip, getFocusableElements } from "@gooddata/sdk-ui-kit"; /** * Props of the component reporting the dashboard filters that could not be applied. @@ -12,31 +14,116 @@ import { UiTooltip } from "@gooddata/sdk-ui-kit"; export interface IRestrictedFiltersPlaceholderProps { /** How many filters were left out of the dashboard. */ count: number; + + /** Removes the filters from the dashboard for good; absent unless the dashboard is being edited. */ + onRemove?: () => void; } /** * Reports filters left out because the current user may not read the objects they filter by. */ -export function RestrictedFiltersPlaceholder({ count }: IRestrictedFiltersPlaceholderProps) { +export function RestrictedFiltersPlaceholder({ count, onRemove }: IRestrictedFiltersPlaceholderProps) { const intl = useIntl(); + const wrapperRef = useRef(null); + // hovering must not steal the focus, so only a deliberate open moves it into the tooltip + const [isOpenedForAction, setIsOpenedForAction] = useState(false); + + const openForAction = () => setIsOpenedForAction(true); + + const withFocusInside = (content: ReactNode, onClose: () => void) => + isOpenedForAction ? ( + { + setIsOpenedForAction(false); + onClose(); + }} + > + {content} + + ) : ( + content + ); + + // the notice goes away with the filters it reports, so the focus has to move out of it first + const focusNextControl = () => { + const wrapper = wrapperRef.current; + const { focusableElements } = getFocusableElements(wrapper?.parentElement); + focusableElements.find((element) => !wrapper?.contains(element))?.focus(); + }; + + const noticeText = intl.formatMessage({ id: "filterBar.restrictedFilters" }, { count }); + + const notice = ( + <> + + {noticeText} + + ); return ( - // the filter bar counts its rows by measuring its direct children, so the tooltip's own - // wrapper and its screen-reader copy have to sit inside a single one of them - + // the bar counts its rows by measuring its direct children, so keep the tooltip's own + // elements inside one of them + { + if (!open) { + setIsOpenedForAction(false); + } + }} + content={({ onClose, type }) => { + const reason = ( + + ); + if (type === "screen-reader") { + return reason; + } + + return withFocusInside( +
+ {reason} + {onRemove ? ( + { + focusNextControl(); + onClose(); + onRemove(); + }} + /> + ) : null} +
, + onClose, + ); + }} anchor={ - // focusable so the reason is reachable without a pointer; it opens nothing - - - - + onRemove ? ( + + ) : ( + + {notice} + + ) } /> diff --git a/libs/sdk-ui-dashboard/src/presentation/insightNotSavedDialog/InsightNotSavedDialog.tsx b/libs/sdk-ui-dashboard/src/presentation/insightNotSavedDialog/InsightNotSavedDialog.tsx new file mode 100644 index 00000000000..65db704f950 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/insightNotSavedDialog/InsightNotSavedDialog.tsx @@ -0,0 +1,72 @@ +// (C) 2026 GoodData Corporation + +import { useCallback, useState } from "react"; + +import { useIntl } from "react-intl"; + +import { ConfirmDialog, Typography, UiButton, UiIcon } from "@gooddata/sdk-ui-kit"; + +import { useDashboardDispatch, useDashboardSelector } from "../../model/react/DashboardStoreProvider.js"; +import { selectDraftInsightsUsedOnDashboard } from "../../model/store/insights/insightsSelectors.js"; +import { uiActions } from "../../model/store/ui/index.js"; +import { selectIsInsightNotSavedDialogOpen } from "../../model/store/ui/uiSelectors.js"; + +/** + * @internal + */ +export function InsightNotSavedDialog() { + const intl = useIntl(); + const dispatch = useDashboardDispatch(); + const [showVisualisationsList, setShowVisualisationsList] = useState(true); + + const isInsightNotSavedDialogOpen = useDashboardSelector(selectIsInsightNotSavedDialogOpen); + const submitDialog = useCallback(() => { + dispatch(uiActions.confirmInsightNotSavedDialogSubmit()); + }, [dispatch]); + const closeDialog = useCallback(() => dispatch(uiActions.closeInsightNotSavedDialog()), [dispatch]); + + const unsavedInsights = useDashboardSelector(selectDraftInsightsUsedOnDashboard); + + if (!isInsightNotSavedDialogOpen) { + return null; + } + + return ( + +
+ + {intl.formatMessage({ id: "insightNotSavedDialog.message" })} + + setShowVisualisationsList(!showVisualisationsList)} + /> + {showVisualisationsList ? ( +
    + {unsavedInsights.map((insight) => ( +
  • + {insight.insight.title} +
  • + ))} +
+ ) : null} +
+
+ ); +} diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/de-DE.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/de-DE.json index 07f946e637b..a20030cdf1f 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/de-DE.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/de-DE.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Ihre Testversion ist abgelaufen.", + "dashboard.contractExpired.trial.description": "Wenn Sie GoodData Cloud weiterhin nutzen möchten, kontaktieren Sie uns bitte.", + "dashboard.contractExpired.nonTrial.title": "Ihr Zugriff ist gesperrt.", + "dashboard.contractExpired.nonTrial.description": "Wenden Sie sich an den Kontoadministrator, um wieder Zugriff zu erhalten.", + "dashboard.contractExpired.buttonTitle": "Kontakt", "loading": "Lädt…", "untitled": "Unbenannt", "empty_value": "leerer Wert", + "deleted_value": "gelöschter Wert", "gs.list.cancel": "Abbrechen", "gs.list.apply": "Anwenden", "gs.list.only": "Nur", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interaktion konfiguriert.", "messages.drill.InteractionUpdatedSuccess": "Interaktion aktualisiert.", "filterBar.label": "Filter", + "filterBar.restrictedFilters.remove": "Eingeschränkte Filter entfernen", + "filterBar.restrictedFilters.reason": "{count, plural, one {Sie haben keinen Zugriff auf # Filter.} other {Sie haben keinen Zugriff auf # Filter.}}", + "filterBar.restrictedFilters": "{count, plural, one {# Filter wurde nicht angewendet} other {# Filter wurden nicht angewendet}}", "filterBar.showAll": "Alle anzeigen", "filterBar.showLess": "Weniger anzeigen", "filterBar.filterListAutoExpanded": "Filterliste wurde automatisch eingeblendet", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "Geplante Exporte", "deleteWidgetDialog.headline": "Widget entfernen?", "deleteWidgetDialog.objectsMessage": "Die folgenden Objekttypen, die mit dem Widget {title} verbunden sind, werden nicht mehr funktionieren:", + "deleteWidgetDialog.objectsMessage.restricted": "Die folgenden mit dem Element verknüpften Objekttypen funktionieren nicht mehr:", "deleteWidgetDialog.alerts": "Warnungen", "deleteWidgetDialog.schedules": "Geplante Exporte", "create.dashboard": "Dashboard erstellen", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} kann nicht nach {parentTitle} gefiltert werden. Es besteht keine Verbindung zwischen den Filterattributen und dem Datenmodell.", "attributesDropdown.details.dataset": "Dataset", "attributesDropdown.details.values": "Werte", + "attributesDropdown.details.definedAs": "Definiert als", "attributesDropdown.details.shortening_decoration": "…und {count} weitere", "layout.header.characters.left": "Noch {currentCharactersCount}/{maximumCharactersCount} Zeichen", "layout.header.add.title.placeholder": "Titel hier hinzufügen...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Die Visualisierung kann nicht nach {attributeName} gefiltert werden. Markierung des Kontrollfelds entfernen.", "configurationPanel.kpiCantBeFilteredByAttribute": " Leistungskennzahl kann nicht nach {attributeName} gefiltert werden. Markierung des Kontrollfelds entfernen.", "configurationPanel.vizCantBeFilteredByDate": "Die Visualisierung kann nicht nach Datum gefiltert werden. Markierung des Kontrollfelds entfernen.", + "configurationPanel.dateDatasetsLoadFailed": "Zugehörige Datumsdaten konnten nicht geladen werden. Schließen Sie den Konfigurationsbereich und versuchen Sie es erneut.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filtern ist auf diese Visualisierung nicht anwendbar.", "configurationPanel.mvfBlockedByRankingFilter": "Dieser Filter kann nicht angewendet werden, weil die Visualisierung einen Top-/Bottom-Werte-Filter verwendet. Entfernen Sie den Top-/Bottom-Werte-Filter, um diesen Filter anzuwenden.", "configurationPanel.mvfMissingMeasure": "Die in diesem Filter verwendete Metrik existiert im workspace nicht mehr.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "In die Visualisierung bohren", "configurationPanel.drillConfig.drillDown": "Nach unten verfeinern", "configurationPanel.drillConfig.disableDrillDownTooltip": "Drilldown ist in der Konfiguration dieser Visualisierung deaktiviert.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "In Details gehen ist für berechnete Attribute nicht verfügbar.", "configurationPanel.drillConfig.drillIntoUrl": "URL-Ansicht verfeinern", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Ungültige URL-Parameter", "configurationPanel.drillConfig.select": "Befehl auswählen…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Rich Text löschen?", "richText.deleteDialog.message": "Der Rich Text und sein gesamter Inhalt werden unwiderruflich gelöscht.", "richText.emptyContent": "Kein Rich-Text-Inhalt", + "visualizationSwitcher.restrictedEntry": "Eingeschränkt", "visualizationSwitcher.emptyContent": "Keine Visualisierung im Switcher", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisierungen", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisierungen", @@ -809,6 +823,7 @@ "granularity.hour": "Stunde", "granularity.minute": "Minute", "widget.options.menu.export": "Exportieren", + "insightMenu.restrictedWidget.options": "Optionen für Visualisierung mit eingeschränktem Zugriff", "menu.close": "Menü schließen", "controlButtons.asTable": "Als Tabelle anzeigen", "controlButtons.asOriginal": "Originalvisualisierung anzeigen", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-AU.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-AU.json index b50367c2d08..f6d140403e2 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-AU.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-AU.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Your trial has ended.", + "dashboard.contractExpired.trial.description": "Contact us if you would like to continue using GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Your access is locked.", + "dashboard.contractExpired.nonTrial.description": "Contact the account administrator to regain access.", + "dashboard.contractExpired.buttonTitle": "Contact us", "loading": "Loading…", "untitled": "Untitled", "empty_value": "empty value", + "deleted_value": "deleted value", "gs.list.cancel": "Cancel", "gs.list.apply": "Apply", "gs.list.only": "Only", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Setup sorted.", "messages.drill.InteractionUpdatedSuccess": "Interaction updated.", "filterBar.label": "Filters", + "filterBar.restrictedFilters.remove": "Remove access-restricted filters", + "filterBar.restrictedFilters.reason": "{count, plural, one {You do not have access to # filter.} other {You do not have access to # filters.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filter was not applied} other {# filters were not applied}}", "filterBar.showAll": "Display all", "filterBar.showLess": "Show less", "filterBar.filterListAutoExpanded": "Filter list has been automatically expanded", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "scheduled exports", "deleteWidgetDialog.headline": "Remove widget?", "deleteWidgetDialog.objectsMessage": "The following types of objects associated with the widget {title} will no longer function:", + "deleteWidgetDialog.objectsMessage.restricted": "The following types of objects associated with the widget will no longer function:", "deleteWidgetDialog.alerts": "alerts", "deleteWidgetDialog.schedules": "scheduled exports", "create.dashboard": "Create Dashboard", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} can't be filtered by {parentTitle}. There's no connection between the filter attributes in the data model.", "attributesDropdown.details.dataset": "Dataset", "attributesDropdown.details.values": "Values", + "attributesDropdown.details.definedAs": "Defined as", "attributesDropdown.details.shortening_decoration": "...and {count} more", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} characters left", "layout.header.add.title.placeholder": "Add Title here...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "The visualisation cannot be filtered by {attributeName}. Unselect the check box.", "configurationPanel.kpiCantBeFilteredByAttribute": "The kpi cannot be filtered by {attributeName}. Unselect the check box.", "configurationPanel.vizCantBeFilteredByDate": "The visualization cannot be filtered by Date. Unselect the check box.", + "configurationPanel.dateDatasetsLoadFailed": "Related date data could not be loaded. Close the configuration panel and try again.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filter is not applicable to this visualisation.", "configurationPanel.mvfBlockedByRankingFilter": "This filter can't be applied because the visualisation uses a Top/bottom values filter. Remove the Top/bottom values filter to apply this filter.", "configurationPanel.mvfMissingMeasure": "The metric used in this filter no longer exists in the workspace.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Drill into visualisation", "configurationPanel.drillConfig.drillDown": "Drill down", "configurationPanel.drillConfig.disableDrillDownTooltip": "Drill down is turned off in this visualisation's configuration.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Drill down is not available for computed attributes.", "configurationPanel.drillConfig.drillIntoUrl": "Drill into URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Invalid URL parameters", "configurationPanel.drillConfig.select": "Select action…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Delete rich text?", "richText.deleteDialog.message": "The rich text and all of its content will be permanently deleted.", "richText.emptyContent": "No rich text content", + "visualizationSwitcher.restrictedEntry": "Restricted", "visualizationSwitcher.emptyContent": "No visualisation in switcher", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisations", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisations", @@ -809,6 +823,7 @@ "granularity.hour": "hour", "granularity.minute": "minute", "widget.options.menu.export": "Export", + "insightMenu.restrictedWidget.options": "Restricted visualisation options", "menu.close": "Close menu", "controlButtons.asTable": "Display as table", "controlButtons.asOriginal": "Show original visualisation", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-GB.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-GB.json index 1501654bc25..6c67c58f9d2 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-GB.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-GB.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Your trial has ended.", + "dashboard.contractExpired.trial.description": "Contact us if you would like to continue using GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Your access is locked.", + "dashboard.contractExpired.nonTrial.description": "Contact the account administrator to regain access.", + "dashboard.contractExpired.buttonTitle": "Contact us", "loading": "Loading…", "untitled": "Untitled", "empty_value": "empty value", + "deleted_value": "deleted value", "gs.list.cancel": "Cancel", "gs.list.apply": "Apply", "gs.list.only": "Only", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interaction configured.", "messages.drill.InteractionUpdatedSuccess": "Interaction updated.", "filterBar.label": "Filters", + "filterBar.restrictedFilters.remove": "Remove restricted filters", + "filterBar.restrictedFilters.reason": "{count, plural, one {You do not have access to # filter.} other {You do not have access to # filters.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filter was not applied} other {# filters were not applied}}", "filterBar.showAll": "Show all", "filterBar.showLess": "Show less", "filterBar.filterListAutoExpanded": "Filter list has been automatically expanded", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "scheduled exports", "deleteWidgetDialog.headline": "Remove widget?", "deleteWidgetDialog.objectsMessage": "The following types of objects associated with the widget {title} will no longer function:", + "deleteWidgetDialog.objectsMessage.restricted": "The following types of objects associated with the widget will no longer function:", "deleteWidgetDialog.alerts": "alerts", "deleteWidgetDialog.schedules": "scheduled exports", "create.dashboard": "Create dashboard", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} cannot be filtered by {parentTitle}. There is no connection between the filter attributes in the data model.", "attributesDropdown.details.dataset": "Dataset", "attributesDropdown.details.values": "Values", + "attributesDropdown.details.definedAs": "Defined as", "attributesDropdown.details.shortening_decoration": "...and {count} more", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} characters left", "layout.header.add.title.placeholder": "Add Title here...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "The visualisation cannot be filtered by {attributeName}. Unselect the check box.", "configurationPanel.kpiCantBeFilteredByAttribute": "The kpi cannot be filtered by {attributeName}. Unselect the check box.", "configurationPanel.vizCantBeFilteredByDate": "The visualisation cannot be filtered by Date. Unselect the check box.", + "configurationPanel.dateDatasetsLoadFailed": "Unable to load related date data. Close the configuration panel and try again.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filter is not applicable to this visualisation.", "configurationPanel.mvfBlockedByRankingFilter": "This filter can't be applied because the visualisation uses a Top/bottom values filter. Remove the Top/bottom values filter to apply this filter.", "configurationPanel.mvfMissingMeasure": "The metric used in this filter no longer exists in the workspace.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Drill into visualisation", "configurationPanel.drillConfig.drillDown": "Drill down", "configurationPanel.drillConfig.disableDrillDownTooltip": "Drill down is turned off in this visualisation's configuration.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Drill down is unavailable for computed attributes.", "configurationPanel.drillConfig.drillIntoUrl": "Drill into URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Invalid URL parameters", "configurationPanel.drillConfig.select": "Choose action…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Delete rich text?", "richText.deleteDialog.message": "The rich text and all of its content will be permanently deleted.", "richText.emptyContent": "No rich text content", + "visualizationSwitcher.restrictedEntry": "Restricted", "visualizationSwitcher.emptyContent": "No visualisation in switcher", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisations", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisations", @@ -809,6 +823,7 @@ "granularity.hour": "hour", "granularity.minute": "minute", "widget.options.menu.export": "Export", + "insightMenu.restrictedWidget.options": "Restricted visualisation options", "menu.close": "Close menu", "controlButtons.asTable": "Display as table", "controlButtons.asOriginal": "Show original visualisation", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json index ea94db9149d..10705ef20be 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/en-US.json @@ -71,6 +71,26 @@ "text": "Dashboard save failed. Remove any unavailable visualizations, then try again, or contact your administrator.", "crowdinContext": "Error notification shown when saving a dashboard fails" }, + "insightNotSavedDialog.headline": { + "text": "Save dashboard", + "crowdinContext": "Headline shown when dashboard save is blocked because it contains draft visualizations" + }, + "insightNotSavedDialog.message": { + "text": "The following objects will be saved together with the dashboard.", + "crowdinContext": "Message shown when dashboard save is blocked due to draft visualizations" + }, + "insightNotSavedDialog.visualisations": { + "text": "Visualizations ({count})", + "crowdinContext": "Title of the list of draft visualizations. {count} is the number of draft visualizations." + }, + "insightNotSavedDialog.close": { + "text": "Close", + "crowdinContext": "Button label used to close the draft-visualization warning dialog" + }, + "insightNotSavedDialog.saveAll": { + "text": "Save all", + "crowdinContext": "Button label used to save all draft visualizations" + }, "messages.dashboard.incompatibleDefaultFilters.title": { "text": "Warning Some dashboard filters couldn’t be applied. Check the filters to make sure you’re viewing the right data.", "crowdinContext": "Title of the warning message shown if default filter overrides are incompatible with the current dashboard filters." @@ -119,6 +139,10 @@ "text": "Filters", "crowdinContext": "Label for the filter bar section" }, + "filterBar.restrictedFilters.remove": { + "text": "Remove restricted filters", + "crowdinContext": "Action in the filter bar notice tooltip, shown in edit mode. Removes the filters the user is not allowed to access from the dashboard for good." + }, "filterBar.restrictedFilters.reason": { "text": "{count, plural, one {You don't have access to # filter.} other {You don't have access to # filters.}}", "crowdinContext": "Tooltip on the filter bar notice, giving the reason why that many dashboard filters were left out. Uses pluralization based on the number of filters." @@ -472,8 +496,8 @@ "crowdinContext": "Label of the default time zone picker in the dashboard settings dialog" }, "settingsDashboardDialog.section.timezone.defaultTimezone.tooltip": { - "text": "Time zone used for date filters and date values on this dashboard.", - "crowdinContext": "Tooltip of the default time zone picker in the dashboard settings dialog" + "text": "Time zone used for date filters and date values on this dashboard. Learn more", + "crowdinContext": "Tooltip of the default time zone picker in the dashboard settings dialog, with a learn more link to the documentation" }, "settingsDashboardDialog.section.timezone.defaultTimezone.help": { "text": "More information", @@ -583,6 +607,10 @@ "text": "Following types objects associated with the widget {title} will no longer work:", "crowdinContext": "Warning message about objects that will be affected when deleting a widget. The {title} placeholder shows the specific widget name." }, + "deleteWidgetDialog.objectsMessage.restricted": { + "text": "The following types of objects associated with the widget will no longer work:", + "crowdinContext": "The same warning for a widget the user has no access to, which therefore must not be named." + }, "deleteWidgetDialog.alerts": { "text": "alerts", "crowdinContext": "Label for alerts that will be affected by widget removal" @@ -3287,6 +3315,10 @@ "text": "Export", "crowdinContext": "Export to file with XLSX extension." }, + "insightMenu.restrictedWidget.options": { + "text": "Restricted visualization options", + "crowdinContext": "Accessible name of the menu of a visualization the user has no access to; it must not name the visualization." + }, "menu.close": { "text": "Close menu", "crowdinContext": "Button label to close a menu" diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-419.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-419.json index 654654cd27c..94c532f0473 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-419.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-419.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Su período de prueba ha finalizado.", + "dashboard.contractExpired.trial.description": "Contacte con nosotros si desea seguir utilizando GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Su acceso está bloqueado.", + "dashboard.contractExpired.nonTrial.description": "Contacte con el administrador de la cuenta para volver a tener acceso.", + "dashboard.contractExpired.buttonTitle": "Contacto", "loading": "Cargando…", "untitled": "Sin título", "empty_value": "valor vacío", + "deleted_value": "valor eliminado", "gs.list.cancel": "Cancelar", "gs.list.apply": "Aplicar", "gs.list.only": "Solo", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interacción configurada.", "messages.drill.InteractionUpdatedSuccess": "Interacción actualizada.", "filterBar.label": "Filtros", + "filterBar.restrictedFilters.remove": "Quitar filtros restringidos", + "filterBar.restrictedFilters.reason": "{count, plural, one {No tiene acceso a # filtro.} other {No tiene acceso a # filtros.}}", + "filterBar.restrictedFilters": "{count, plural, one {No se aplicó # filtro} other {No se aplicaron # filtros}}", "filterBar.showAll": "Mostrar todo", "filterBar.showLess": "Mostrar menos", "filterBar.filterListAutoExpanded": "La lista de filtros se expandió automáticamente", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "exportaciones programadas", "deleteWidgetDialog.headline": "¿Quitar el widget?", "deleteWidgetDialog.objectsMessage": "Los siguientes tipos de objetos asociados al widget {title} ya no funcionarán:", + "deleteWidgetDialog.objectsMessage.restricted": "Los siguientes tipos de objetos asociados con el elemento ya no funcionarán:", "deleteWidgetDialog.alerts": "alertas", "deleteWidgetDialog.schedules": "exportaciones programadas", "create.dashboard": "Crear panel", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} no se puede filtrar por {parentTitle}. No hay ninguna conexión entre los atributos de filtro en el modelo de datos.", "attributesDropdown.details.dataset": "Conjunto de datos", "attributesDropdown.details.values": "Valores", + "attributesDropdown.details.definedAs": "Definido como", "attributesDropdown.details.shortening_decoration": "…y {count} más", "layout.header.characters.left": "Quedan {currentCharactersCount} / {maximumCharactersCount} caracteres", "layout.header.add.title.placeholder": "Agregar título aquí...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "La visualización no puede filtrarse por {attributeName}. Desmarque la casilla de verificación.", "configurationPanel.kpiCantBeFilteredByAttribute": "El KPI no se puede filtrar por {attributeName}. Desmarque la casilla de verificación.", "configurationPanel.vizCantBeFilteredByDate": "La visualización no puede filtrarse por Fecha. Desmarque la casilla de verificación.", + "configurationPanel.dateDatasetsLoadFailed": "No se pudieron cargar los datos de fecha relacionados. Cierre el panel de configuración e inténtelo de nuevo.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "El filtro no se puede aplicar a esta visualización.", "configurationPanel.mvfBlockedByRankingFilter": "Este filtro no se puede aplicar porque la visualización usa un filtro de valores superiores/inferiores. Quite el filtro de valores superiores/inferiores para aplicar este filtro.", "configurationPanel.mvfMissingMeasure": "La métrica usada en este filtro ya no existe en el espacio de trabajo.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Profundizar en la visualización", "configurationPanel.drillConfig.drillDown": "Profundización", "configurationPanel.drillConfig.disableDrillDownTooltip": "La opción de profundizar está desactivada en la configuración de esta visualización.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Detallar no está disponible para los atributos calculados.", "configurationPanel.drillConfig.drillIntoUrl": "Explorar hasta la URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parámetros de URL no válidos", "configurationPanel.drillConfig.select": "Seleccione una acción…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "¿Eliminar texto enriquecido?", "richText.deleteDialog.message": "El texto enriquecido y todo su contenido se eliminará de forma permanente.", "richText.emptyContent": "Sin contenido de texto enriquecido", + "visualizationSwitcher.restrictedEntry": "Restringido", "visualizationSwitcher.emptyContent": "No hay visualización en el conmutador", "visualizationSwitcherToolbar.visualizationsList.header": "Visualizaciones", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualizaciones", @@ -809,6 +823,7 @@ "granularity.hour": "hora", "granularity.minute": "minuto", "widget.options.menu.export": "Exportar", + "insightMenu.restrictedWidget.options": "Opciones de la visualización restringida", "menu.close": "Cerrar menú", "controlButtons.asTable": "Mostrar como tabla", "controlButtons.asOriginal": "Mostrar visualización original", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-ES.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-ES.json index 5710f8fc645..29ee19b47ec 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-ES.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/es-ES.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Su prueba ha terminado.", + "dashboard.contractExpired.trial.description": "Contacte con nosotros si desea seguir utilizando GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Su acceso se ha bloqueado.", + "dashboard.contractExpired.nonTrial.description": "Contacte con el administrador de la cuenta para volver a tener acceso.", + "dashboard.contractExpired.buttonTitle": "Contacte con nosotros", "loading": "Cargando…", "untitled": "Sin título", "empty_value": "valor vacío", + "deleted_value": "valor eliminado", "gs.list.cancel": "Cancelar", "gs.list.apply": "Aplicar", "gs.list.only": "Solo", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interacción configurada.", "messages.drill.InteractionUpdatedSuccess": "Interacción actualizada.", "filterBar.label": "Filtros", + "filterBar.restrictedFilters.remove": "Quitar filtros restringidos", + "filterBar.restrictedFilters.reason": "{count, plural, one {No tiene acceso a # filtro.} other {No tiene acceso a # filtros.}}", + "filterBar.restrictedFilters": "{count, plural, one {No se aplicó # filtro} other {No se aplicaron # filtros}}", "filterBar.showAll": "Mostrar todo", "filterBar.showLess": "Mostrar menos", "filterBar.filterListAutoExpanded": "La lista de filtros se ha ampliado automáticamente", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "exportaciones programadas", "deleteWidgetDialog.headline": "¿Quitar el widget?", "deleteWidgetDialog.objectsMessage": "Los siguientes tipos de objetos asociados al widget {title} ya no funcionarán:", + "deleteWidgetDialog.objectsMessage.restricted": "Los siguientes tipos de objetos asociados al elemento ya no funcionarán:", "deleteWidgetDialog.alerts": "alertas", "deleteWidgetDialog.schedules": "exportaciones programadas", "create.dashboard": "Crear panel", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} no se puede filtrar por {parentTitle}. No hay ninguna conexión entre los atributos de filtro en el modelo de datos.", "attributesDropdown.details.dataset": "Conjunto de datos", "attributesDropdown.details.values": "Valores", + "attributesDropdown.details.definedAs": "Definido como", "attributesDropdown.details.shortening_decoration": "…y {count} más", "layout.header.characters.left": "Quedan {currentCharactersCount} / {maximumCharactersCount} caracteres", "layout.header.add.title.placeholder": "Añadir título aquí...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "La visualización no puede filtrarse por {attributeName}. Desmarque la casilla de verificación.", "configurationPanel.kpiCantBeFilteredByAttribute": "El KPI no se puede filtrar por {attributeName}. Desmarque la casilla de verificación.", "configurationPanel.vizCantBeFilteredByDate": "La visualización no puede filtrarse por Fecha. Desmarque la casilla de verificación.", + "configurationPanel.dateDatasetsLoadFailed": "No se pudieron cargar los datos de fecha relacionados. Cierre el panel de configuración e inténtelo de nuevo.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "El filtro no se puede aplicar a esta visualización.", "configurationPanel.mvfBlockedByRankingFilter": "Este filtro no se puede aplicar porque la visualización usa un filtro de valores superiores/inferiores. Quite el filtro de valores superiores/inferiores para aplicar este filtro.", "configurationPanel.mvfMissingMeasure": "La métrica usada en este filtro ya no existe en el espacio de trabajo.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Profundice en la visualización", "configurationPanel.drillConfig.drillDown": "Exploración vertical", "configurationPanel.drillConfig.disableDrillDownTooltip": "La exploración está desactivada en la configuración de esta visualización.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "La opción de detallar no está disponible para los atributos calculados.", "configurationPanel.drillConfig.drillIntoUrl": "Explorar hasta la URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parámetros de URL no válidos", "configurationPanel.drillConfig.select": "Elija una acción…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "¿Eliminar texto enriquecido?", "richText.deleteDialog.message": "El texto enriquecido y todo su contenido se eliminará de forma permanente.", "richText.emptyContent": "Sin contenido de texto enriquecido", + "visualizationSwitcher.restrictedEntry": "Restringido", "visualizationSwitcher.emptyContent": "No hay visualización en el conmutador", "visualizationSwitcherToolbar.visualizationsList.header": "Visualizaciones", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualizaciones", @@ -809,6 +823,7 @@ "granularity.hour": "hora", "granularity.minute": "minuto", "widget.options.menu.export": "Exportar", + "insightMenu.restrictedWidget.options": "Opciones de la visualización restringida", "menu.close": "Cerrar menú", "controlButtons.asTable": "Mostrar como tabla", "controlButtons.asOriginal": "Mostrar visualización original", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fi-FI.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fi-FI.json index b1fcc8bed8e..db28053a663 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fi-FI.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fi-FI.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Kokeilusi on päättynyt.", + "dashboard.contractExpired.trial.description": "Ota yhteyttä, jos haluat jatkaa GoodData Cloudin käyttöä.", + "dashboard.contractExpired.nonTrial.title": "Pääsysi on lukittu.", + "dashboard.contractExpired.nonTrial.description": "Ota yhteyttä tilin ylläpitäjään pääsyn palauttamiseksi.", + "dashboard.contractExpired.buttonTitle": "Ota meihin yhteyttä", "loading": "Ladataan…", "untitled": "Nimeämätön", "empty_value": "tyhjä arvo", + "deleted_value": "poistettu arvo", "gs.list.cancel": "Peruuta", "gs.list.apply": "Sovella", "gs.list.only": "Vain", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Vuorovaikutus määritetty.", "messages.drill.InteractionUpdatedSuccess": "Vuorovaikutus päivitetty.", "filterBar.label": "Suodattimet", + "filterBar.restrictedFilters.remove": "Poista rajoitetut suodattimet", + "filterBar.restrictedFilters.reason": "{count, plural, one {Sinulla ei ole käyttöoikeutta # suodattimeen.} other {Sinulla ei ole käyttöoikeutta # suodattimeen.}}", + "filterBar.restrictedFilters": "{count, plural, one {# suodatin jätettiin käyttämättä} other {# suodatinta jätettiin käyttämättä}}", "filterBar.showAll": "Näytä kaikki", "filterBar.showLess": "Näytä vähemmän", "filterBar.filterListAutoExpanded": "Suodatinluettelo on laajennettu automaattisesti", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "suunnitellut viennit", "deleteWidgetDialog.headline": "Poistetaanko widgetti?", "deleteWidgetDialog.objectsMessage": "Seuraavat tyyppiset pienoissovellukseen liittyvät objektit eivät {title} enää toimi:", + "deleteWidgetDialog.objectsMessage.restricted": "Seuraavat elementtiin liittyvät objektityypit eivät enää toimi:", "deleteWidgetDialog.alerts": "hälytykset", "deleteWidgetDialog.schedules": "suunnitellut viennit", "create.dashboard": "Luo kojelauta", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} ei voida suodattaa {parentTitle} avulla. Tietomallissa ei ole yhteyttä suodatinattribuuttien välillä.", "attributesDropdown.details.dataset": "Tietojoukko", "attributesDropdown.details.values": "Arvot", + "attributesDropdown.details.definedAs": "Määritelty muodossa", "attributesDropdown.details.shortening_decoration": "…ja {count} lisää", "layout.header.characters.left": "{currentCharactersCount} /{maximumCharactersCount} merkkiä jäljellä", "layout.header.add.title.placeholder": "Lisää otsikko tähän...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Visualisointia ei voida suodattaa {attributeName}. Poista valintaruudun valinta.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI:ta ei voi suodattaa {attributeName}. Poista valintamerkki.", "configurationPanel.vizCantBeFilteredByDate": "Visualisointia ei voida suodattaa päivämäärän mukaan. Poista valinta valintaruudusta.", + "configurationPanel.dateDatasetsLoadFailed": "Liittyviä päivämäärätietoja ei voitu ladata. Sulje määrityspaneeli ja yritä uudelleen.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Suodatin ei sovellu tähän visualisointiin.", "configurationPanel.mvfBlockedByRankingFilter": "Tätä suodatinta ei voi käyttää, koska visualisointi käyttää Ylä-/ala-arvojen suodatinta. Poista Ylä-/ala-arvojen suodatin, jotta voit käyttää tätä suodatinta.", "configurationPanel.mvfMissingMeasure": "Tässä suodattimessa käytettyä metriikkaa ei enää ole työtilassa.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Poraa visualisointiin", "configurationPanel.drillConfig.drillDown": "Poraa alas", "configurationPanel.drillConfig.disableDrillDownTooltip": "Poraus on pois päältä tämän visualisoinnin kokoonpanossa.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Poraaminen syvemmälle ei ole käytettävissä laskennallisille attribuuteille.", "configurationPanel.drillConfig.drillIntoUrl": "Poraa URL-osoitteeseen", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Virheelliset URL-parametrit", "configurationPanel.drillConfig.select": "Valitse toiminto...", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Poista rich text?", "richText.deleteDialog.message": "Rich text ja sen kaikki sisältö poistetaan peruuttamattomasti.", "richText.emptyContent": "Ei rich text -sisältöä", + "visualizationSwitcher.restrictedEntry": "Rajoitettu", "visualizationSwitcher.emptyContent": "Ei visualisointia vaihtimessa", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisoinnit", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisoinnit", @@ -809,6 +823,7 @@ "granularity.hour": "tunti", "granularity.minute": "minuutti", "widget.options.menu.export": "Vie", + "insightMenu.restrictedWidget.options": "Rajoitetun visualisoinnin asetukset", "menu.close": "Sulje valikko", "controlButtons.asTable": "Näytä taulukkona", "controlButtons.asOriginal": "Näytä alkuperäinen visualisointi", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-CA.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-CA.json index 92bd4754e58..bca0ad091e7 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-CA.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-CA.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Votre essai est terminé.", + "dashboard.contractExpired.trial.description": "Contactez-nous si vous souhaitez continuer à utiliser GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Votre accès est bloqué.", + "dashboard.contractExpired.nonTrial.description": "Contactez l’administrateur du compte pour récupérer l’accès.", + "dashboard.contractExpired.buttonTitle": "Contactez-nous", "loading": "Chargement en cours…", "untitled": "Sans titre", "empty_value": "valeur vide", + "deleted_value": "valeur supprimée", "gs.list.cancel": "Annuler", "gs.list.apply": "Appliquer", "gs.list.only": "Uniquement", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interaction configurée", "messages.drill.InteractionUpdatedSuccess": "Interaction modifiée", "filterBar.label": "Filtres", + "filterBar.restrictedFilters.remove": "Supprimer les filtres restreints", + "filterBar.restrictedFilters.reason": "{count, plural, one {Vous n’avez pas accès à # filtre.} other {Vous n’avez pas accès à # filtres.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtre n’a pas été appliqué} other {# filtres n’ont pas été appliqués}}", "filterBar.showAll": "Afficher tout", "filterBar.showLess": "Montrer moins", "filterBar.filterListAutoExpanded": "La liste de filtres a été automatiquement développée", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "Exportations programmées", "deleteWidgetDialog.headline": "Supprimer le widget?", "deleteWidgetDialog.objectsMessage": "Les types d’objets suivants associés au widget {title} ne fonctionneront plus :", + "deleteWidgetDialog.objectsMessage.restricted": "Les types d’objets suivants associés à l’élément ne fonctionneront plus :", "deleteWidgetDialog.alerts": "Alertes", "deleteWidgetDialog.schedules": "Exportations programmées", "create.dashboard": "Créer un tableau de bord", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} ne peut pas être filtré par {parentTitle}. Il n’existe aucune connexion entre les attributs du filtre dans le modèle de données.", "attributesDropdown.details.dataset": "Ensemble de données", "attributesDropdown.details.values": "Valeurs", + "attributesDropdown.details.definedAs": "Défini comme", "attributesDropdown.details.shortening_decoration": "…et {count} de plus", "layout.header.characters.left": "{currentCharactersCount}/{maximumCharactersCount} caractères restant", "layout.header.add.title.placeholder": "Ajouter un titre ici...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "La visualisation ne peut pas être filtrée par {attributeName}. Désélectionnez la case à cocher.", "configurationPanel.kpiCantBeFilteredByAttribute": "Le KPI ne peut pas être filtré par {attributeName}. Décochez la case.", "configurationPanel.vizCantBeFilteredByDate": "La visualisation ne peut pas être filtrée par la Date. Désélectionnez la case à cocher.", + "configurationPanel.dateDatasetsLoadFailed": "Les données de date associées n’ont pas pu être chargées. Fermez le panneau de configuration et réessayez.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Le filtre ne s’applique pas à cette visualisation.", "configurationPanel.mvfBlockedByRankingFilter": "Ce filtre ne peut pas être appliqué, car la visualisation utilise un filtre des valeurs supérieures/inférieures. Supprimez le filtre des valeurs supérieures/inférieures pour appliquer ce filtre.", "configurationPanel.mvfMissingMeasure": "La métrique utilisée dans ce filtre n’existe plus dans l’espace de travail.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Explorer plus en détail la visualisation", "configurationPanel.drillConfig.drillDown": "Exploration descendante", "configurationPanel.drillConfig.disableDrillDownTooltip": "L’exploration en profondeur est désactivée dans la configuration de cette visualisation.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Explorer en détail n’est pas disponible pour les attributs calculés.", "configurationPanel.drillConfig.drillIntoUrl": "Explorer l’URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Paramètres URL non valides", "configurationPanel.drillConfig.select": "Choisir une action…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Supprimer le texte enrichi ?", "richText.deleteDialog.message": "Le texte enrichi et tout son contenu seront supprimés de manière irréversible.", "richText.emptyContent": "Pas de texte enrichi", + "visualizationSwitcher.restrictedEntry": "Restreint", "visualizationSwitcher.emptyContent": "Pas de visualisation dans le sélecteur", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisations", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisations", @@ -809,6 +823,7 @@ "granularity.hour": "heure", "granularity.minute": "minute", "widget.options.menu.export": "Exporter", + "insightMenu.restrictedWidget.options": "Options de la visualisation restreinte", "menu.close": "Fermer le menu", "controlButtons.asTable": "Afficher sous forme de tableau", "controlButtons.asOriginal": "Afficher la visualisation originale", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-FR.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-FR.json index 675436f844e..91b96ae3eac 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-FR.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/fr-FR.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Votre essai est terminé.", + "dashboard.contractExpired.trial.description": "Contactez-nous si vous souhaitez continuer à utiliser GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Votre accès est bloqué.", + "dashboard.contractExpired.nonTrial.description": "Contactez l'administrateur du compte pour récupérer l'accès.", + "dashboard.contractExpired.buttonTitle": "Contactez-nous", "loading": "Chargement en cours…", "untitled": "Sans titre", "empty_value": "valeur vide", + "deleted_value": "valeur supprimée", "gs.list.cancel": "Annuler", "gs.list.apply": "Appliquer", "gs.list.only": "Uniquement", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interaction configurée", "messages.drill.InteractionUpdatedSuccess": "Interaction modifiée", "filterBar.label": "Filtres", + "filterBar.restrictedFilters.remove": "Supprimer les filtres restreints", + "filterBar.restrictedFilters.reason": "{count, plural, one {Vous n’avez pas accès à # filtre.} other {Vous n’avez pas accès à # filtres.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtre n’a pas été appliqué} other {# filtres n’ont pas été appliqués}}", "filterBar.showAll": "Afficher tout", "filterBar.showLess": "Montrer moins", "filterBar.filterListAutoExpanded": "La liste de filtres a été automatiquement développée", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "exportations programmées", "deleteWidgetDialog.headline": "Supprimer le widget ?", "deleteWidgetDialog.objectsMessage": "Les types d'objets suivants associés au widget {title} ne fonctionneront plus :", + "deleteWidgetDialog.objectsMessage.restricted": "Les types d’objets suivants associés à l’élément ne fonctionneront plus :", "deleteWidgetDialog.alerts": "alertes", "deleteWidgetDialog.schedules": "exportations programmées", "create.dashboard": "Créer un tableau de bord", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} ne peut pas être filtré par {parentTitle}. Il n'existe aucune connexion entre les attributs du filtre dans le modèle de données.", "attributesDropdown.details.dataset": "Ensemble de données", "attributesDropdown.details.values": "Valeurs", + "attributesDropdown.details.definedAs": "Défini comme", "attributesDropdown.details.shortening_decoration": "…et {count} de plus", "layout.header.characters.left": "{currentCharactersCount}/{maximumCharactersCount} caractères restant", "layout.header.add.title.placeholder": "Ajouter un titre ici...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "La visualisation ne peut pas être filtrée par {attributeName}. Désélectionnez la case à cocher.", "configurationPanel.kpiCantBeFilteredByAttribute": "Le KPI ne peut pas être filtré par {attributeName}. Décochez la case.", "configurationPanel.vizCantBeFilteredByDate": "La visualisation ne peut pas être filtrée par date. Désélectionnez la case à cocher.", + "configurationPanel.dateDatasetsLoadFailed": "Les données de date associées n’ont pas pu être chargées. Fermez le panneau de configuration et réessayez.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Le filtre ne peut pas être appliqué à cette visualisation.", "configurationPanel.mvfBlockedByRankingFilter": "Ce filtre ne peut pas être appliqué, car la visualisation utilise un filtre de valeurs Top/Bottom. Supprimez le filtre de valeurs Top/Bottom pour appliquer ce filtre.", "configurationPanel.mvfMissingMeasure": "La métrique utilisée dans ce filtre n’existe plus dans l’espace de travail.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Explorer la visualisation", "configurationPanel.drillConfig.drillDown": "Exploration descendante", "configurationPanel.drillConfig.disableDrillDownTooltip": "L'exploration descendante est désactivée dans la configuration de cette visualisation.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Explorer en détail n’est pas disponible pour les attributs calculés.", "configurationPanel.drillConfig.drillIntoUrl": "Explorer l’URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Paramètres d'URL non valides", "configurationPanel.drillConfig.select": "Choisir une action…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Supprimer le texte enrichi ?", "richText.deleteDialog.message": "Le texte enrichi et tout son contenu seront supprimés de manière irréversible.", "richText.emptyContent": "Pas de texte enrichi", + "visualizationSwitcher.restrictedEntry": "Restreint", "visualizationSwitcher.emptyContent": "Pas de visualisation dans le commutateur", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisations", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisations", @@ -809,6 +823,7 @@ "granularity.hour": "heure", "granularity.minute": "minute", "widget.options.menu.export": "Exporter", + "insightMenu.restrictedWidget.options": "Options de visualisation restreinte", "menu.close": "Fermer le menu", "controlButtons.asTable": "Afficher sous forme de tableau", "controlButtons.asOriginal": "Afficher la visualisation originale", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/id-ID.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/id-ID.json index 3bdd7e3abea..47d65dcb2fd 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/id-ID.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/id-ID.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Uji coba Anda telah berakhir.", + "dashboard.contractExpired.trial.description": "Hubungi kami jika Anda ingin terus menggunakan GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Akses Anda dikunci.", + "dashboard.contractExpired.nonTrial.description": "Hubungi administrator akun untuk mendapatkan kembali akses.", + "dashboard.contractExpired.buttonTitle": "Hubungi kami", "loading": "Memuat…", "untitled": "Tanpa judul", "empty_value": "nilai kosong", + "deleted_value": "nilai yang dihapus", "gs.list.cancel": "Batal", "gs.list.apply": "Terapkan", "gs.list.only": "Hanya", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interaksi dikonfigurasi.", "messages.drill.InteractionUpdatedSuccess": "Interaksi diperbarui.", "filterBar.label": "Filter", + "filterBar.restrictedFilters.remove": "Hapus filter terbatas", + "filterBar.restrictedFilters.reason": "{count, plural, other {Anda tidak memiliki akses ke # filter.}}", + "filterBar.restrictedFilters": "{count, plural, other {# filter tidak diterapkan}}", "filterBar.showAll": "Tampilkan semua", "filterBar.showLess": "Tampilkan lebih sedikit", "filterBar.filterListAutoExpanded": "Daftar filter telah diperluas secara otomatis", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "ekspor terjadwal", "deleteWidgetDialog.headline": "Hapus widget?", "deleteWidgetDialog.objectsMessage": "Jenis objek berikut yang terkait dengan widget {title} tidak akan lagi berfungsi:", + "deleteWidgetDialog.objectsMessage.restricted": "Jenis objek berikut yang terkait dengan widget tidak akan lagi berfungsi:", "deleteWidgetDialog.alerts": "peringatan", "deleteWidgetDialog.schedules": "ekspor terjadwal", "create.dashboard": "Buat dasbor", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} tidak dapat difilter oleh {parentTitle}. Tidak ada koneksi antara atribut filter dalam model data.", "attributesDropdown.details.dataset": "Dataset", "attributesDropdown.details.values": "Nilai", + "attributesDropdown.details.definedAs": "Didefinisikan sebagai", "attributesDropdown.details.shortening_decoration": "…dan {count} lainnya", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} karakter tersisa", "layout.header.add.title.placeholder": "Tambahkan Judul di sini...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Visualisasi tidak dapat difilter oleh {attributeName}. Batalkan pilihan kotak centang.", "configurationPanel.kpiCantBeFilteredByAttribute": "kpi tidak dapat difilter oleh {attributeName}. Batalkan pilihan kotak centang.", "configurationPanel.vizCantBeFilteredByDate": "Visualisasi tidak dapat difilter oleh Tanggal. Batalkan pilihan kotak centang.", + "configurationPanel.dateDatasetsLoadFailed": "Data tanggal terkait tidak dapat dimuat. Tutup panel konfigurasi dan coba lagi.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filter tidak dapat diterapkan pada visualisasi ini.", "configurationPanel.mvfBlockedByRankingFilter": "Filter ini tidak dapat diterapkan karena visualisasi menggunakan filter nilai Teratas/Terbawah. Hapus filter nilai Teratas/Terbawah untuk menerapkan filter ini.", "configurationPanel.mvfMissingMeasure": "Metrik yang digunakan dalam filter ini tidak lagi ada di ruang kerja.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Telusuri ke visualisasi", "configurationPanel.drillConfig.drillDown": "Telusuri ke bawah", "configurationPanel.drillConfig.disableDrillDownTooltip": "Telusuri ke bawah dimatikan dalam konfigurasi visualisasi ini.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Telusuri ke bawah tidak tersedia untuk atribut terkomputasi.", "configurationPanel.drillConfig.drillIntoUrl": "Telusuri ke URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parameter URL tidak valid", "configurationPanel.drillConfig.select": "Pilih tindakan…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Hapus rich text?", "richText.deleteDialog.message": "Rich text dan semua kontennya akan dihapus secara permanen.", "richText.emptyContent": "Tidak ada konten rich text", + "visualizationSwitcher.restrictedEntry": "Dibatasi", "visualizationSwitcher.emptyContent": "Tidak ada visualisasi dalam pengalih", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisasi", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisasi", @@ -809,6 +823,7 @@ "granularity.hour": "jam", "granularity.minute": "menit", "widget.options.menu.export": "Ekspor", + "insightMenu.restrictedWidget.options": "Opsi visualisasi terbatas", "menu.close": "Tutup menu", "controlButtons.asTable": "Tampilkan sebagai tabel", "controlButtons.asOriginal": "Tampilkan visualisasi asli", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/it-IT.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/it-IT.json index c94c6898979..3159a56fcbf 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/it-IT.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/it-IT.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Il periodo di prova è terminato.", + "dashboard.contractExpired.trial.description": "È possibile contattarci qualora si desiderasse continuare a utilizzare GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "L'accesso è bloccato.", + "dashboard.contractExpired.nonTrial.description": "Contattare l'amministratore dell'account per recuperare l'accesso.", + "dashboard.contractExpired.buttonTitle": "Contattaci", "loading": "Caricamento…", "untitled": "Senza titolo", "empty_value": "Valore vuoto", + "deleted_value": "valore eliminato", "gs.list.cancel": "Annulla", "gs.list.apply": "Applica", "gs.list.only": "Solo", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interazione configurata.", "messages.drill.InteractionUpdatedSuccess": "Interazione aggiornata.", "filterBar.label": "Filtri", + "filterBar.restrictedFilters.remove": "Rimuovi i filtri con restrizioni", + "filterBar.restrictedFilters.reason": "{count, plural, one {Non disponi dell'accesso a # filtro.} other {Non disponi dell'accesso a # filtri.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtro non è stato applicato} other {# filtri non sono stati applicati}}", "filterBar.showAll": "Mostra tutto", "filterBar.showLess": "Mostra di meno", "filterBar.filterListAutoExpanded": "L'elenco dei filtri è stato espanso automaticamente", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "esportazioni programmate", "deleteWidgetDialog.headline": "Rimuovere il widget?", "deleteWidgetDialog.objectsMessage": "I seguenti tipi di oggetti associati al widget {title} non funzionano più:", + "deleteWidgetDialog.objectsMessage.restricted": "I seguenti tipi di oggetti associati all'elemento non funzioneranno più:", "deleteWidgetDialog.alerts": "avvisi", "deleteWidgetDialog.schedules": "esportazioni programmate", "create.dashboard": "Creare cruscotto", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} non può essere filtrato per {parentTitle}. Non esiste alcun collegamento tra gli attributi dei filtri nel modello dei dati.", "attributesDropdown.details.dataset": "Set di dati", "attributesDropdown.details.values": "Valori", + "attributesDropdown.details.definedAs": "Definito come", "attributesDropdown.details.shortening_decoration": "…e altri {count}", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} caratteri rimasti", "layout.header.add.title.placeholder": "Aggiungi titolo qui...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "La visualizzazione non può essere filtrata da {attributeName}. Deselezionare la casella di controllo.", "configurationPanel.kpiCantBeFilteredByAttribute": "Il KPI non può essere filtrato per {attributeName}. Deselezionare la casella di controllo.", "configurationPanel.vizCantBeFilteredByDate": "La visualizzazione non può essere filtrata per data. Deselezionare la casella di controllo.", + "configurationPanel.dateDatasetsLoadFailed": "Non è stato possibile caricare i dati relativi alle date. Chiudere il pannello di configurazione e riprovare.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Il filtro non può essere applicato a questa visualizzazione.", "configurationPanel.mvfBlockedByRankingFilter": "Questo filtro non può essere applicato perché la visualizzazione usa un filtro Valori superiori/inferiori. Rimuovi il filtro Valori superiori/inferiori per applicare questo filtro.", "configurationPanel.mvfMissingMeasure": "La metrica usata in questo filtro non esiste più nell'area di lavoro.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Approfondimento della visualizzazione", "configurationPanel.drillConfig.drillDown": "Drill-down", "configurationPanel.drillConfig.disableDrillDownTooltip": "Il drill down è disattivato nella configurazione di questa visualizzazione.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Approfondisci non è disponibile per gli attributi calcolati.", "configurationPanel.drillConfig.drillIntoUrl": "Fai il drill dell'URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parametri dell'URL non validi", "configurationPanel.drillConfig.select": "Scegli azione...", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Eliminare il testo formattato?", "richText.deleteDialog.message": "Il testo formattato e tutti i suoi contenuti verranno eliminati in modo irreversibile.", "richText.emptyContent": "Nessun contenuto in testo formattato", + "visualizationSwitcher.restrictedEntry": "Limitato", "visualizationSwitcher.emptyContent": "Nessuna visualizzazione nel selettore", "visualizationSwitcherToolbar.visualizationsList.header": "Visualizzazioni", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualizzazioni", @@ -809,6 +823,7 @@ "granularity.hour": "ora", "granularity.minute": "minuto", "widget.options.menu.export": "Esporta", + "insightMenu.restrictedWidget.options": "Opzioni della visualizzazione con accesso limitato", "menu.close": "Chiudere il menu", "controlButtons.asTable": "Mostra come tabella", "controlButtons.asOriginal": "Mostra la visualizzazione originale", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ja-JP.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ja-JP.json index a5f10e69915..68a69b5cc25 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ja-JP.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ja-JP.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "トライアルは終了しました。", + "dashboard.contractExpired.trial.description": "引き続き GoodData Cloud をご利用になる場合は、こちらまでお問い合わせください。", + "dashboard.contractExpired.nonTrial.title": "アクセスはロックされています。", + "dashboard.contractExpired.nonTrial.description": "アカウント管理者に連絡して、アクセス権を再取得してください。", + "dashboard.contractExpired.buttonTitle": "お問い合わせ先", "loading": "読み込んでいます…", "untitled": "無題", "empty_value": "空の値", + "deleted_value": "消去された値", "gs.list.cancel": "キャンセル", "gs.list.apply": "適用", "gs.list.only": "のみ選択", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "操作が設定されました。", "messages.drill.InteractionUpdatedSuccess": "操作が更新されました。", "filterBar.label": "フィルター", + "filterBar.restrictedFilters.remove": "制限付きフィルターを削除", + "filterBar.restrictedFilters.reason": "{count, plural, other {# 件のフィルターにアクセスできません。}}", + "filterBar.restrictedFilters": "{count, plural, other {# 件のフィルターが適用されませんでした}}", "filterBar.showAll": "すべて表示", "filterBar.showLess": "表示を減らす", "filterBar.filterListAutoExpanded": "フィルター リストが自動的に展開されました", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "エクスポート予定", "deleteWidgetDialog.headline": "ウィジェットを削除しますか?", "deleteWidgetDialog.objectsMessage": "ウィジェット{title}に関連付けられた、以下のタイプのオブジェクトは、動作しなくなります。", + "deleteWidgetDialog.objectsMessage.restricted": "ウィジェットに関連付けられている次の種類のオブジェクトは機能しなくなります:", "deleteWidgetDialog.alerts": "アラート", "deleteWidgetDialog.schedules": "エクスポート予定", "create.dashboard": "ダッシュボードを作成", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} フィルターが無効 {parentTitle}。フィルターアトリビュートとデータモデルが無関係です。", "attributesDropdown.details.dataset": "データセット", "attributesDropdown.details.values": "値", + "attributesDropdown.details.definedAs": "定義", "attributesDropdown.details.shortening_decoration": "…及び{count}その他", "layout.header.characters.left": "残り {currentCharactersCount} / {maximumCharactersCount} 文字", "layout.header.add.title.placeholder": "こちらにタイトルを追加します…", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "ビジュアライゼーションは{attributeName} でフィルターできません。チェックボックスをオフにします。", "configurationPanel.kpiCantBeFilteredByAttribute": "KPIは{attributeName}でフィルターできません。チェックボックスの選択を解除してください。", "configurationPanel.vizCantBeFilteredByDate": "ビジュアライゼーションを日付でフィルターすることはできません。チェックボックスの選択を解除してください。", + "configurationPanel.dateDatasetsLoadFailed": "関連する日付データを読み込めませんでした。構成パネルを閉じて、もう一度お試しください。", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "このビジュアライゼーションにはフィルターを適用できません。", "configurationPanel.mvfBlockedByRankingFilter": "このフィルターは、ビジュアライゼーションで上位/下位値フィルターが使用されているため適用できません。このフィルターを適用するには、上位/下位値フィルターを削除してください。", "configurationPanel.mvfMissingMeasure": "このフィルターで使用されているメトリックは、ワークスペースに存在しなくなりました。", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "ビジュアライゼーションのドリル", "configurationPanel.drillConfig.drillDown": "ドリルダウン", "configurationPanel.drillConfig.disableDrillDownTooltip": "このビジュアライゼーションの設定では、ドリルダウンはオフになっています。", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "計算アトリビュートではドリルダウンを使用できません。", "configurationPanel.drillConfig.drillIntoUrl": "URL にドリル", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "無効な URL パラメーター", "configurationPanel.drillConfig.select": "アクションを選択...", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "リッチテキストを削除しますか?", "richText.deleteDialog.message": "リッチテキストとそのコンテンツはすべて元に戻せない状態で削除されます。", "richText.emptyContent": "リッチテキストのコンテンツがありません", + "visualizationSwitcher.restrictedEntry": "制限あり", "visualizationSwitcher.emptyContent": "スイッチャーでのビジュアライゼーションなし", "visualizationSwitcherToolbar.visualizationsList.header": "ビジュアライゼーション", "visualizationSwitcherToolbar.visualizationsList.tooltip": "ビジュアライゼーション", @@ -809,6 +823,7 @@ "granularity.hour": "時", "granularity.minute": "分", "widget.options.menu.export": "エクスポート", + "insightMenu.restrictedWidget.options": "制限されたビジュアライゼーションのオプション", "menu.close": "メニューを閉じる", "controlButtons.asTable": "表として表示", "controlButtons.asOriginal": "オリジナルのビジュアライゼーションを表示", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ko-KR.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ko-KR.json index 02444115953..83f507b5640 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ko-KR.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ko-KR.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "체험 기간이 종료되었습니다.", + "dashboard.contractExpired.trial.description": "GoodData Cloud를 계속 사용하시려는 경우 저희에게 연락주시기 바랍니다.", + "dashboard.contractExpired.nonTrial.title": "접근이 차단되었습니다.", + "dashboard.contractExpired.nonTrial.description": "계정 관리자에게 연락하여 접근 권한을 복구하세요.", + "dashboard.contractExpired.buttonTitle": "문의하기", "loading": "로딩 중…", "untitled": "제목 없음", "empty_value": "빈 값", + "deleted_value": "삭제된 값", "gs.list.cancel": "취소", "gs.list.apply": "적용", "gs.list.only": "만 해당됨", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "인터랙션이 구성되었습니다.", "messages.drill.InteractionUpdatedSuccess": "인터랙션이 업데이트되었습니다.", "filterBar.label": "필터(들)", + "filterBar.restrictedFilters.remove": "제한된 필터 제거", + "filterBar.restrictedFilters.reason": "{count, plural, other {#개의 필터에 액세스할 수 없습니다.}}", + "filterBar.restrictedFilters": "{count, plural, other {#개의 필터가 적용되지 않았습니다}}", "filterBar.showAll": "모두 보기", "filterBar.showLess": "간단히 보기", "filterBar.filterListAutoExpanded": "필터 목록이 자동으로 확장되었습니다", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "예약된 내보내기", "deleteWidgetDialog.headline": "위젯을 제거하시겠습니까?", "deleteWidgetDialog.objectsMessage": "위젯 {title}와 연관된 다음 유형의 객체는 더 이상 작동하지 않습니다:", + "deleteWidgetDialog.objectsMessage.restricted": "위젯과 연결된 다음 유형의 객체는 더 이상 작동하지 않습니다:", "deleteWidgetDialog.alerts": "알림", "deleteWidgetDialog.schedules": "예약된 내보내기", "create.dashboard": "대시보드 생성", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle}은(는) {parentTitle}(으)로 필터링할 수 없습니다. 데이터 모델에서 필터 속성 간의 연결이 없습니다.", "attributesDropdown.details.dataset": "데이터셋", "attributesDropdown.details.values": "값", + "attributesDropdown.details.definedAs": "다음으로 정의됨", "attributesDropdown.details.shortening_decoration": "…및 {count} 더보기", "layout.header.characters.left": "{currentCharactersCount} /{maximumCharactersCount} 남은 문자 수", "layout.header.add.title.placeholder": "여기에 제목 추가...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "시각화는 {attributeName}(으)로 필터링할 수 없습니다. 체크박스를 선택 해제하세요.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI는 {attributeName}(으)로 필터링할 수 없습니다. 체크박스를 선택 해제하세요.", "configurationPanel.vizCantBeFilteredByDate": "시각화는 날짜로 필터링할 수 없습니다. 체크 박스를 선택 해제하세요.", + "configurationPanel.dateDatasetsLoadFailed": "관련 날짜 데이터를 로드할 수 없습니다. 구성 패널을 닫고 다시 시도하십시오.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "이 시각화에는 필터를 적용할 수 없습니다.", "configurationPanel.mvfBlockedByRankingFilter": "이 필터는 시각화에서 상위/하위 값 필터를 사용하므로 적용할 수 없습니다. 이 필터를 적용하려면 상위/하위 값 필터를 제거하세요.", "configurationPanel.mvfMissingMeasure": "이 필터에 사용된 메트릭이 더 이상 작업 공간에 존재하지 않습니다.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "시각화 드릴 다운", "configurationPanel.drillConfig.drillDown": "드릴 다운", "configurationPanel.drillConfig.disableDrillDownTooltip": "이 시각화의 구성에서 드릴 다운이 꺼져 있습니다.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "계산된 속성에는 드릴 다운을 사용할 수 없습니다.", "configurationPanel.drillConfig.drillIntoUrl": "URL 드릴 다운", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "잘못된 URL 매개변수", "configurationPanel.drillConfig.select": "작업 선택…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "서식 있는 텍스트 삭제하시겠습니까?", "richText.deleteDialog.message": "서식 있는 텍스트 및 모든 내용이 되돌릴 수 없이 삭제됩니다.", "richText.emptyContent": "서식 있는 텍스트 없음", + "visualizationSwitcher.restrictedEntry": "제한됨", "visualizationSwitcher.emptyContent": "전환기에 시각화 없음", "visualizationSwitcherToolbar.visualizationsList.header": "시각화", "visualizationSwitcherToolbar.visualizationsList.tooltip": "시각화", @@ -809,6 +823,7 @@ "granularity.hour": "시간", "granularity.minute": "분", "widget.options.menu.export": "내보내기", + "insightMenu.restrictedWidget.options": "제한된 시각화 옵션", "menu.close": "메뉴 닫기", "controlButtons.asTable": "표로 표시", "controlButtons.asOriginal": "원본 시각화 보기", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/nl-NL.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/nl-NL.json index e7dbb44751a..fcf27e2cb19 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/nl-NL.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/nl-NL.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Uw proefperiode is afgelopen.", + "dashboard.contractExpired.trial.description": "Neem contact met ons op als u GoodData Cloud wilt blijven gebruiken.", + "dashboard.contractExpired.nonTrial.title": "Uw toegang is vergrendeld.", + "dashboard.contractExpired.nonTrial.description": "Neem contact op met de accountbeheerder om weer toegang te krijgen.", + "dashboard.contractExpired.buttonTitle": "Contacteer ons", "loading": "Wordt geladen…", "untitled": "Zonder titel", "empty_value": "lege waarde", + "deleted_value": "verwijderde waarde", "gs.list.cancel": "Annuleren", "gs.list.apply": "Toepassen", "gs.list.only": "Alleen", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interactie geconfigureerd.", "messages.drill.InteractionUpdatedSuccess": "Interactie bijgewerkt.", "filterBar.label": "Filters", + "filterBar.restrictedFilters.remove": "Verwijder beperkte filters", + "filterBar.restrictedFilters.reason": "{count, plural, one {U hebt geen toegang tot # filter.} other {U hebt geen toegang tot # filters.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filter is niet toegepast} other {# filters zijn niet toegepast}}", "filterBar.showAll": "Alle tonen", "filterBar.showLess": "Minder tonen", "filterBar.filterListAutoExpanded": "Filterlijst is automatisch uitgevouwen", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "geplande export", "deleteWidgetDialog.headline": "Widget verwijderen?", "deleteWidgetDialog.objectsMessage": "De volgende typen objecten die zijn gekoppeld aan de widget {title} werken niet meer:", + "deleteWidgetDialog.objectsMessage.restricted": "De volgende typen objecten die aan het element zijn gekoppeld, werken niet meer:", "deleteWidgetDialog.alerts": "waarschuwingen", "deleteWidgetDialog.schedules": "geplande export", "create.dashboard": "Dashboard maken", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} kan niet worden gefilterd op {parentTitle}. Er is geen verbinding tussen de filterattributen in het gegevensmodel.", "attributesDropdown.details.dataset": "Dataset", "attributesDropdown.details.values": "Waarden", + "attributesDropdown.details.definedAs": "Gedefinieerd als", "attributesDropdown.details.shortening_decoration": "…en {count} meer", "layout.header.characters.left": "Nog {currentCharactersCount} / {maximumCharactersCount} tekens", "layout.header.add.title.placeholder": "Titel hier toevoegen...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "De visualisatie kan niet worden gefilterd op {attributeName}. Schakel het selectievakje uit.", "configurationPanel.kpiCantBeFilteredByAttribute": "De KPI kan niet worden gefilterd op {attributeName}. Vink het selectievakje af.", "configurationPanel.vizCantBeFilteredByDate": "De visualisatie kan niet worden gefilterd op Datum. Schakel het selectievakje uit.", + "configurationPanel.dateDatasetsLoadFailed": "Gerelateerde datumgegevens konden niet worden geladen. Sluit het configuratiepaneel en probeer het opnieuw.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filter is niet van toepassing op deze visualisatie.", "configurationPanel.mvfBlockedByRankingFilter": "Dit filter kan niet worden toegepast omdat de visualisatie een filter voor Top-/onderste waarden gebruikt. Verwijder het filter voor Top-/onderste waarden om dit filter toe te passen.", "configurationPanel.mvfMissingMeasure": "De metriek die in dit filter wordt gebruikt, bestaat niet meer in de werkruimte.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Boor in visualisatie", "configurationPanel.drillConfig.drillDown": "Inzoomen", "configurationPanel.drillConfig.disableDrillDownTooltip": "Inzoomen is uitgeschakeld in de configuratie van deze visualisatie.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Inzoomen op details is niet beschikbaar voor berekende attributen.", "configurationPanel.drillConfig.drillIntoUrl": "Inzoomen op URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Ongeldige URL-parameters", "configurationPanel.drillConfig.select": "Actie kiezen…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Tekst met opmaak verwijderen?", "richText.deleteDialog.message": "De tekst met opmaak en alle inhoud wordt onomkeerbaar verwijderd.", "richText.emptyContent": "Geen inhoud met tekst met opmaak", + "visualizationSwitcher.restrictedEntry": "Beperkt", "visualizationSwitcher.emptyContent": "Geen visualisatie in schakelaar", "visualizationSwitcherToolbar.visualizationsList.header": "Visualisaties", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualisaties", @@ -809,6 +823,7 @@ "granularity.hour": "uur", "granularity.minute": "minuut", "widget.options.menu.export": "Exporteren", + "insightMenu.restrictedWidget.options": "Opties voor beperkte visualisatie", "menu.close": "Menu sluiten", "controlButtons.asTable": "Als tabel weergeven", "controlButtons.asOriginal": "Toon originele visualisatie", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pl-PL.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pl-PL.json index d9e81161de2..94ac5e7b6c4 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pl-PL.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pl-PL.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Twój okres próbny dobiegł końca.", + "dashboard.contractExpired.trial.description": "Skontaktuj się z nami, jeśli chcesz kontynuować korzystanie z GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Twój dostęp jest zablokowany.", + "dashboard.contractExpired.nonTrial.description": "Skontaktuj się z administratorem konta, aby odzyskać dostęp.", + "dashboard.contractExpired.buttonTitle": "Skontaktuj się z nami", "loading": "Ładowanie…", "untitled": "Bez tytułu", "empty_value": "pusta wartość", + "deleted_value": "usunięta wartość", "gs.list.cancel": "Anuluj", "gs.list.apply": "Zastosuj", "gs.list.only": "Tylko", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interakcja skonfigurowana.", "messages.drill.InteractionUpdatedSuccess": "Interakcja zaktualizowana.", "filterBar.label": "Filtry", + "filterBar.restrictedFilters.remove": "Usuń filtry z ograniczonym dostępem", + "filterBar.restrictedFilters.reason": "{count, plural, one {Nie masz dostępu do # filtru.} few {Nie masz dostępu do # filtrów.} many {Nie masz dostępu do # filtrów.} other {Nie masz dostępu do # filtra.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtr nie został zastosowany} few {# filtry nie zostały zastosowane} many {# filtrów nie zostało zastosowanych} other {# filtra nie zastosowano}}", "filterBar.showAll": "Pokaż wszystko", "filterBar.showLess": "Pokaż mniej", "filterBar.filterListAutoExpanded": "Lista filtrów została automatycznie rozwinięta", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "zaplanowane eksporty", "deleteWidgetDialog.headline": "Usunąć widżet?", "deleteWidgetDialog.objectsMessage": "Następujące typy obiektów powiązanych z widżetem {title} przestaną działać:", + "deleteWidgetDialog.objectsMessage.restricted": "Następujące typy obiektów powiązanych z elementem przestaną działać:", "deleteWidgetDialog.alerts": "alerty", "deleteWidgetDialog.schedules": "zaplanowane eksporty", "create.dashboard": "Utwórz pulpit nawigacyjny", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} nie można filtrować za pomocą {parentTitle}. W modelu danych nie ma połączenia między atrybutami filtra.", "attributesDropdown.details.dataset": "Zestaw danych", "attributesDropdown.details.values": "Wartości", + "attributesDropdown.details.definedAs": "Zdefiniowane jako", "attributesDropdown.details.shortening_decoration": "…i {count} więcej", "layout.header.characters.left": "{currentCharactersCount} /{maximumCharactersCount} pozostało znaków", "layout.header.add.title.placeholder": "Dodaj tytuł tutaj...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Wizualizacja nie może być filtrowana przez {attributeName}. Odznacz pole wyboru.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI nie może być filtrowany przez {attributeName}. Odznacz pole wyboru.", "configurationPanel.vizCantBeFilteredByDate": "Wizualizacja nie może być filtrowana wg daty. Odznacz pole wyboru.", + "configurationPanel.dateDatasetsLoadFailed": "Nie udało się załadować powiązanych danych dotyczących dat. Zamknij panel konfiguracji i spróbuj ponownie.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filtr nie ma zastosowania do tej wizualizacji.", "configurationPanel.mvfBlockedByRankingFilter": "Tego filtra nie można zastosować, ponieważ wizualizacja używa filtra Top/bottom values. Usuń filtr Top/bottom values, aby zastosować ten filtr.", "configurationPanel.mvfMissingMeasure": "Metryka używana w tym filtrze już nie istnieje w przestrzeni roboczej.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Przewijanie do wizualizacji", "configurationPanel.drillConfig.drillDown": "Przewijanie w dół", "configurationPanel.drillConfig.disableDrillDownTooltip": "Przewijanie w dół jest wyłączony w konfiguracji tej wizualizacji.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Opcja „przejdź w dół szczegółów” nie jest dostępna dla atrybutów obliczeniowych.", "configurationPanel.drillConfig.drillIntoUrl": "Przewijanie do URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Nieprawidłowe parametry URL", "configurationPanel.drillConfig.select": "Wybierz działanie…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Usunąć tekst sformatowany?", "richText.deleteDialog.message": "Tekst sformatowany oraz cała jego zawartość zostaną nieodwracalnie usunięte.", "richText.emptyContent": "Brak treści sformatowanej", + "visualizationSwitcher.restrictedEntry": "Ograniczone", "visualizationSwitcher.emptyContent": "Brak wizualizacji w przełączniku", "visualizationSwitcherToolbar.visualizationsList.header": "Wizualizacje", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Wizualizacje", @@ -809,6 +823,7 @@ "granularity.hour": "godzina", "granularity.minute": "minuta", "widget.options.menu.export": "Eksportuj", + "insightMenu.restrictedWidget.options": "Opcje wizualizacji z ograniczonym dostępem", "menu.close": "Zamknij menu", "controlButtons.asTable": "Pokaż jako tabelę", "controlButtons.asOriginal": "Pokaż oryginalną wizualizację", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-BR.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-BR.json index 81837879c04..36332d03896 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-BR.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-BR.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Seu período de teste terminou.", + "dashboard.contractExpired.trial.description": "Entre em contato conosco se deseja continuar usando o GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Seu acesso está bloqueado.", + "dashboard.contractExpired.nonTrial.description": "Entre em contato com o administrador da conta para recuperar o acesso.", + "dashboard.contractExpired.buttonTitle": "Fale conosco", "loading": "Carregando…", "untitled": "Sem título", "empty_value": "valor vazio", + "deleted_value": "valor excluído", "gs.list.cancel": "Cancelar", "gs.list.apply": "Aplicar", "gs.list.only": "Só", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interação configurada.", "messages.drill.InteractionUpdatedSuccess": "Interação atualizada.", "filterBar.label": "Filtros", + "filterBar.restrictedFilters.remove": "Remover filtros restritos", + "filterBar.restrictedFilters.reason": "{count, plural, one {Você não tem acesso a # filtro.} other {Você não tem acesso a # filtros.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtro não foi aplicado} other {# filtros não foram aplicados}}", "filterBar.showAll": "Exibir tudo", "filterBar.showLess": "Exibir menos", "filterBar.filterListAutoExpanded": "A lista de filtros foi expandida automaticamente", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "exportações programadas", "deleteWidgetDialog.headline": "Remover widget?", "deleteWidgetDialog.objectsMessage": "Os seguintes tipos de objetos associados ao widget {title} não funcionarão mais:", + "deleteWidgetDialog.objectsMessage.restricted": "Os seguintes tipos de objetos associados ao elemento não funcionarão mais:", "deleteWidgetDialog.alerts": "alertas", "deleteWidgetDialog.schedules": "exportações programadas", "create.dashboard": "Criar dashboard", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} não pode ser filtrado por {parentTitle}. Não há conexão entre os atributos do filtro e o modelo de dados.", "attributesDropdown.details.dataset": "Conjunto de Dados", "attributesDropdown.details.values": "Valores", + "attributesDropdown.details.definedAs": "Definido como", "attributesDropdown.details.shortening_decoration": "…e mais {count}", "layout.header.characters.left": "{currentCharactersCount}/{maximumCharactersCount} caracteres restantes", "layout.header.add.title.placeholder": "Adicione o título aqui...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "A visualização não pode ser filtrada pelo {attributeName}. Desmarque a caixa de seleção.", "configurationPanel.kpiCantBeFilteredByAttribute": "O KPI não pode ser filtrado por {attributeName}. Desmarque a caixa de seleção.", "configurationPanel.vizCantBeFilteredByDate": "A visualização não pode ser filtrada por Data. Desmarque a caixa de seleção.", + "configurationPanel.dateDatasetsLoadFailed": "Não foi possível carregar os dados de data relacionados. Feche o painel de configuração e tente novamente.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "O filtro não é aplicável a esta visualização.", "configurationPanel.mvfBlockedByRankingFilter": "Este filtro não pode ser aplicado porque a visualização usa um filtro de valores Top/bottom. Remova o filtro de valores Top/bottom para aplicar este filtro.", "configurationPanel.mvfMissingMeasure": "A métrica usada neste filtro não existe mais no espaço de trabalho.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Explorar a visualização", "configurationPanel.drillConfig.drillDown": "Detalhamento aprofundado", "configurationPanel.drillConfig.disableDrillDownTooltip": "A busca detalhada está desativada na configuração desta visualização.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Detalhar não está disponível para atributos calculados.", "configurationPanel.drillConfig.drillIntoUrl": "Detalhar URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parâmetros inválidos da URL", "configurationPanel.drillConfig.select": "Escolher ação…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Excluir rich text?", "richText.deleteDialog.message": "O rich text e todo o seu conteúdo serão excluídos, isso não pode ser desfeito.", "richText.emptyContent": "Sem conteúdo de rich text", + "visualizationSwitcher.restrictedEntry": "Restrito", "visualizationSwitcher.emptyContent": "Nenhuma visualização no switcher", "visualizationSwitcherToolbar.visualizationsList.header": "Visualizações", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualizações", @@ -809,6 +823,7 @@ "granularity.hour": "hora", "granularity.minute": "minuto", "widget.options.menu.export": "Exportar", + "insightMenu.restrictedWidget.options": "Opções da visualização restrita", "menu.close": "Fechar menu", "controlButtons.asTable": "Mostrar como tabela", "controlButtons.asOriginal": "Mostrar visualização original", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-PT.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-PT.json index ac15c77b3b1..38214cf9bab 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-PT.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/pt-PT.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "A sua avaliação terminou.", + "dashboard.contractExpired.trial.description": "Contacte-nos se pretender continuar a usar o GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "O seu acesso encontra-se bloqueado.", + "dashboard.contractExpired.nonTrial.description": "Para recuperar o acesso, contacte o administrador de conta.", + "dashboard.contractExpired.buttonTitle": "Contacte-nos", "loading": "A carregar…", "untitled": "Sem título", "empty_value": "valor vazio", + "deleted_value": "valor eliminado", "gs.list.cancel": "Cancelar", "gs.list.apply": "Aplicar", "gs.list.only": "Só", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interação configurada.", "messages.drill.InteractionUpdatedSuccess": "Interação atualizada.", "filterBar.label": "Filtros", + "filterBar.restrictedFilters.remove": "Remover filtros restritos", + "filterBar.restrictedFilters.reason": "{count, plural, one {Você não tem acesso a # filtro.} other {Você não tem acesso a # filtros.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtro não foi aplicado} other {# filtros não foram aplicados}}", "filterBar.showAll": "Mostrar tudo", "filterBar.showLess": "Mostrar menos", "filterBar.filterListAutoExpanded": "A lista de filtros foi expandida automaticamente", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "Exportações programadas", "deleteWidgetDialog.headline": "Remover widget?", "deleteWidgetDialog.objectsMessage": "Os seguintes tipos de objetos associados ao widget {title} deixarão de funcionar:", + "deleteWidgetDialog.objectsMessage.restricted": "Os seguintes tipos de objetos associados ao elemento deixarão de funcionar:", "deleteWidgetDialog.alerts": "Alertas", "deleteWidgetDialog.schedules": "Exportações programadas", "create.dashboard": "Criar dashboard", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} não pode ser filtrado por {parentTitle}. Não há ligação entre os atributos de filtro no modelo de dados.", "attributesDropdown.details.dataset": "Conjunto de dados", "attributesDropdown.details.values": "Valores", + "attributesDropdown.details.definedAs": "Definido como", "attributesDropdown.details.shortening_decoration": "…e {count} mais", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} carateres remanescentes", "layout.header.add.title.placeholder": "Adicionar Título aqui...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "A visualização não pode ser filtrada por {attributeName}. Desmarque a caixa de seleção.", "configurationPanel.kpiCantBeFilteredByAttribute": "Não é possível filtrar o kpi por {attributeName}. Desmarque a caixa de verificação.", "configurationPanel.vizCantBeFilteredByDate": "A visualização não pode ser filtrada por Data. Desmarque a caixa de seleção.", + "configurationPanel.dateDatasetsLoadFailed": "Não foi possível carregar os dados de data relacionados. Feche o painel de configuração e tente novamente.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "O filtro não é aplicável a esta visualização.", "configurationPanel.mvfBlockedByRankingFilter": "Este filtro não pode ser aplicado porque a visualização utiliza um filtro de Valores superiores/inferiores. Remova o filtro de Valores superiores/inferiores para aplicar este filtro.", "configurationPanel.mvfMissingMeasure": "A métrica usada neste filtro não existe mais no espaço de trabalho.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Aprofunde-se na visualização", "configurationPanel.drillConfig.drillDown": "Exploração descendente", "configurationPanel.drillConfig.disableDrillDownTooltip": "A pesquisa detalhada está desativada na configuração desta visualização.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Detalhar não está disponível para atributos calculados.", "configurationPanel.drillConfig.drillIntoUrl": "Aprofundar URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Parâmetros de URL inválidos", "configurationPanel.drillConfig.select": "Escolher ação…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Eliminar Rich Text?", "richText.deleteDialog.message": "O Rich Text e todo o seu conteúdo será eliminado de forma irreversível.", "richText.emptyContent": "Nenhum conteúdo Rich Text", + "visualizationSwitcher.restrictedEntry": "Restrito", "visualizationSwitcher.emptyContent": "Nenhuma visualização no alternador", "visualizationSwitcherToolbar.visualizationsList.header": "Visualizações", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Visualizações", @@ -809,6 +823,7 @@ "granularity.hour": "hora", "granularity.minute": "minuto", "widget.options.menu.export": "Exportar", + "insightMenu.restrictedWidget.options": "Opções de visualização restrita", "menu.close": "Fechar menu", "controlButtons.asTable": "Mostrar como tabela", "controlButtons.asOriginal": "Mostrar visualização original", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ru-RU.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ru-RU.json index bc3677566d5..bc165ab66aa 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ru-RU.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/ru-RU.json @@ -1,13 +1,19 @@ { + "dashboard.contractExpired.trial.title": "Срок действия вашей пробной версии истек.", + "dashboard.contractExpired.trial.description": "Свяжитесь с нами, если вы хотите продолжить использование GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Ваш доступ заблокирован.", + "dashboard.contractExpired.nonTrial.description": "Обратитесь к администратору учетной записи, чтобы восстановить доступ.", + "dashboard.contractExpired.buttonTitle": "Связаться с нами", "loading": "Загрузка…", "untitled": "Без названия", "empty_value": "значение отсутствует", + "deleted_value": "удаленное значение", "gs.list.cancel": "Отмена", "gs.list.apply": "Применить", "gs.list.only": "Только", "attributesDropdown.onlyLabel": "Выбрать только {itemTitle}", "messages.dashboardSaveSuccess": "Отлично. Мы сохранили ваш дашборд.", - "options.menu.save.as": "Сохранить как новую", + "options.menu.save.as": "Сохранить как новый", "options.menu.save.as.tooltip": "Пустой дашборд нельзя сохранить.", "options.menu.settings": "Настройки", "messages.dashboardSaveFailed": "Не удалось сохранить дашборд, повторите попытку.", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Взаимодействие настроено.", "messages.drill.InteractionUpdatedSuccess": "Взаимодействие обновлено.", "filterBar.label": "Фильтры", + "filterBar.restrictedFilters.remove": "Удалить фильтры с ограниченным доступом", + "filterBar.restrictedFilters.reason": "{count, plural, one {У вас нет доступа к # фильтру.} few {У вас нет доступа к # фильтрам.} many {У вас нет доступа к # фильтрам.} other {У вас нет доступа к # фильтрам.}}", + "filterBar.restrictedFilters": "{count, plural, one {# фильтр не применен} few {# фильтра не применены} many {# фильтров не применены} other {# фильтра не применено}}", "filterBar.showAll": "Показать все", "filterBar.showLess": "Скрыть", "filterBar.filterListAutoExpanded": "Список фильтров был автоматически развернут", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "запланированные экспорты", "deleteWidgetDialog.headline": "Удалить виджет?", "deleteWidgetDialog.objectsMessage": "Следующие типы объектов, связанных с виджетом {title}, перестанут работать:", + "deleteWidgetDialog.objectsMessage.restricted": "Следующие типы объектов, связанных с виджетом, перестанут работать:", "deleteWidgetDialog.alerts": "оповещения", "deleteWidgetDialog.schedules": "запланированные экспорты", "create.dashboard": "Создать дашборд", @@ -198,7 +208,7 @@ "dialogs.schedule.email.recipients.note": "Примечание: Все получатели, помеченные как «(гость)», получат тот же вид дашборда, что и вы.", "dialogs.schedule.email.subject.label": "Тема:", "dialogs.schedule.email.recipients.label": "Получателей", - "dialogs.schedule.email.filters": "Дашборд содержит изменения фильтра, которые будут сохранены в расписании.", + "dialogs.schedule.email.filters": "Фильтры ({count})", "dialogs.schedule.email.tabs.general": "Общие", "dialogs.schedule.email.tabs.filters": "Фильтры", "dialogs.schedule.email.destination": "Направление", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} нельзя фильтровать по {parentTitle}. В модели данных нет связи между атрибутами фильтра.", "attributesDropdown.details.dataset": "Датасет", "attributesDropdown.details.values": "Значения", + "attributesDropdown.details.definedAs": "Определяется как", "attributesDropdown.details.shortening_decoration": "…и еще {count}", "layout.header.characters.left": "осталось символов: {currentCharactersCount}/{maximumCharactersCount}", "layout.header.add.title.placeholder": "Добавьте заголовок здесь...", @@ -443,7 +454,7 @@ "visualizationsList.noVisualizationsFound": "Подходящих визуализаций не найдено.", "search_insights": "Найти по всем визуализациям…", "search_insights.similar": "Похожие", - "save.as.new": "Сохранить как новую", + "save.as.new": "Сохранить как новый", "addPanel.deleteItem": "Сбросьте, чтобы удалить", "addPanel.newItem": "Новый элемент", "addPanel.attributeFilter": "Фильтр атрибутов", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Эту визуализацию нельзя фильтровать по {attributeName}. Снимите флажок.", "configurationPanel.kpiCantBeFilteredByAttribute": "Этот КПЭ нельзя фильтровать по {attributeName}. Снимите флажок.", "configurationPanel.vizCantBeFilteredByDate": "Эту визуализацию нельзя фильтровать по дате. Снимите флажок.", + "configurationPanel.dateDatasetsLoadFailed": "Не удалось загрузить связанные данные о датах. Закройте панель конфигурации и повторите попытку.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Фильтр неприменим к этой визуализации.", "configurationPanel.mvfBlockedByRankingFilter": "Этот фильтр нельзя применить, потому что в визуализации используется фильтр значений Top/bottom. Чтобы применить этот фильтр, удалите фильтр значений Top/bottom.", "configurationPanel.mvfMissingMeasure": "Показатель, используемый в этом фильтре, больше не существует в рабочей области.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Дрил в визуализацию", "configurationPanel.drillConfig.drillDown": "Углубиться", "configurationPanel.drillConfig.disableDrillDownTooltip": "Детализация отключена в конфигурации этого анализа.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Детализация недоступна для вычисляемых атрибутов.", "configurationPanel.drillConfig.drillIntoUrl": "Углубиться в URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Неверные параметры URL", "configurationPanel.drillConfig.select": "Выбрать действие…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Удалить форматированный текст?", "richText.deleteDialog.message": "Форматированный текст и всё его содержимое будут безвозвратно удалены.", "richText.emptyContent": "Нет содержимого форматированного текста", + "visualizationSwitcher.restrictedEntry": "Ограничено", "visualizationSwitcher.emptyContent": "В переключателе нет визуализации", "visualizationSwitcherToolbar.visualizationsList.header": "Анализы", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Анализы", @@ -809,6 +823,7 @@ "granularity.hour": "час", "granularity.minute": "минута", "widget.options.menu.export": "Экспортировать", + "insightMenu.restrictedWidget.options": "Параметры визуализации с ограниченным доступом", "menu.close": "Закрыть меню", "controlButtons.asTable": "Показать в виде таблицы", "controlButtons.asOriginal": "Показать исходную визуализацию", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/sl-SI.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/sl-SI.json index 11371cb6adf..c20402083c2 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/sl-SI.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/sl-SI.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Vaša preizkusna različica se je končala.", + "dashboard.contractExpired.trial.description": "Kontaktirajte nas, če želite še naprej uporabljati GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Vaš dostop je zaklenjen.", + "dashboard.contractExpired.nonTrial.description": "Za ponovno pridobitev dostopa kontaktirajte skrbnika računa.", + "dashboard.contractExpired.buttonTitle": "Kontaktirajte nas", "loading": "Nalaganje…", "untitled": "Brez naslova", "empty_value": "prazna vrednost", + "deleted_value": "izbrisana vrednost", "gs.list.cancel": "Prekliči", "gs.list.apply": "Uporabi", "gs.list.only": "Samo", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Interakcija nastavljena.", "messages.drill.InteractionUpdatedSuccess": "Interakcija posodobljena.", "filterBar.label": "Filtri", + "filterBar.restrictedFilters.remove": "Odstrani omejene filtre", + "filterBar.restrictedFilters.reason": "{count, plural, one {Nimate dostopa do # filtra.} two {Nimate dostopa do # filtrov.} few {Nimate dostopa do # filtrov.} other {Nimate dostopa do # filtrov.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filter ni bil uporabljen} two {# filtra nista bila uporabljena} few {# filtri niso bili uporabljeni} other {# filtrov ni bilo uporabljenih}}", "filterBar.showAll": "Prikaži vse", "filterBar.showLess": "Prikaži manj", "filterBar.filterListAutoExpanded": "Seznam filtrov je bil samodejno razširjen", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "načrtovani izvozi", "deleteWidgetDialog.headline": "Odstraniti gradnik?", "deleteWidgetDialog.objectsMessage": "Naslednji tipi objektov, povezani z gradnikom {title}, ne bodo več delovali:", + "deleteWidgetDialog.objectsMessage.restricted": "Naslednje vrste objektov, povezane z gradnikom, ne bodo več delovale:", "deleteWidgetDialog.alerts": "opozorila", "deleteWidgetDialog.schedules": "načrtovani izvozi", "create.dashboard": "Ustvari nadzorno ploščo", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} ni mogoče filtrirati po {parentTitle}. V modelu podatkov ni povezave med atributoma filtra.", "attributesDropdown.details.dataset": "Podatkovni sklop", "attributesDropdown.details.values": "Vrednosti", + "attributesDropdown.details.definedAs": "Določeno kot", "attributesDropdown.details.shortening_decoration": "…in še {count} drugih", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} znakov preostane", "layout.header.add.title.placeholder": "Dodajte naslov tukaj...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Vizualizacije ni mogoče filtrirati glede na {attributeName}. Odznačite potrditveno polje.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI-ja ni mogoče filtrirati glede na {attributeName}. Odznačite potrditveno polje.", "configurationPanel.vizCantBeFilteredByDate": "Vizualizacije ni mogoče filtrirati po datumu. Odznačite potrditveno polje.", + "configurationPanel.dateDatasetsLoadFailed": "Povezanih datumskih podatkov ni bilo mogoče naložiti. Zaprite podokno za konfiguracijo in poskusite znova.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filter ni uporaben za to vizualizacijo.", "configurationPanel.mvfBlockedByRankingFilter": "Tega filtra ni mogoče uporabiti, ker vizualizacija uporablja filter Najvišje/najnižje vrednosti. Odstranite filter Najvišje/najnižje vrednosti, da uporabite ta filter.", "configurationPanel.mvfMissingMeasure": "Metrika, uporabljena v tem filtru, ne obstaja več v delovnem prostoru.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Poglobitev v vizualizacijo", "configurationPanel.drillConfig.drillDown": "Poglobiti se", "configurationPanel.drillConfig.disableDrillDownTooltip": "Poglobitev je izključena v tej konfiguraciji vizualizacije.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Drilldown ni na voljo za izračunane atribute.", "configurationPanel.drillConfig.drillIntoUrl": "Poglobitev v URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Neveljavni URL parametri", "configurationPanel.drillConfig.select": "Izberi dejanje…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Želite izbrisati obogateno besedilo?", "richText.deleteDialog.message": "Obogateno besedilo in vsa njegova vsebina bosta nepreklicno izbrisana.", "richText.emptyContent": "Ni vsebine obogatenega besedila", + "visualizationSwitcher.restrictedEntry": "Omejeno", "visualizationSwitcher.emptyContent": "Brez vizualizacije v preklopniku", "visualizationSwitcherToolbar.visualizationsList.header": "Vizualizacije", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Vizualizacije", @@ -809,6 +823,7 @@ "granularity.hour": "ura", "granularity.minute": "minuta", "widget.options.menu.export": "Izvozi", + "insightMenu.restrictedWidget.options": "Možnosti omejene vizualizacije", "menu.close": "Zapri meni", "controlButtons.asTable": "Pokaži kot tabela", "controlButtons.asOriginal": "Pokaži kot prvotna vizualizacija", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/th-TH.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/th-TH.json index 64da2a9b067..f714870cb12 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/th-TH.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/th-TH.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "ช่วงทดลองใช้ของคุณสิ้นสุดแล้ว.", + "dashboard.contractExpired.trial.description": "ติดต่อเราหากคุณต้องการใช้งาน GoodData Cloud ต่อไป", + "dashboard.contractExpired.nonTrial.title": "การเข้าถึงของคุณถูกล็อก.", + "dashboard.contractExpired.nonTrial.description": "ติดต่อผู้ดูแลระบบบัญชีเพื่อรับการเข้าถึงอีกครั้ง", + "dashboard.contractExpired.buttonTitle": "ติดต่อเรา", "loading": "กำลังโหลด…", "untitled": "ไม่มีชื่อ", "empty_value": "ค่าว่าง", + "deleted_value": "ค่าที่ถูกลบ", "gs.list.cancel": "ยกเลิก", "gs.list.apply": "นำไปใช้", "gs.list.only": "เท่านั้น", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "กำหนดค่าการโต้ตอบแล้ว", "messages.drill.InteractionUpdatedSuccess": "อัปเดตการโต้ตอบแล้ว", "filterBar.label": "ตัวกรอง", + "filterBar.restrictedFilters.remove": "ลบตัวกรองที่ถูกจำกัด", + "filterBar.restrictedFilters.reason": "{count, plural, other {คุณไม่มีสิทธิ์เข้าถึงตัวกรอง # รายการ}}", + "filterBar.restrictedFilters": "{count, plural, other {ไม่ได้ใช้ตัวกรอง # รายการ}}", "filterBar.showAll": "แสดงทั้งหมด", "filterBar.showLess": "แสดงน้อยลง", "filterBar.filterListAutoExpanded": "รายการตัวกรองถูกขยายโดยอัตโนมัติ", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "การส่งออกตามกำหนดการ", "deleteWidgetDialog.headline": "นำวิดเจ็ตออกหรือไม่?", "deleteWidgetDialog.objectsMessage": "อ็อบเจ็กต์ประเภทต่อไปนี้ที่เชื่อมโยงกับวิดเจ็ต {title} จะไม่ทำงานอีกต่อไป:", + "deleteWidgetDialog.objectsMessage.restricted": "อ็อบเจ็กต์ประเภทต่อไปนี้ที่เชื่อมโยงกับวิดเจ็ตจะไม่ทำงานอีกต่อไป:", "deleteWidgetDialog.alerts": "การแจ้งเตือน", "deleteWidgetDialog.schedules": "การส่งออกตามกำหนดการ", "create.dashboard": "สร้างแดชบอร์ด", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} ไม่สามารถถูกกรองโดย {parentTitle} ไม่มีการเชื่อมต่อระหว่างแอตทริบิวต์ของตัวกรองในแบบจำลองข้อมูล", "attributesDropdown.details.dataset": "ชุดข้อมูล", "attributesDropdown.details.values": "ค่า", + "attributesDropdown.details.definedAs": "กำหนดเป็น", "attributesDropdown.details.shortening_decoration": "…และอีก {count} รายการ", "layout.header.characters.left": "เหลืออีก {currentCharactersCount} / {maximumCharactersCount} อักขระ", "layout.header.add.title.placeholder": "เพิ่มชื่อที่นี่...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "การแสดงภาพข้อมูลไม่สามารถถูกกรองโดย {attributeName} ได้ ยกเลิกการเลือกช่องทำเครื่องหมาย", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI ไม่สามารถถูกกรองโดย {attributeName} ได้ ยกเลิกการเลือกช่องทำเครื่องหมาย", "configurationPanel.vizCantBeFilteredByDate": "การแสดงภาพข้อมูลไม่สามารถถูกกรองตามวันที่ได้ ยกเลิกการเลือกช่องทำเครื่องหมาย", + "configurationPanel.dateDatasetsLoadFailed": "ไม่สามารถโหลดข้อมูลวันที่ที่เกี่ยวข้องได้ ปิดแผงการกำหนดค่าแล้วลองอีกครั้ง", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "ตัวกรองไม่สามารถใช้ได้กับการแสดงภาพข้อมูลนี้", "configurationPanel.mvfBlockedByRankingFilter": "ไม่สามารถใช้ตัวกรองนี้ได้ เนื่องจากการแสดงภาพข้อมูลใช้ตัวกรองค่าบนสุด/ล่างสุด นำตัวกรองค่าบนสุด/ล่างสุดออกเพื่อใช้ตัวกรองนี้", "configurationPanel.mvfMissingMeasure": "เมตริกที่ใช้ในตัวกรองนี้ไม่มีอยู่ในพื้นที่ทำงานอีกต่อไป", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "เจาะลึกไปยังการแสดงภาพข้อมูล", "configurationPanel.drillConfig.drillDown": "เจาะลึกลง", "configurationPanel.drillConfig.disableDrillDownTooltip": "การเจาะลึกลงถูกปิดในกำหนดค่าของการแสดงภาพข้อมูลนี้", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "การเจาะลึกลงไม่พร้อมใช้งานสำหรับแอตทริบิวต์ที่คำนวณ", "configurationPanel.drillConfig.drillIntoUrl": "เจาะลึกไปยัง URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "พารามิเตอร์ URL ไม่ถูกต้อง", "configurationPanel.drillConfig.select": "เลือกการดำเนินการ…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "ลบ Rich Text หรือไม่?", "richText.deleteDialog.message": "Rich Text และเนื้อหาทั้งหมดจะถูกลบอย่างไม่สามารถย้อนกลับได้", "richText.emptyContent": "ไม่มีเนื้อหา Rich Text", + "visualizationSwitcher.restrictedEntry": "ถูกจำกัด", "visualizationSwitcher.emptyContent": "ไม่มีการแสดงภาพข้อมูลในตัวสลับ", "visualizationSwitcherToolbar.visualizationsList.header": "การแสดงภาพข้อมูล", "visualizationSwitcherToolbar.visualizationsList.tooltip": "การแสดงภาพข้อมูล", @@ -809,6 +823,7 @@ "granularity.hour": "ชั่วโมง", "granularity.minute": "นาที", "widget.options.menu.export": "ส่งออก", + "insightMenu.restrictedWidget.options": "ตัวเลือกการแสดงภาพข้อมูลที่ถูกจำกัด", "menu.close": "ปิดเมนู", "controlButtons.asTable": "แสดงเป็นตาราง", "controlButtons.asOriginal": "แสดงการแสดงภาพข้อมูลเดิม", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/tr-TR.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/tr-TR.json index 52c3fab056b..7ee5f6e6b36 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/tr-TR.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/tr-TR.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Deneme süreniz sona erdi.", + "dashboard.contractExpired.trial.description": "GoodData Cloud'u kullanmaya devam etmek istiyorsanız bizimle iletişime geçin.", + "dashboard.contractExpired.nonTrial.title": "Erişiminiz kilitlendi.", + "dashboard.contractExpired.nonTrial.description": "Erişimi yeniden kazanmak için hesap yöneticisiyle iletişime geçin.", + "dashboard.contractExpired.buttonTitle": "Bize Ulaşın", "loading": "Yükleniyor…", "untitled": "Başlıksız", "empty_value": "boş değer", + "deleted_value": "silinmiş değer", "gs.list.cancel": "İptal", "gs.list.apply": "Uygula", "gs.list.only": "Yalnızca", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Etkileşim yapılandırıldı.", "messages.drill.InteractionUpdatedSuccess": "Etkileşim güncellendi.", "filterBar.label": "Filtreler", + "filterBar.restrictedFilters.remove": "Kısıtlı filtreleri kaldır", + "filterBar.restrictedFilters.reason": "{count, plural, one {# filtreye erişiminiz yok.} other {# filtreye erişiminiz yok.}}", + "filterBar.restrictedFilters": "{count, plural, one {# filtre uygulanmadı} other {# filtre uygulanmadı}}", "filterBar.showAll": "Tümünü göster", "filterBar.showLess": "Daha az göster", "filterBar.filterListAutoExpanded": "Filtre listesi otomatik olarak genişletildi", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "zamanlanmış dışa aktarımlar", "deleteWidgetDialog.headline": "Widget kaldırılsın mı?", "deleteWidgetDialog.objectsMessage": "Aşağıdaki türdeki widget {title} ile ilişkili nesneler artık çalışmayacak:", + "deleteWidgetDialog.objectsMessage.restricted": "Öğeyle ilişkili aşağıdaki nesne türleri artık çalışmayacak:", "deleteWidgetDialog.alerts": "uyarılar", "deleteWidgetDialog.schedules": "zamanlanmış dışa aktarımlar", "create.dashboard": "Gösterge tablosu oluştur", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle}, {parentTitle} ile filtrelenemez. Veri modelinde filtre öznitelikleri arasında bağlantı yok.", "attributesDropdown.details.dataset": "Veri kümesi", "attributesDropdown.details.values": "Değerler", + "attributesDropdown.details.definedAs": "Şöyle tanımlanır", "attributesDropdown.details.shortening_decoration": "…ve {count} daha fazla", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} karakter kaldı", "layout.header.add.title.placeholder": "Buraya Başlık Ekleyin...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Görselleştirme {attributeName} ile filtrelenemez. Onay kutusunun işaretini kaldırın.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI, {attributeName} tarafından filtrelenemez. Onay kutusunun işaretini kaldırın.", "configurationPanel.vizCantBeFilteredByDate": "Görselleştirme Tarihe göre filtrelenemez. Onay kutusunun işaretini kaldırın.", + "configurationPanel.dateDatasetsLoadFailed": "İlgili tarih verileri yüklenemedi. Yapılandırma panelini kapatıp tekrar deneyin.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Filtre bu görselleştirme için uygulanamaz.", "configurationPanel.mvfBlockedByRankingFilter": "Bu filtre, görselleştirme bir En üst/En alt değerler filtresi kullandığı için uygulanamaz. Bu filtreyi uygulamak için En üst/En alt değerler filtresini kaldırın.", "configurationPanel.mvfMissingMeasure": "Bu filtrede kullanılan metrik artık çalışma alanında mevcut değil.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Görselleştirmeye derinlemesine inceleme yap", "configurationPanel.drillConfig.drillDown": "Detayına in", "configurationPanel.drillConfig.disableDrillDownTooltip": "Bu görselleştirmenin yapılandırmasında ayrıntıya inme devre dışı bırakılmış.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Hesaplanan öznitelikler için ayrıntıya inilemez.", "configurationPanel.drillConfig.drillIntoUrl": "URL'yi ayrıntılı incele", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Geçersiz URL parametreleri", "configurationPanel.drillConfig.select": "Eylem seçin…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Zengin metin silinsin mi?", "richText.deleteDialog.message": "Zengin metin ve tüm içeriği geri döndürülemez şekilde silinecektir.", "richText.emptyContent": "Zengin metin içeriği yok", + "visualizationSwitcher.restrictedEntry": "Kısıtlı", "visualizationSwitcher.emptyContent": "Değiştiricide görselleştirme yok", "visualizationSwitcherToolbar.visualizationsList.header": "Görselleştirmeler", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Görselleştirmeler", @@ -809,6 +823,7 @@ "granularity.hour": "saat", "granularity.minute": "dakika", "widget.options.menu.export": "Dışa aktar", + "insightMenu.restrictedWidget.options": "Kısıtlanmış görselleştirme seçenekleri", "menu.close": "Menüyü kapat", "controlButtons.asTable": "Tablo olarak göster", "controlButtons.asOriginal": "Orijinal görselleştirmeyi göster", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/uk-UA.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/uk-UA.json index 0163b8c1205..bfa3135fe49 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/uk-UA.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/uk-UA.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Ваш пробний період завершився.", + "dashboard.contractExpired.trial.description": "Зв’яжіться з нами, якщо хочете й надалі користуватися GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Ваш доступ заблоковано.", + "dashboard.contractExpired.nonTrial.description": "Зверніться до адміністратора облікового запису, щоб відновити доступ.", + "dashboard.contractExpired.buttonTitle": "Зв’язатися з нами", "loading": "Завантаження…", "untitled": "Без назви", "empty_value": "порожнє значення", + "deleted_value": "видалене значення", "gs.list.cancel": "Скасувати", "gs.list.apply": "Застосувати", "gs.list.only": "Лише", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Взаємодію налаштовано.", "messages.drill.InteractionUpdatedSuccess": "Взаємодію оновлено.", "filterBar.label": "Фільтри", + "filterBar.restrictedFilters.remove": "Видалити обмежені фільтри", + "filterBar.restrictedFilters.reason": "{count, plural, one {У вас немає доступу до # фільтра.} few {У вас немає доступу до # фільтрів.} many {У вас немає доступу до # фільтрів.} other {У вас немає доступу до # фільтра.}}", + "filterBar.restrictedFilters": "{count, plural, one {Не застосовано # фільтр} few {Не застосовано # фільтри} many {Не застосовано # фільтрів} other {Не застосовано # фільтра}}", "filterBar.showAll": "Показати все", "filterBar.showLess": "Показати менше", "filterBar.filterListAutoExpanded": "Список фільтрів було автоматично розгорнуто", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "заплановані експорти", "deleteWidgetDialog.headline": "Видалити віджет?", "deleteWidgetDialog.objectsMessage": "Наведені нижче типи об’єктів, пов’язаних із віджетом {title}, більше не працюватимуть:", + "deleteWidgetDialog.objectsMessage.restricted": "Такі типи об’єктів, пов’язаних з елементом, більше не працюватимуть:", "deleteWidgetDialog.alerts": "сповіщення", "deleteWidgetDialog.schedules": "заплановані експорти", "create.dashboard": "Створити інформаційну панель", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} не можна фільтрувати за {parentTitle}. Між атрибутами фільтра в моделі даних немає зв’язку.", "attributesDropdown.details.dataset": "Набір даних", "attributesDropdown.details.values": "Значення", + "attributesDropdown.details.definedAs": "Визначено як", "attributesDropdown.details.shortening_decoration": "…і ще {count}", "layout.header.characters.left": "Залишилося символів: {currentCharactersCount} / {maximumCharactersCount}", "layout.header.add.title.placeholder": "Додайте заголовок тут...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Візуалізацію не можна фільтрувати за {attributeName}. Зніміть прапорець.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI не можна фільтрувати за {attributeName}. Зніміть прапорець.", "configurationPanel.vizCantBeFilteredByDate": "Візуалізацію не можна фільтрувати за датою. Зніміть прапорець.", + "configurationPanel.dateDatasetsLoadFailed": "Не вдалося завантажити пов’язані дані дат. Закрийте панель конфігурації та повторіть спробу.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Фільтр не можна застосувати до цієї візуалізації.", "configurationPanel.mvfBlockedByRankingFilter": "Цей фільтр не можна застосувати, оскільки візуалізація використовує фільтр верхніх/нижніх значень. Видаліть фільтр верхніх/нижніх значень, щоб застосувати цей фільтр.", "configurationPanel.mvfMissingMeasure": "Метрика, яка використовується в цьому фільтрі, більше не існує в робочій області.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Перейти до візуалізації", "configurationPanel.drillConfig.drillDown": "Деталізувати вниз", "configurationPanel.drillConfig.disableDrillDownTooltip": "Деталізацію вимкнено в конфігурації цієї візуалізації.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Обчислювані атрибути неможливо деталізувати.", "configurationPanel.drillConfig.drillIntoUrl": "Перейти за URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Недійсні параметри URL", "configurationPanel.drillConfig.select": "Виберіть дію…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Видалити форматований текст?", "richText.deleteDialog.message": "Форматований текст і весь його вміст буде безповоротно видалено.", "richText.emptyContent": "Немає вмісту форматованого тексту", + "visualizationSwitcher.restrictedEntry": "Обмежено", "visualizationSwitcher.emptyContent": "У перемикачі немає візуалізації", "visualizationSwitcherToolbar.visualizationsList.header": "Візуалізації", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Візуалізації", @@ -809,6 +823,7 @@ "granularity.hour": "година", "granularity.minute": "хвилина", "widget.options.menu.export": "Експортувати", + "insightMenu.restrictedWidget.options": "Параметри візуалізації з обмеженим доступом", "menu.close": "Закрити меню", "controlButtons.asTable": "Показати як таблицю", "controlButtons.asOriginal": "Показати початкову візуалізацію", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/vi-VN.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/vi-VN.json index c9193ed6665..56d3e2da19e 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/vi-VN.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/vi-VN.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "Bản dùng thử của bạn đã kết thúc.", + "dashboard.contractExpired.trial.description": "Liên hệ với chúng tôi nếu bạn muốn tiếp tục dùng GoodData Cloud.", + "dashboard.contractExpired.nonTrial.title": "Quyền truy cập của bạn đã bị khóa.", + "dashboard.contractExpired.nonTrial.description": "Liên hệ với quản trị viên tài khoản để lấy lại quyền truy cập.", + "dashboard.contractExpired.buttonTitle": "Liên hệ với chúng tôi", "loading": "Đang tải…", "untitled": "Không có tiêu đề", "empty_value": "giá trị trống", + "deleted_value": "giá trị đã xóa", "gs.list.cancel": "Hủy", "gs.list.apply": "Áp dụng", "gs.list.only": "Chỉ", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "Đã cấu hình tương tác.", "messages.drill.InteractionUpdatedSuccess": "Đã cập nhật tương tác.", "filterBar.label": "Bộ lọc", + "filterBar.restrictedFilters.remove": "Xóa các bộ lọc bị hạn chế", + "filterBar.restrictedFilters.reason": "{count, plural, other {Bạn không có quyền truy cập vào # bộ lọc.}}", + "filterBar.restrictedFilters": "{count, plural, other {# bộ lọc chưa được áp dụng}}", "filterBar.showAll": "Hiện tất cả", "filterBar.showLess": "Hiện ít", "filterBar.filterListAutoExpanded": "Danh sách bộ lọc đã được tự động mở rộng", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "các mục xuất theo lịch", "deleteWidgetDialog.headline": "Loại bỏ tiện ích?", "deleteWidgetDialog.objectsMessage": "Các loại đối tượng sau liên kết với tiện ích {title} sẽ không còn hoạt động:", + "deleteWidgetDialog.objectsMessage.restricted": "Các loại đối tượng sau đây được liên kết với tiện ích sẽ không còn hoạt động:", "deleteWidgetDialog.alerts": "cảnh báo", "deleteWidgetDialog.schedules": "các mục xuất theo lịch", "create.dashboard": "Tạo bảng điều khiển", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} không thể được lọc theo {parentTitle}. Không có kết nối giữa các thuộc tính bộ lọc trong mô hình dữ liệu.", "attributesDropdown.details.dataset": "Tập dữ liệu", "attributesDropdown.details.values": "Giá trị", + "attributesDropdown.details.definedAs": "Được định nghĩa là", "attributesDropdown.details.shortening_decoration": "…và {count} nữa", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} ký tự còn lại", "layout.header.add.title.placeholder": "Thêm Tiêu đề tại đây...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "Trực quan hóa không thể được lọc theo {attributeName}. Bỏ chọn hộp kiểm.", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI không thể được lọc theo {attributeName}. Bỏ chọn hộp kiểm.", "configurationPanel.vizCantBeFilteredByDate": "Trực quan hóa không thể được lọc theo Ngày. Bỏ chọn hộp kiểm.", + "configurationPanel.dateDatasetsLoadFailed": "Không thể tải dữ liệu ngày liên quan. Hãy đóng bảng cấu hình và thử lại.", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "Bộ lọc không áp dụng được cho trực quan hóa này.", "configurationPanel.mvfBlockedByRankingFilter": "Không thể áp dụng bộ lọc này vì trực quan hóa sử dụng bộ lọc giá trị Trên cùng/dưới cùng. Hãy xóa bộ lọc giá trị Trên cùng/dưới cùng để áp dụng bộ lọc này.", "configurationPanel.mvfMissingMeasure": "Thước đo được dùng trong bộ lọc này không còn tồn tại trong không gian làm việc.", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "Đi sâu vào trực quan hóa", "configurationPanel.drillConfig.drillDown": "Đi sâu xuống", "configurationPanel.drillConfig.disableDrillDownTooltip": "Đi sâu xuống đang tắt trong cấu hình của trực quan hóa này.", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "Tính năng đi sâu xuống không khả dụng cho các thuộc tính được tính toán.", "configurationPanel.drillConfig.drillIntoUrl": "Đi sâu vào URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "Tham số URL không hợp lệ", "configurationPanel.drillConfig.select": "Chọn hành động…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "Xóa văn bản có định dạng?", "richText.deleteDialog.message": "Văn bản có định dạng và toàn bộ nội dung của nó sẽ bị xóa vĩnh viễn.", "richText.emptyContent": "Không có nội dung văn bản đa định dạng", + "visualizationSwitcher.restrictedEntry": "Bị hạn chế", "visualizationSwitcher.emptyContent": "Không có trực quan hóa trong trình chuyển đổi", "visualizationSwitcherToolbar.visualizationsList.header": "Trực quan hóa", "visualizationSwitcherToolbar.visualizationsList.tooltip": "Trực quan hóa", @@ -809,6 +823,7 @@ "granularity.hour": "giờ", "granularity.minute": "phút", "widget.options.menu.export": "Xuất", + "insightMenu.restrictedWidget.options": "Tùy chọn trực quan hóa bị hạn chế", "menu.close": "Đóng menu", "controlButtons.asTable": "Hiển thị dưới dạng bảng", "controlButtons.asOriginal": "Hiển thị trực quan hóa ban đầu", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-HK.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-HK.json index bbc8647ed95..90d82917ceb 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-HK.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-HK.json @@ -1,19 +1,25 @@ { + "dashboard.contractExpired.trial.title": "您的試用期已結束。", + "dashboard.contractExpired.trial.description": "如果您想繼續使用GoodData Cloud,請聯繫我們。", + "dashboard.contractExpired.nonTrial.title": "您的存取權限已被鎖定。", + "dashboard.contractExpired.nonTrial.description": "請與帳戶管理員聯繫以重新獲得存取權限。", + "dashboard.contractExpired.buttonTitle": "聯絡我們", "loading": "載入中...", "untitled": "《無題》", "empty_value": "空值", + "deleted_value": "已刪除值", "gs.list.cancel": "取消", "gs.list.apply": "應用", "gs.list.only": "只", "attributesDropdown.onlyLabel": "只揀{itemTitle}", - "messages.dashboardSaveSuccess": "很好. 我哋儲存咗你嘅儀錶板。", + "messages.dashboardSaveSuccess": "很好。我們已儲存您的儀表板。", "options.menu.save.as": "另存新檔", "options.menu.save.as.tooltip": "無法儲存空儀表板。", "options.menu.settings": "設置", "messages.dashboardSaveFailed": "控制面板保存失敗。 刪除任何不可用的可視化效果,然後重試,或聯繫您的管理員。", - "messages.dashboard.incompatibleDefaultFilters.title": "警告無法應用某些儀表板篩選器。 檢查篩選條件以確保你查看的數據正確無誤。", + "messages.dashboard.incompatibleDefaultFilters.title": "警告無法應用某些儀表板篩選器。 檢查篩選條件以確保您查看的數據正確無誤。", "messages.dashboard.invalidDrills.title": "一些交互已被刪除", - "messages.dashboard.invalidDrills.body.modern": "一個或多個儀表板/可視化/計量/屬性已被更改或刪除,導致從以下小組件中刪除了一些交互: {listOfWidgetTitles}", + "messages.dashboard.invalidDrills.body.modern": "一個或多個儀表板/可視化/指標/屬性已被更改或刪除,導致從以下小組件中刪除了一些交互: {listOfWidgetTitles}", "messages.dashboard.sanitizedDrills.title": "部分互動設定已更新", "messages.dashboard.sanitizedDrills.body.modern": "部分已儲存的下鑽篩選器設定不再符合目前的儀表板或可視化,並已從以下元件中移除:{listOfWidgetTitles}", "messages.dashboard.invalidCustomUrlDrills.title": "某些切入到URL交互不起作用", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "已配置交互。", "messages.drill.InteractionUpdatedSuccess": "交互已更新。", "filterBar.label": "篩選器", + "filterBar.restrictedFilters.remove": "移除受限制的篩選器", + "filterBar.restrictedFilters.reason": "{count, plural, other {您無權存取 # 個篩選器。}}", + "filterBar.restrictedFilters": "{count, plural, other {未套用 # 個篩選器}}", "filterBar.showAll": "顯示全部", "filterBar.showLess": "顯示簡要信息", "filterBar.filterListAutoExpanded": "篩選器清單已自動展開", @@ -31,7 +40,7 @@ "filterBar.invalidFilterSelection": "要應用更改,請為以下過濾器選擇有效值: {names}", "filterBar.applyFilters": "套用所有篩選器", "geochart.alternateView.tablist.label": "圖層表格", - "filterBar.executionTimestampNotificationMessage": "注意。 你正在查看最新數據,但對於任何相對日期篩選器,儀表板都將其視為{date}“今天”。", + "filterBar.executionTimestampNotificationMessage": "注意。 您正在查看最新數據,但對於任何相對日期篩選器,儀表板都將其視為{date}“今天”。", "filterBar.executionTimestampNotificationAction": "重置至今日", "options.menu.export": "匯出", "options.menu.export.header.data": "數據", @@ -86,7 +95,7 @@ "dialogs.export.pdf.pageOrientation.option.landscape": "橫向", "dialogs.export.pdf.includePageExportInfo": "包括頁面導出信息", "dialogs.schedule.email.accessibilityTitle": "定時電子郵件", - "dialogs.schedule.email.delete.success": "成功! 你排程的電子郵件已刪除。", + "dialogs.schedule.email.delete.success": "成功! 您排程的電子郵件已刪除。", "dialogs.schedule.email.submit.success": "成功! 您的儀表板已排程透過電子郵件發送。", "dialogs.schedule.email.submit.error": "無法排程儀表板。 請稍後再試。", "dialogs.schedule.email.save.success": "成功! 您的排程已儲存。", @@ -96,8 +105,8 @@ "dialogs.save.as.new.title": "將儀表板另存為新儀表板", "dialogs.save.as.new.desc": "為新儀表板指定一個新的描述性名稱,以便以後更容易找到它。", "dialogs.save.as.new.default.title": "副本 {title}", - "dialogs.save.as.new.alertsAndEmailsMessage": "警示和電子郵件計劃唔會重複", - "dialogs.save.as.new.alertsMessage": "警示唔會重複", + "dialogs.save.as.new.alertsAndEmailsMessage": "警示和電子郵件排程不會被複製", + "dialogs.save.as.new.alertsMessage": "警示不會被複製", "dialogs.save.as.new.emailsMessage": "電子郵件排程不會重複", "cancelConfirmationDialog.headline": "放棄更改?", "cancelConfirmationDialog.message": "所有更改都將丟失", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "計劃導出", "deleteWidgetDialog.headline": "刪除小部件?", "deleteWidgetDialog.objectsMessage": "與小組件{title}關聯的以下類型對象將不再有效:", + "deleteWidgetDialog.objectsMessage.restricted": "與元件關聯的以下物件類型將不再運作:", "deleteWidgetDialog.alerts": "警報", "deleteWidgetDialog.schedules": "計劃導出", "create.dashboard": "創建儀表板", @@ -149,20 +159,20 @@ "drill_modal_picker.drill_to_url.disabled.missing_attributes": "錯誤:無法鑽研。請選取包含以下內容的項目:{attributes}。", "drill_modal_picker.label": "選擇drill action", "drillModal.backToTop": "返回頁首", - "filters.alertMessage.relativePreset": "係 {calendarIcon} {range}", + "filters.alertMessage.relativePreset": "在 {calendarIcon} {range}", "filters.alertMessage.relativePreset.inPeriod": "在時期 {calendarIcon} {range}", "filters.config.warning.multipleWorkspacesConfigs": "有多個工作區日期篩選器配置與此工作區關聯。 使用了第一個。", "filters.config.warning.selectedFilterNotValid": "默認篩選器選項無效,而是使用第一個可用選項。", "filters.config.warning.allOptionsHidden": "儀表板日期篩選器配置被忽略,因為它會隱藏所有篩選器選項。", "filters.config.warning.conflictingIdentifiers": "儀表板日期篩選器配置被忽略,因為它包含具有相同標識符的日期篩選器選項。", - "filters.config.warning.workspace.notAvailable": "工作區日期篩選器配置未定義,抑或你冇存取權限。", + "filters.config.warning.workspace.notAvailable": "工作區日期篩選器配置尚未定義,或您沒有存取權限。", "filters.configurationPanel.header": "篩選器", "filters.configurationPanel.crossFiltering.toggle": "交叉篩選", - "filters.configurationPanel.crossFiltering.toggle.tooltip": "允許用戶透過單擊可視化中嘅數據啲嚟篩選儀表板。", + "filters.configurationPanel.crossFiltering.toggle.tooltip": "允許使用者透過點擊可視化中的數據點來篩選儀表板。", "filters.configurationPanel.userFilterReset.toggle": "重置", "filters.configurationPanel.userFilterReset.toggle.tooltip": "允許用戶將所有過濾器重置為默認值。", "filters.configurationPanel.userFilterSave.toggle": "自動保存上一個狀態", - "filters.configurationPanel.userFilterSave.toggle.tooltip": "允許自動保存過濾器嘅最後狀態。 当用戶返回時,佢哋嘅過濾器將保持原樣。", + "filters.configurationPanel.userFilterSave.toggle.tooltip": "允許自動儲存篩選器的最後狀態。 當使用者返回時,其篩選器將保持原樣。", "filters.configurationPanel.filterViews.toggle": "儲存視圖", "filters.configurationPanel.filterViews.toggle.tooltip": "

允許使用者將篩選器狀態儲存為個人視圖,以便以後返回。

只有儲存視圖的使用者才能訪問該視圖並將其設置為默認視圖。

", "filters.configurationPanel.persistentFiltersAcrossTabs.toggle": "跨分頁保留篩選器", @@ -170,7 +180,7 @@ "settingsDashboardDialog.sectionHeaders.dateDataset": "描述中的日期範圍", "settingsDashboardDialog.sectionHeaders.dateDataset.tooltip": "日期範圍過濾器用於章節描述中指標參考的日期數據集。", "dateDataset.autoSelect": "自動選擇", - "dateDataset.notFound": "搵唔到", + "dateDataset.notFound": "找不到", "dialogs.schedule.email.attachments.label": "附件", "dialogs.schedule.email.title.placeholder": "《無標題》時間表", "dialogs.alert.title.placeholder": "警報名稱", @@ -179,23 +189,23 @@ "dialogs.alert.accessibility.label.title": "警報名稱", "dialogs.schedule.accessibility.label.email.title": "排程名稱", "dialogs.schedule.email.message.label": "消息", - "dialogs.schedule.email.max.recipients": "錯誤:收件人過多-你最多可以加起來。{maxRecipients}", + "dialogs.schedule.email.max.recipients": "錯誤:收件人過多-您最多可以加起來。{maxRecipients}", "dialogs.schedule.email.min.recipients": "錯誤:必填字段-選擇收件人以繼續。", "dialogs.schedule.email.usersLoad.error": "錯誤:無法加載用戶-請稍後重試。", "dialogs.schedule.email.user.missing.email": "錯誤:以下收件人的電子郵件地址無效-請修復它們以繼續: {recipients}。", "dialogs.schedule.email.user.missing.email.tooltip": "錯誤:此收件人的電子郵件地址無效。", - "dialogs.schedule.email.user.invalid.external": "錯誤:以下收件人冇所需嘅訪問權限-請刪除佢哋以繼續: {recipients}。", + "dialogs.schedule.email.user.invalid.external": "錯誤:以下收件人沒有所需的存取權限 — 請移除他們以繼續:{recipients}。", "dialogs.schedule.email.user.invalid.external.tooltip": "錯誤:此收件人沒有所需的訪問權限。", "dialogs.schedule.email.user.invalid.onlyYou": "錯誤:只有您可以成為收件人-刪除其他人以繼續。", "dialogs.schedule.email.user.used.external": "電子郵件“{email}”不屬於此工作區。 接收者可能會收到敏感數據。", "dialogs.schedule.email.user.remove": "刪除 {name}", "dialogs.schedule.email.userAndEmail.remove": "刪去 {name} ({email})", - "dialogs.schedule.email.user.warning.external": "警告:外部收件人-“{email}”將收到與你相同的儀表板视圖。", + "dialogs.schedule.email.user.warning.external": "警告:外部收件人-“{email}”將收到與您相同的儀表板視圖。", "dialogs.schedule.email.user.unknown": "這是一名未知使用者,無法添加為收件人。", - "dialogs.schedule.email.user.noMatch": "冇匹配嘅使用者或組。", + "dialogs.schedule.email.user.noMatch": "沒有相符的使用者或群組。", "dialogs.schedule.email.user.notMe": "此使用者不被允許。 收件人僅能只有您。", "dialogs.schedule.email.user.guest": "(遊客)", - "dialogs.schedule.email.recipients.note": "注意:所有標記為“( guest )”的收件人都將收到與您相同的控制面板视圖。", + "dialogs.schedule.email.recipients.note": "注意:所有標記為“( guest )”的收件人都將收到與您相同的控制面板視圖。", "dialogs.schedule.email.subject.label": "主題", "dialogs.schedule.email.recipients.label": "收件人", "dialogs.schedule.email.filters": "過濾器({count})", @@ -204,7 +214,7 @@ "dialogs.schedule.email.destination": "目的地", "dialogs.schedule.email.destination.missing": "沒有可用的目的地。", "dialogs.schedule.email.destination.help": "創建目標", - "dialogs.schedule.email.message.placeholder": "你好,你預定嘅電郵已經準備就緒。 你可以喺附件中下載儀表板。", + "dialogs.schedule.email.message.placeholder": "您好,您排程的電郵已準備就緒。 您可以在附件中下載儀表板。", "dialogs.schedule.email.create": "創建", "dialogs.schedule.email.closeLabel": "關閉對話框", "dialogs.schedule.email.backLabel": "返回上一步", @@ -215,28 +225,28 @@ "dialogs.schedule.email.save": "儲存", "dialogs.schedule.email.save.executionTimestampMode": "要創建或編輯預定的匯出,請將儀表板日期重置為今日。", "dialogs.alert.save.executionTimestampMode": "要創建或編輯警報,請將儀表板日期重置為今日。", - "dialogs.schedule.email.widgetError": "錯誤:無法發送時間表 — 源小部件已被刪除。 刪除時間表或聯絡你的管理員。", - "dialogs.schedule.email.destinationWarning": "所選目標僅支持將匯出發送畀自己。", + "dialogs.schedule.email.widgetError": "錯誤:無法發送時間表 — 源小部件已被刪除。 刪除時間表或聯絡您的管理員。", + "dialogs.schedule.email.destinationWarning": "所選目標僅支援將匯出傳送給自己。", "dialogs.automation.filters.showAll": "顯示全部", - "dialogs.automation.filters.showAll.ariaDescription": "用嚟顯示所有濾鏡。", + "dialogs.automation.filters.showAll.ariaDescription": "用於顯示所有篩選器。", "dialogs.automation.filters.showLess": "顯示簡要信息", - "dialogs.automation.filters.showLess.ariaDescription": "用嚟顯示少啲濾鏡。", + "dialogs.automation.filters.showLess.ariaDescription": "用於顯示較少篩選器。", "dialogs.automation.filters.add": "添加篩選器", "dialogs.automation.filters.addDisabled": "添加過濾器已禁用 — 所有過濾器都已添加。", "dialogs.automation.filters.title": "添加篩選器", - "dialogs.automation.filters.noFilters": "冇更多可用嘅過濾器", - "dialogs.automation.filters.useFiltersMessage": "始終將呢啲篩選器用于將來嘅導出", + "dialogs.automation.filters.noFilters": "沒有更多可用的篩選器", + "dialogs.automation.filters.useFiltersMessage": "日後的匯出一律使用這些篩選器", "dialogs.automation.filters.searchAriaLabel": "搜尋過濾器", "dialogs.automation.filters.deleteAriaLabel": "除濾網", "dialogs.automation.filters.deleteTooltip": "除濾網", - "dialogs.automation.filters.lockedTooltip": "呢個過濾器係上鎖嘅,唔可以拆。", + "dialogs.automation.filters.lockedTooltip": "此篩選器已鎖定,無法移除。", "dialogs.automation.parameters.chip": "{title} 為 {value}", "automationParameters.announcement.added": "參數 {title} 已新增。", "automationParameters.announcement.removed": "參數 {title} 已移除。", "automationParameters.announcement.changed": "參數 {title} 已變更為 {value}。", - "dialogs.automation.filters.useFiltersMessage.tooltip": "選中以保留呢啲過濾器以進行每次導出。 如果未選中,導出將使用儀錶板中最新嘅默認過濾器。", + "dialogs.automation.filters.useFiltersMessage.tooltip": "勾選以在每次匯出時保留這些篩選器。 如果未勾選,匯出將使用儀表板中最新的預設篩選器。", "dialogs.automation.filters.useFiltersMessage.parameters.tooltip": "勾選以保留這些篩選器及參數值供每次匯出使用。若未勾選,匯出會使用儀表板的最新預設值。", - "dialogs.automation.filters.activeFilters": "注意:呢啲過濾器將始終用于將來嘅導出。", + "dialogs.automation.filters.activeFilters": "注意:這些篩選器將一律用於日後的匯出。", "dialogs.schedule.email.timezone": "時區", "dialogs.automation.timezone.default": "預設", "dialogs.automation.timezone.default.tooltip": "遵循為儀表板設定的時區;如儀表板未設定時區,則遵循為您的組織、工作區或使用者帳戶設定的時區。如該設定變更,時區會自動更新。", @@ -256,7 +266,7 @@ "dialogs.automation.applyCurrentFilters.schedule.confirm": "警告:創建此計劃時使用的某些篩選器不再可用或無效。 編輯將應用當前的控制面板過濾器。", "dialogs.automation.applyCurrentFilters.schedule.timezone.confirm": "警告:建立此計劃時所用的時區不再與儀表板相符。編輯將套用儀表板目前的時區。", "dialogs.automation.applyCurrentFilters.schedule.filtersAndTimezone.confirm": "警告:建立此計劃時所用的部分篩選器和時區不再可用或有效。編輯將套用目前的儀表板篩選器和時區。", - "dialogs.automation.filters.schedule.ariaLabel": "有關選項嘅信息:將來導出時,請始終使用呢啲過濾器。", + "dialogs.automation.filters.schedule.ariaLabel": "有關此選項的資訊:日後匯出時一律使用這些篩選器。", "dialogs.schedule.management.title": "排程匯出", "dialogs.schedule.management.delete.dialog": "刪除排程?", "dialogs.schedule.management.delete.dialog.confirm": "該排程 將被永久刪除。", @@ -264,7 +274,7 @@ "dialogs.schedule.management.create": "創建", "dialogs.schedule.management.create.new": "創新", "dialogs.schedule.management.create.tooManyMessage": "您的組織已達到計劃導出的限制。 請與您的管理員聯繫以創建更多。", - "dialogs.schedule.management.noSchedules": "冇計劃嘅導出。{br}新建。", + "dialogs.schedule.management.noSchedules": "沒有已排程的匯出。{br}新建。", "dialogs.schedule.management.attachments.xlsx.settings": "XLSX選項", "dialogs.schedule.management.attachments.csv.settings": "CSV 選項", "dialogs.schedule.management.attachments.cells.merged": "保持屬性儲存格合併", @@ -274,7 +284,7 @@ "dialogs.export.pdf.headline": "匯出設定 (.pdf)", "dialogs.schedule.management.attachments.delete": "刪除 {format} 附件", "dialogs.schedule.management.attachments.removed": "已移除 {format} 附件。", - "dialogs.schedule.management.attachments.message": "注意。 交叉篩選將唔會喺排程嘅匯出中應用。", + "dialogs.schedule.management.attachments.message": "注意。 交叉篩選將不會套用於排程的匯出中。", "scheduledEmail.attachmentFormat.widget.png": "快照(.png)", "scheduledEmail.attachmentFormat.widget.pptx": "單滑軌(.pptx)", "scheduledEmail.attachmentFormat.widget.pdf": "單滑軌(.pdf)", @@ -302,7 +312,7 @@ "dialogs.alerting.footer.title": "如何創建警示?", "dialogs.alerting.footer.title.short": "幫助", "dialogs.alerting.management.list.title": "您的警示", - "dialogs.alerting.management.noAlerts": "冇警示。", + "dialogs.alerting.management.noAlerts": "沒有警示。", "dialogs.alerting.title.placeholder": "無標題警示", "dialogs.alerting.management.delete.dialog": "刪除警示?", "dialogs.alerting.management.delete.dialog.confirm": "警報及其所有通知將被永久刪除。", @@ -322,9 +332,9 @@ "gs.date.tomorrow": "聽日", "gs.date.yesterday": "昨日", "messages.exportResultError": "無法匯出數據。 請稍後再試。", - "messages.exportResultError.tooManyRows": "出口失敗。 行太多 ({actual})。 限制係 {limit}。", + "messages.exportResultError.tooManyRows": "匯出失敗。 行數過多({actual})。 上限為 {limit}。", "messages.exportResultError.timeout": "匯出逾時。請縮小匯出範圍(較少列或欄),然後再試一次。", - "messages.exportResultRestrictedError": "你無法導出此可視化效果,因為它包含受限數據。", + "messages.exportResultRestrictedError": "您無法導出此可視化效果,因為它包含受限數據。", "messages.exportResultStart": "匯出中", "messages.exportResultSuccess": "匯出成功。", "messages.sharingChangedSuccess": "共享已更新。", @@ -332,17 +342,17 @@ "messages.shareLinkCopied": "連結已複製。", "messages.sharingDialogError.general": "無法載入共享設置。 請稍後再試。", "messages.settingsDialogError.general": "無法加載儀表板設置。 請稍後重試。", - "visualization.dataTooLarge.headline": "顯示嘅數據啲過多", + "visualization.dataTooLarge.headline": "要顯示的數據點過多", "visualization.dataTooLarge.text": "嘗試將一個或多個過濾器應用於儀表板。", - "visualization.execute_protected_report.headline": "你無權查看此報告", + "visualization.execute_protected_report.headline": "您無權查看此報告", "visualization.execute_protected_report.text": "請與管理員聯繫。", - "visualization.empty.headline": "冇啱您的篩選器選擇嘅數據", - "visualization.error.headline": "抱歉,我哋無法顯示此可視化效果", + "visualization.empty.headline": "沒有符合您篩選器選擇的數據", + "visualization.error.headline": "抱歉,我們無法顯示此可視化", "visualization.error.text": "請與管理員聯繫。", "visualization.ErrorMessageResultCacheMissing": "可視化需要更新", "visualization.ErrorDescriptionResultCacheMissing": "基礎數據已更改或過期。 重新整理頁面。", - "visualization.warning.export.too_small": "冇足夠嘅可視化空間", - "options.menu.unsupported.error": "目前無法匯出可視化效果。 嘗試應用不同的篩選器,或使用不同的計量或屬性。", + "visualization.warning.export.too_small": "可視化空間不足", + "options.menu.unsupported.error": "目前無法匯出可視化效果。 嘗試應用不同的篩選器,或使用不同的指標或屬性。", "options.menu.unsupported.loading": "目前無法匯出可視化效果。", "options.menu.unsupported.incompatibleWidget": "無法排程自定義小組件的匯出。", "options.menu.unsupported.schedulingForInsightNotEnabled": "您無法排程匯出該小組件。", @@ -378,14 +388,14 @@ "widget.drill.dialog.exportToCSV.raw": "原始(.csv)", "share.button.text": "共享", "share.button.tooltip": "共享儀表板", - "dashboard.error.empty.heading": "此儀表板係空的", + "dashboard.error.empty.heading": "此儀表板是空的", "dashboard.error.empty.text": "已刪除所有可視化效果。", "dashboard.shareDialog.underLenientControl.label": "如果其他使用者切入到此儀表板,則可以訪問此儀表板", "dashboard.shareDialog.lock.label": "只有管理員才能編輯此儀表板", "header.lockStatus.tooltip": "此儀表板已鎖定。 只有管理員才能編輯此儀表板。", "header.shareStatus.private.text": "私人", "header.shareStatus.private.strict.tooltip": "只有您可以查看和編輯此儀表板。", - "header.shareStatus.private.not.strict.tooltip": "創建者可以編輯此儀錶板。 具有適當用戶角色嘅其他使用者可以喺切入到此儀錶板時編輯此儀錶板。", + "header.shareStatus.private.not.strict.tooltip": "建立者可以編輯此儀表板。 具有適當使用者角色的其他使用者可以在向下切入到此儀表板時進行編輯。", "topBar.timezoneIndicator.label": "時區: ", "topBar.timezoneIndicator.tooltip": "時區", "dashboard.shareDialog.removeAccessGranteeTooltip": "刪除對此儀表板的存取權限。", @@ -403,7 +413,7 @@ "attributesDropdown.selectionMode.multi": "多個值", "attributesDropdown.selectionMode.multi.tooltip": "選取多於一個值。某些條件(包含、開始於、結束於)只支援單一值。", "attributesDropdown.selectionMode.single": "單值", - "attributesDropdown.selectionMode.disabled.tooltip": "如果在“篩選依據”中選擇了任何篩選器,則唔允許選擇單值。 要使用“單值”選擇,請禁用“篩選依據”中嘅所有篩選器。", + "attributesDropdown.selectionMode.disabled.tooltip": "如果在“篩選依據”中選擇了任何篩選器,則不允許使用單值選擇。 若要使用單值選擇,請停用“篩選依據”中的所有篩選器。", "attributesDropdown.selectionType.as": "如", "attributesDropdown.selectionType.listOrText": "列表或文字", "attributesDropdown.selectionType.list": "清單", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} 無法被 {parentTitle} 篩選。資料模型中的篩選器屬性之間沒有連線。", "attributesDropdown.details.dataset": "數據", "attributesDropdown.details.values": "值", + "attributesDropdown.details.definedAs": "定義為", "attributesDropdown.details.shortening_decoration": "… 同{count}更多", "layout.header.characters.left": "{currentCharactersCount} / {maximumCharactersCount} 剩餘字符數", "layout.header.add.title.placeholder": "在此處添加標題...", @@ -427,7 +438,7 @@ "controlButtons.edit.tooltip": "編輯儀表板", "controlButtons.edit.executionTimestampMode.tooltip": "要編輯儀表板,請把日期重置為今日。", "controlButtons.setting.value": "設置", - "controlButtons.setting.title": "與此儀表板相關的所有設置。 你可以更改儀表板描述交叉篩選、允許保存的篩選條件同警報評估頻率。", + "controlButtons.setting.title": "與此儀表板相關的所有設置。 您可以更改儀表板描述交叉篩選、允許保存的篩選條件同警報評估頻率。", "controlButtons.saveAndPublish.value": "儲存並發佈", "controlButtons.save.value": "儲存", "controlButtons.saveAndPublish.title": "所有更改將立即對可以存取此儀表板的每個人可見。", @@ -437,10 +448,10 @@ "controlButtons.saving.value": "儲存...", "gs.visualizationsList.tabs.all": "都", "gs.visualizationsList.tabs.my": "由我創建", - "visualizationsList.noInsights": "此工作區唔包含任何可視化效果。", + "visualizationsList.noInsights": "此工作區不包含任何可視化。", "visualizationsList.create": "創建可視化效果", "visualizationsList.noUserInsights": "未創建任何可視化效果。", - "visualizationsList.noVisualizationsFound": "冇匹配嘅可視化效果。", + "visualizationsList.noVisualizationsFound": "沒有相符的可視化。", "search_insights": "搜尋所有可視化效果…", "search_insights.similar": "相似", "save.as.new": "另存新檔", @@ -457,9 +468,9 @@ "addPanel.richTextWidget": "富文字元件", "addPanel.dashboardFilter": "儀表板篩選器", "addPanel.visualizationSwitcher": "可視化切換器", - "addPanel.visualizationSwitcher.tooltip": "把多個可視化合併到一個元素中,並使用導航下拉列表喺它們之間切換。", + "addPanel.visualizationSwitcher.tooltip": "將多個可視化合併到單一元素中,並使用導航下拉選單在它們之間切換。", "addPanel.dashboardLayout": "列容器", - "addPanel.dashboardLayout.tooltip": "創建垂直嘅Widget組,並將其放置喺分區內嘅單個列中。 非常適合堆疊多件物品。", + "addPanel.dashboardLayout.tooltip": "建立垂直的小工具群組,放置在區段內的單一欄中。 非常適合堆疊多個項目。", "addPanel.dashboardLayout.tooltip.learnMore": "瞭解更多信息", "sidebar.collapse": "收合面板", "sidebar.search": "搜尋可視化", @@ -470,8 +481,8 @@ "configurationPanel.drillConfig.targetTab": "去標籤", "configurationPanel.drillConfig.drillIntoDashboard.searchPlaceholder": "搜索所有儀表板...", "configurationPanel.drillConfig.forbiddenDashboard": "(未與您分享)", - "configurationPanel.drillConfig.forbiddenDashboard.tooltip": "儀錶板唔會與你共享,但其他用戶可以訪問此儀錶板。", - "configurationPanel.drillConfig.notSharedDashboard.tooltip": "儀錶板唔會與你共享,但所有用戶都可以訪問它,如果佢哋切入到它。", + "configurationPanel.drillConfig.forbiddenDashboard.tooltip": "儀表板未與您共享,但其他使用者可以存取此儀表板。", + "configurationPanel.drillConfig.notSharedDashboard.tooltip": "儀表板未與您共享,但所有使用者只要向下切入即可存取。", "configurationPanel.remove.form.dashboard": "從儀表板中刪除", "filterBar.filter.drop": "拖放以添加", "filterBar.filter.addFilterPlaceholder": "拖{icon}到呢度", @@ -481,38 +492,40 @@ "configurationPanel.filterBy": "篩選依據", "configurationPanel.dateAs": "如", "configurationPanel.vizCantBeFilteredByAttribute": "無法通過 {attributeName}篩選可視化效果。 取消選中該複選框。", - "configurationPanel.kpiCantBeFilteredByAttribute": "KPI唔可以透過 {attributeName}進行篩選。 取消選中該複選框。", + "configurationPanel.kpiCantBeFilteredByAttribute": "KPI 無法透過 {attributeName} 進行篩選。 請取消勾選該核取方塊。", "configurationPanel.vizCantBeFilteredByDate": "無法按日期篩選可視化效果。 取消選中該複選框。", + "configurationPanel.dateDatasetsLoadFailed": "無法載入相關日期資料。請關閉設定面板,然後再試一次。", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "篩選器不適用於此可視化。", "configurationPanel.mvfBlockedByRankingFilter": "無法套用此篩選器,因為此可視化使用了熱門/冷門值篩選器。請移除熱門/冷門值篩選器以套用此篩選器。", "configurationPanel.mvfMissingMeasure": "此篩選器中使用的指標已不再存在於工作區中。", "filters.mvf.missingMetric.tooltip": "錯誤:此篩選器中使用的指標已不再存在於工作區中。", - "configurationPanel.kpiCantBeFilteredByDate": "KPI唔可以按日期篩選。 取消選中該複選框。", + "configurationPanel.kpiCantBeFilteredByDate": "KPI 無法按日期篩選。 請取消勾選該核取方塊。", "configurationPanel.unrelatedVizDateInfo": "“{dateDataSet}”無法再應用於可視化效果。 選擇其他維度或編輯可視化效果。", - "configurationPanel.unrelatedKpiDateInfo": "“{dateDataSet}”唔可以再應用於KPI。 選擇其他維度或編輯KPI。", + "configurationPanel.unrelatedKpiDateInfo": "“{dateDataSet}”無法再套用於KPI。 請選擇其他維度或編輯KPI。", "configurationPanel.interactions": "相互作用", "configurationPanel.drillConfig.interactions": "切入交互", "configurationPanel.drillConfig.addInteraction": "添加交互", "configurationPanel.drillConfig.clickHint": "当我點擊時", - "configurationPanel.drillConfig.measureValue": "計量值", + "configurationPanel.drillConfig.measureValue": "指標值", "configurationPanel.drillConfig.attributeValue": "屬性值", - "configurationPanel.drillConfig.selectTarget": "我諗", + "configurationPanel.drillConfig.selectTarget": "我想要", "configurationPanel.drillConfig.clickHintItem": "當我點擊(值)時", "configurationPanel.drillConfig.drillIntoDashboard": "切入到儀表板", "configurationPanel.drillConfig.drillIntoInsight": "深入了解可視化效果", "configurationPanel.drillConfig.drillDown": "向下切入", "configurationPanel.drillConfig.disableDrillDownTooltip": "在此可視化的配置中,“向下切入”處於關閉狀態。", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "向下鑽取不適用於計算屬性。", "configurationPanel.drillConfig.drillIntoUrl": "切入到URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "無效的URL參數", "configurationPanel.drillConfig.select": "選擇操作...", - "configurationPanel.drillConfig.drillIntoDashboard.dateFilterWarning": "可視化效果中嘅日期屬性值唔會被傳輸以篩選目標儀錶板。", + "configurationPanel.drillConfig.drillIntoDashboard.dateFilterWarning": "可視化中的日期屬性值不會被傳輸以篩選目標儀表板。", "configurationPanel.drillConfig.filterSelection.button.withCount": "使用 {count, plural, other {# 個篩選器}}", "configurationPanel.drillConfig.filterSelection.subview.title": "篩選器", "configurationPanel.drillConfig.filterSelection.subview.tooltip": "所選篩選器會傳遞至鑽取目的地。", "configurationPanel.drillConfig.filterSelection.section.drilledDataPoint": "已鑽取的資料點", "configurationPanel.drillConfig.filterSelection.section.visualization": "可視化", "configurationPanel.drillConfig.filterSelection.section.dashboard": "儀表板", - "configurationPanel.drillConfig.filterSelection.section.metric": "計量", + "configurationPanel.drillConfig.filterSelection.section.metric": "指標", "configurationPanel.drillConfig.filterSelection.drillDownInsightFilter.tooltip": "可視化篩選器一律套用至向下鑽取。", "configurationPanel.drillConfig.filterSelection.drillDownDashboardFilter.tooltip": "儀表板篩選器一律套用至向下鑽取。", "configurationPanel.drillConfig.filterSelection.rankingFilter.tooltip": "頂部/底部篩選器無法傳遞至鑽取目的地。", @@ -533,8 +546,8 @@ "configurationPanel.visualprops.customDescription": "自定義", "configurationPanel.visualprops.customDescriptionHelp": "為此可視化效果實例添加自定義說明。", "configurationPanel.visualprops.noneDescription": "沒有", - "configurationPanel.visualprops.includeMetrics": "包括計量說明", - "configurationPanel.visualprops.includeMetricsHelp": "顯示部分,其中包含可視化計量列表及其描述。", + "configurationPanel.visualprops.includeMetrics": "包括指標說明", + "configurationPanel.visualprops.includeMetricsHelp": "顯示部分,其中包含可視化指標列表及其描述。", "configurationPanel.visualprops.descriptionPlaceholder": "添加描述...", "configurationPanel.drillIntoUrl.defaultButtonValue": "選擇網址", "configurationPanel.drillIntoUrl.attributeUrlSectionTitle": "屬性標籤中的超連結", @@ -589,7 +602,7 @@ "layout.widget.height.min": "Widget位於最小高度", "layout.widget.height.max": "細部件處於最大高度", "dropzone.new.row.desc": "拖放以建立{nbsp}新區段", - "dropzone.widget.desc": "放喺度", + "dropzone.widget.desc": "拖放至此", "dropzone.widget.last.in.row.desc": "拖放到 現有部分", "dropzone.widget.last.in.container.row.desc": "拖放到 現有容器", "deleteKpiConfirmationDialog.headline": "刪除KPI", @@ -599,7 +612,7 @@ "newDashboard.dropInsight": "液滴可視化", "attributesDropdown.filterConfiguredMessage": "{parentTitle} 已經被 {childTitle} 篩選器直接過濾或透過其他篩選器過濾。", "attributesDropdown.attributeNameWithData": "數據為", - "filter.visibilityMode.tooltip.hidden": "此篩選器係隱藏的。 它只能透過編輯模式存取。", + "filter.visibilityMode.tooltip.hidden": "此篩選器已隱藏。 只能透過編輯模式存取。", "filter.visibilityMode.tooltip.readonly.editMode": "此篩選器已鎖定。 它只能透過編輯模式進行更改。", "filter.visibilityMode.tooltip.readonly.viewMode": "此篩選器已鎖定。", "filter.resetButton.tooltip": "全部重設為預設值", @@ -622,19 +635,20 @@ "configurationPanel.respectCrossFiltering": "遵循交叉篩選", "configurationPanel.respectCrossFiltering.tooltip": "禁用此選項可忽略來自其他可視化的交叉篩選。", "richText.formattingOptions": "格式設置選項", - "richText.summaryDashboard.generating.reload": "我哋目前正喺產生儀表板摘要。請重新載入頁面,以查看產生程序是否已完成。", - "richText.summaryDashboard.generating.shortly": "我哋目前正喺產生儀表板摘要。將會喺短時間內提供。", + "richText.summaryDashboard.generating.reload": "我們目前正在產生儀表板摘要。請重新載入頁面,以查看產生程序是否已完成。", + "richText.summaryDashboard.generating.shortly": "我們目前正在產生儀表板摘要。將於短時間內提供。", "richText.summaryDashboard.failed": "儀表板摘要產生失敗。請稍後再試。", "richText.summaryDashboard.empty": "(未有可用的儀表板摘要)", "richText.deleteDialog.header": "刪除富文字?", "richText.deleteDialog.message": "富文字及其所有內容將被不可逆地刪除。", "richText.emptyContent": "無富文字內容", - "visualizationSwitcher.emptyContent": "切換器中冇可視化效果", + "visualizationSwitcher.restrictedEntry": "受限制", + "visualizationSwitcher.emptyContent": "切換器中沒有可視化", "visualizationSwitcherToolbar.visualizationsList.header": "可視化", "visualizationSwitcherToolbar.visualizationsList.tooltip": "可視化", "visualizationSwitcherToolbar.visualizationsList.prev": "以前", "visualizationSwitcherToolbar.visualizationsList.next": "下一個", - "visualizationSwitcherToolbar.visualizationsList.empty": "切換器中冇可視化效果,請添加一個以繼續。", + "visualizationSwitcherToolbar.visualizationsList.empty": "切換器中沒有可視化,請新增一個以繼續。", "visualizationSwitcherToolbar.visualizationsList.add": "添加可視化", "visualizationSwitcherToolbar.remove": "移除切換器", "visualizationSwitcher.configurationPanel.remove.from.switcher": "從切換器中刪除", @@ -654,9 +668,9 @@ "nestedLayout.emptyDropZone.message": "拖動到此處{br}添加到容器", "nestedLayout.emptyDropZone.active.message": "拖放到此處{br}添加到容器", "attributesDropdown.valuesLimiting.title": "按以下條件篩選值", - "attributesDropdown.valuesLimiting.empty": "透過應用儀錶板過濾器或添加計量嚟限制篩選器選項。", + "attributesDropdown.valuesLimiting.empty": "透過套用儀表板篩選器或新增指標來限制篩選器選項。", "attributesDropdown.valuesLimiting.addLink": "增加", - "attributesDropdown.valuesLimiting.addMetricTitle": "選擇計量", + "attributesDropdown.valuesLimiting.addMetricTitle": "選擇指標", "attributesDropdown.valuesLimiting.addFilterTitle": "選擇儀表板篩選器", "attributesDropdown.valuesLimiting.addItemTitle": "增加", "attributesDropdown.valuesLimiting.searchPlaceholder": "搜尋...", @@ -664,12 +678,12 @@ "attributesDropdown.valuesLimiting.countAttribute": "計數 {attribute}", "attributesDropdown.valuesLimiting.metricsEmpty": "無需添加更多項目。", "attributesDropdown.valuesLimiting.unknownItem": "未知項目", - "attributesDropdown.valuesLimiting.searchNoMatch": "未搵到匹配項。", + "attributesDropdown.valuesLimiting.searchNoMatch": "找不到相符的項目。", "attributesDropdown.valuesLimiting.filtersEmpty": "無需添加更多儀表板篩選器。", "attributesDropdown.valuesLimiting.addFilters.title": "儀表板篩選器", - "attributesDropdown.valuesLimiting.addFilters.description": "使用現有嘅儀錶板篩選器嚟隱藏唔相關嘅值。 ", - "attributesDropdown.valuesLimiting.addLimitingItem.title": "計量", - "attributesDropdown.valuesLimiting.addLimitingItem.description": "使用指標隱藏唔相關嘅值。 僅顯示指標結果為非null嘅值。 瞭解更多信息", + "attributesDropdown.valuesLimiting.addFilters.description": "使用現有的儀表板篩選器來隱藏不相關的值。 ", + "attributesDropdown.valuesLimiting.addLimitingItem.title": "指標", + "attributesDropdown.valuesLimiting.addLimitingItem.description": "使用指標隱藏不相關的值。 僅顯示該指標結果為非null的值。 了解更多", "attributesDropdown.valuesLimiting.commonDateFilterTitle": "{dateFilterTitle} 如 {dataSetTitle}", "attributesDropdown.valuesLimiting.addDateTitle": "選擇入住日期", "attributesDropdown.valuesLimiting.disableDateFilter": "篩選器不可用:篩選器 {dateFilterTitle} 已在使用此日期數據集。", @@ -682,11 +696,11 @@ "filters.filterViews.dropdown.unsetAsDefault": "取消設為預設值", "filters.filterViews.dropdown.unsetAsDefault.ariaLabel": "未設定 {name} 為預設", "filters.filterViews.dropdown.isDefault": "(默認)", - "filters.filterViews.dropdown.tooltip": "

視圖是儲存的篩選器集,可讓您快速返抵特定的過濾器狀態。

如果把视圖設置為默認視圖,則每次打開儀表板時,該視圖都將自動應用。

", + "filters.filterViews.dropdown.tooltip": "

視圖是儲存的篩選器集,可讓您快速返抵特定的過濾器狀態。

如果把視圖設置為默認視圖,則每次打開儀表板時,該視圖都將自動應用。

", "filters.filterViews.dropdown.newButton": "創建視圖", - "filters.filterViews.dropdown.emptyList": "你尚未儲存此儀表板的任何視圖。", + "filters.filterViews.dropdown.emptyList": "您尚未儲存此儀表板的任何視圖。", "filters.filterViews.toast.viewSaved": "很好!您的視圖已儲存。", - "filters.filterViews.toast.viewDeleted": "很好!我哋已刪除了您的視圖。", + "filters.filterViews.toast.viewDeleted": "很好!我們已刪除您的視圖。", "filters.filterViews.toast.viewApplied": "很好!您的視圖已被應用。", "filters.filterViews.toast.viewSetAsDefault": "很好!您的視圖設置為默認視圖。", "filters.filterViews.toast.viewUnsetAsDefault": "很好!您的視圖不再設置為默認視圖。", @@ -694,7 +708,7 @@ "filters.filterViews.toast.viewNotDeleted": "失敗! 您的視圖未被刪除。 請再試一次。", "filters.filterViews.toast.viewNotSetAsDefault": "失敗! 您的視圖未設置為默認視圖。 請再試一次。", "filters.filterViews.toast.viewNotUnsetAsDefault": "失敗! 您的視圖仍設置為默認視圖。 請再試一次。", - "filters.filterViews.toast.viewNotApplied": "失敗! 您的视圖未被應用。 請再試一次。", + "filters.filterViews.toast.viewNotApplied": "失敗! 您的視圖未被應用。 請再試一次。", "parameter.toast.reset": "警告: 參數「{name}」超出範圍,因此在您設定有效值之前,會套用預設值。", "parameter.toast.removed": "警告: 參數「{name}」已被移除,因為該參數已不存在。", "parameter.toast.incompatible": "警告: 參數「{name}」不再相容,已被忽略。", @@ -703,7 +717,7 @@ "filters.filterViews.list.ariaLabel.noActions": "已保存的視圖列表", "filters.filterViews.add.title": "創建視圖", "filters.filterViews.add.nameLabel": "名字", - "filters.filterViews.add.namePlaceholder": "無標題视圖", + "filters.filterViews.add.namePlaceholder": "無標題視圖", "filters.filterViews.add.setAsDefaultLabel": "設為預設值", "filters.filterViews.add.deleteTooltip": "刪除", "filters.filterViews.add.deleteTooltip.ariaLabel": "刪去 {name}", @@ -716,7 +730,7 @@ "filters.filterViews.delete.deleteButton": "刪除", "filters.filterViews.delete.cancelButton": "取消", "filters.filterViews.tooltip.ariaLabel": "有關已保存視圖的信息", - "filters.locked.filter.tooltip": "呢個過濾器上鎖咗", + "filters.locked.filter.tooltip": "此篩選器已鎖定", "insightAlert.config.action.edit": "編輯", "insightAlert.config.action.pause": "暫停", "insightAlert.config.action.resume": "取消暫停", @@ -726,12 +740,12 @@ "insightAlert.config.comparisonOperator.greaterThan": "大於", "insightAlert.config.comparisonOperator.greaterThanOrEquals": "大於或等於", "insightAlert.config.changeOperator.header": "漲跌幅%", - "insightAlert.config.changeOperator.header.info": "改變{spacer}計算主要(當前時間段)和次要(上一個時間段)計量值之間的相對變化。{spacer}公式{spacer}(小學 - 中學) / 中學", + "insightAlert.config.changeOperator.header.info": "改變{spacer}計算主要(當前時間段)和次要(上一個時間段)指標值之間的相對變化。{spacer}公式{spacer}(小學 - 中學) / 中學", "input.unit.percent": "百分比值", "insightAlert.config.differenceOperator.header": "差異 #", - "insightAlert.config.differenceOperator.header.info": "不同之處{spacer}計算主要(當前時間段)和次要(上一個時間段)計量值之間的淨差。{spacer}公式{spacer}小學 - 中學", + "insightAlert.config.differenceOperator.header.info": "不同之處{spacer}計算主要(當前時間段)和次要(上一個時間段)指標值之間的淨差。{spacer}公式{spacer}小學 - 中學", "insightAlert.config.aiOperator.header": "人工智能", - "insightAlert.config.aiOperator.header.info": "所有人工智能運算子都係基於人工智能函數。", + "insightAlert.config.aiOperator.header.info": "所有人工智能運算子都是基於人工智能函數。", "insightAlert.config.anomalyDetection": "有異常", "insightAlert.config.sensitivity": "靈敏度", "insightAlert.config.sensitivity.low": "低", @@ -757,16 +771,16 @@ "insightAlert.config.differenceOperator.decreasesBy": "減少", "insightAlert.config.differenceOperator.changesBy": "更改", "insightAlert.config.alerts": "通知", - "insightAlert.config.alerts.empty": "此可視化冇警示。", + "insightAlert.config.alerts.empty": "此可視化沒有警示。", "insightAlert.config.noAlertMeasures": "通知", - "insightAlert.config.noAlertMeasures.title": "冇可用於警示嘅計量。", - "insightAlert.config.noAlertMeasures.description": "請添加計量以啟用警示。", + "insightAlert.config.noAlertMeasures.title": "沒有可用於警示的指標。", + "insightAlert.config.noAlertMeasures.description": "請添加指標以啟用警示。", "insightAlert.config.trigger": "觸發", "insightAlert.config.interval": "觸發間隔", "insightAlert.config.interval.tooltip": "每個所選間隔最多只觸發一次(例如每天或每週一次)", - "insightAlert.config.metric": "計量", + "insightAlert.config.metric": "指標", "insightAlert.config.action": "行動", - "insightAlert.config.when": "幾時", + "insightAlert.config.when": "當", "insightAlert.config.for": "為", "insightAlert.config.condition": "條件", "insightAlert.config.threshold": "門檻", @@ -798,7 +812,7 @@ "insightAlert.config.invalidWidget": "錯誤:無法完全編輯警報 — 來源元件已被刪除。刪除警報或聯絡您的管理員。", "insightAlert.config.unusedWidget": "無法完全編輯警報 — 來源元件無法使用。", "insightAlert.config.missingThreshold": "錯誤:必填欄位 — 輸入臨界值以繼續。", - "insightAlert.config.selectMetric": "選擇計量", + "insightAlert.config.selectMetric": "選擇指標", "insightAlert.config.selectAttribute": "所有", "alert.accessibility.destination.label": "選擇發送警示的方式", "granularity.year": "年", @@ -809,17 +823,18 @@ "granularity.hour": "小時", "granularity.minute": "分鐘", "widget.options.menu.export": "匯出", + "insightMenu.restrictedWidget.options": "受限制的可視化選項", "menu.close": "關閉餐牌", "controlButtons.asTable": "顯示為表", "controlButtons.asOriginal": "顯示原始可視化", "controlButtons.announcement.switchedToTable": "可視化切換到表格視圖", "controlButtons.announcement.switchedToOriginal": "可視化畫面切換回原來的畫面", - "automationFilters.announcement.filterAdded": " {title}過濾器加咗。", - "automationFilters.announcement.filterRemoved": "去咗過濾器 {title} 。", - "automationFilters.announcement.filterChanged": "過濾器 {title} 換咗。", + "automationFilters.announcement.filterAdded": "已新增篩選器 {title}。", + "automationFilters.announcement.filterRemoved": "已移除篩選器 {title}。", + "automationFilters.announcement.filterChanged": "已變更篩選器 {title}。", "dashboard.crossFilter.success": "成功:{count, plural, one {# 過濾器} other {# 從圖表中應用的}}過濾器。", "kdaDialog.dialog.filters.title": "篩選器", - "kdaDialog.dialog.filters.noFilters": "冇濾網", + "kdaDialog.dialog.filters.noFilters": "沒有篩選器", "kdaDialog.dialog.filters.add.tooltip": "添加篩選器", "kdaDialog.dialog.filters.search": "搜尋", "kdaDialog.dialog.title": "主要驅動因素分析{metric}", @@ -837,7 +852,7 @@ "kdaDialog.replaceConfirmation.message": "已有關鍵驅動分析在進行或顯示。開始新分析會捨棄目前結果。", "kdaDialog.replaceConfirmation.stay": "留", "kdaDialog.replaceConfirmation.runAnyway": "仍然執行", - "kdaDialog.dialog.bars.metric.title": "計量", + "kdaDialog.dialog.bars.metric.title": "指標", "kdaDialog.dialog.bars.filters.title": "篩選器", "kdaDialog.dialog.bars.date.title": "日期範圍: {title}", "kdaDialog.dialog.bars.date.splitter": "對", @@ -865,7 +880,7 @@ "kdaDialog.dialog.bars.date.period.title": "期比", "kdaDialog.dialog.bars.date.period.samePeriodPreviousYear": "上年同期", "kdaDialog.dialog.bars.date.period.previousPeriod": "上一期", - "kdaDialog.dialog.keyDrives.empty": "冇關鍵驅動力", + "kdaDialog.dialog.keyDrives.empty": "沒有關鍵驅動因素", "kdaDialog.dialog.keyDriver.chart.std": "改變顯著性閾值", "kdaDialog.dialog.keyDrives.error.general.title": "抱歉,我們無法顯示關鍵驅動因素", "kdaDialog.dialog.keyDrives.error.general.description": "我們無法為此指標計算關鍵驅動因素。若問題持續,請聯絡系統管理員。", @@ -874,7 +889,7 @@ "kdaDialog.dialog.keyDrives.error.details.title": "原始錯誤響應", "kdaDialog.dialog.keyDrives.error.details.title.tooltip": "原始錯誤響應顯示有關已發生錯誤的後端信息。 它僅對管理員可見。", "drill.kda.from": "從", - "drill.kda.in": "係", + "drill.kda.in": "在", "drill.kda.drop": "下降 {where} {title}", "drill.kda.increase": "增加 {where} {title}", "drill.kda.no_change": "無變化 {where} {title}", @@ -882,7 +897,7 @@ "dashboard.tabs.default.label": "無題標籤", "dashboard.tabs.move.left": "左移", "dashboard.tabs.move.right": "移右", - "dashboard.tabs.delete.disabled-tooltip": "你唔可以刪除最後一個標籤。 當只剩下一個標籤頁時,標籤頁導航喺檢視模式下就唔會睇到。", + "dashboard.tabs.delete.disabled-tooltip": "您無法刪除最後一個分頁。 當只剩下一個分頁時,分頁導航在檢視模式下將不會顯示。", "dashboard.tabs.add-button-tooltip": "加入新標籤", "dashboard.tabs.rename": "改名", "dashboard.tabs.accessibility.label": "儀表板標籤", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hans.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hans.json index a96e78e2273..9772ed55cbe 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hans.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hans.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "您的试用已结束。", + "dashboard.contractExpired.trial.description": "如果想继续使用 GoodData Cloud,请联系我们。", + "dashboard.contractExpired.nonTrial.title": "您的访问权限已被锁定。", + "dashboard.contractExpired.nonTrial.description": "请联系账户管理员以重新获得访问权限。", + "dashboard.contractExpired.buttonTitle": "联系我们", "loading": "正在加载…", "untitled": "无标题", "empty_value": "空值", + "deleted_value": "已删除的值", "gs.list.cancel": "取消", "gs.list.apply": "应用", "gs.list.only": "仅", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "交互已配置。", "messages.drill.InteractionUpdatedSuccess": "交互已更新。", "filterBar.label": "筛选器", + "filterBar.restrictedFilters.remove": "移除受限筛选器", + "filterBar.restrictedFilters.reason": "{count, plural, other {您无权访问 # 个筛选器。}}", + "filterBar.restrictedFilters": "{count, plural, other {# 个筛选器未应用}}", "filterBar.showAll": "显示全部", "filterBar.showLess": "显示更少", "filterBar.filterListAutoExpanded": "筛选器列表已自动展开", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "计划的导出", "deleteWidgetDialog.headline": "移除小部件?", "deleteWidgetDialog.objectsMessage": "以下与小部件 {title} 关联的类型对象将无法使用:", + "deleteWidgetDialog.objectsMessage.restricted": "以下与该组件关联的对象类型将无法正常工作:", "deleteWidgetDialog.alerts": "警报", "deleteWidgetDialog.schedules": "计划的导出", "create.dashboard": "创建控制面板", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} 无法被 {parentTitle} 筛选。数据模型中的筛选器属性之间没有关联。", "attributesDropdown.details.dataset": "数据集", "attributesDropdown.details.values": "数值", + "attributesDropdown.details.definedAs": "定义为", "attributesDropdown.details.shortening_decoration": "…和另外 {count} 个", "layout.header.characters.left": "还剩 {currentCharactersCount} / {maximumCharactersCount} 个字符", "layout.header.add.title.placeholder": "在此处添加标题...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "可视化无法按 {attributeName} 筛选。请取消选中该复选框。", "configurationPanel.kpiCantBeFilteredByAttribute": "KPI 无法按 {attributeName} 筛选。请取消选中该复选框。", "configurationPanel.vizCantBeFilteredByDate": "可视化无法按日期筛选。请取消选中该复选框。", + "configurationPanel.dateDatasetsLoadFailed": "无法加载相关日期数据。请关闭配置面板,然后重试。", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "筛选器不适用于此可视化。", "configurationPanel.mvfBlockedByRankingFilter": "无法应用此筛选器,因为该可视化使用了 Top/bottom values 筛选器。请移除 Top/bottom values 筛选器以应用此筛选器。", "configurationPanel.mvfMissingMeasure": "此筛选器中使用的指标在工作区中已不存在。", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "深入可视化", "configurationPanel.drillConfig.drillDown": "深入查看", "configurationPanel.drillConfig.disableDrillDownTooltip": "在此可视化的配置中,向下钻取处于关闭状态。", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "计算属性不支持向下钻取。", "configurationPanel.drillConfig.drillIntoUrl": "深入查看 URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "URL 参数无效", "configurationPanel.drillConfig.select": "选择操作…", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "删除富文本?", "richText.deleteDialog.message": "富文本及其所有内容将被不可逆地删除。", "richText.emptyContent": "无富文本内容", + "visualizationSwitcher.restrictedEntry": "受限", "visualizationSwitcher.emptyContent": "切换器中无可视化", "visualizationSwitcherToolbar.visualizationsList.header": "可视化", "visualizationSwitcherToolbar.visualizationsList.tooltip": "可视化", @@ -809,6 +823,7 @@ "granularity.hour": "hour(小时)", "granularity.minute": "minute(分钟)", "widget.options.menu.export": "导出", + "insightMenu.restrictedWidget.options": "受限的可视化选项", "menu.close": "关闭菜单", "controlButtons.asTable": "以表格形式显示", "controlButtons.asOriginal": "显示原始可视化", diff --git a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hant.json b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hant.json index 44b46bbb00b..ffddb38db5b 100644 --- a/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hant.json +++ b/libs/sdk-ui-dashboard/src/presentation/localization/bundles/zh-Hant.json @@ -1,7 +1,13 @@ { + "dashboard.contractExpired.trial.title": "您的試用期已結束。", + "dashboard.contractExpired.trial.description": "如果您想繼續使用 GoodData Cloud,請聯絡我們。", + "dashboard.contractExpired.nonTrial.title": "您的存取權限已被鎖定。", + "dashboard.contractExpired.nonTrial.description": "請聯絡帳戶管理員以重新獲得存取權限。", + "dashboard.contractExpired.buttonTitle": "聯絡我們", "loading": "載入中…", "untitled": "無標題", "empty_value": "空值", + "deleted_value": "刪除的值", "gs.list.cancel": "取消", "gs.list.apply": "申請", "gs.list.only": "僅有的", @@ -23,6 +29,9 @@ "messages.drill.InteractionConfiguredSuccess": "交互已配置。", "messages.drill.InteractionUpdatedSuccess": "互動已更新。", "filterBar.label": "過濾器", + "filterBar.restrictedFilters.remove": "移除受限制的篩選器", + "filterBar.restrictedFilters.reason": "{count, plural, other {您無權存取 # 個篩選器。}}", + "filterBar.restrictedFilters": "{count, plural, other {未套用 # 個篩選器}}", "filterBar.showAll": "顯示所有", "filterBar.showLess": "顯示較少", "filterBar.filterListAutoExpanded": "篩選器清單已自動展開", @@ -137,6 +146,7 @@ "deleteDashboardDialog.schedules": "預定匯出", "deleteWidgetDialog.headline": "刪除小部件?", "deleteWidgetDialog.objectsMessage": "與小部件關聯的以下類型物件{title} 將不再起作用:", + "deleteWidgetDialog.objectsMessage.restricted": "與小工具相關聯的下列物件類型將不再運作:", "deleteWidgetDialog.alerts": "警報", "deleteWidgetDialog.schedules": "預定匯出", "create.dashboard": "建立儀表板", @@ -416,6 +426,7 @@ "attributesDropdown.noConnectionMessage": "{childTitle} 無法過濾 {parentTitle}。資料模型中的過濾器屬性之間沒有任何關聯。", "attributesDropdown.details.dataset": "數據集", "attributesDropdown.details.values": "價值觀", + "attributesDropdown.details.definedAs": "定義為", "attributesDropdown.details.shortening_decoration": "…和{count} 更多的", "layout.header.characters.left": "{currentCharactersCount} /{maximumCharactersCount} 剩餘字符", "layout.header.add.title.placeholder": "在此新增標題...", @@ -483,6 +494,7 @@ "configurationPanel.vizCantBeFilteredByAttribute": "可視化不能依以下條件過濾{attributeName}。取消選取該複選框。", "configurationPanel.kpiCantBeFilteredByAttribute": "kpi 無法過濾 {attributeName}。取消選取該複選框。", "configurationPanel.vizCantBeFilteredByDate": "可視化無法按日期過濾。取消選取該複選框。", + "configurationPanel.dateDatasetsLoadFailed": "無法載入相關日期資料。請關閉設定面板,然後再試一次。", "configurationPanel.vizCantBeFilteredByMeasureValueFilter": "篩選不適用於此視覺化。", "configurationPanel.mvfBlockedByRankingFilter": "無法套用此篩選,因為該視覺化使用了前/後值篩選。請移除前/後值篩選以套用此篩選。", "configurationPanel.mvfMissingMeasure": "此篩選器中使用的指標已不存在於工作區中。", @@ -502,6 +514,7 @@ "configurationPanel.drillConfig.drillIntoInsight": "鑽入", "configurationPanel.drillConfig.drillDown": "向下鑽取", "configurationPanel.drillConfig.disableDrillDownTooltip": "在此視覺化配置中,向下鑽取已關閉。", + "configurationPanel.drillConfig.computedAttributeDrillDownTooltip": "計算屬性無法使用向下鑽取。", "configurationPanel.drillConfig.drillIntoUrl": "鑽取 URL", "configurationPanel.drillConfig.drillIntoUrl.invalidCustomUrl": "無效的 URL 參數", "configurationPanel.drillConfig.select": "選擇行動...", @@ -629,6 +642,7 @@ "richText.deleteDialog.header": "刪除富文本?", "richText.deleteDialog.message": "富文本及其所有內容將不可逆轉地刪除。", "richText.emptyContent": "沒有富文本內容", + "visualizationSwitcher.restrictedEntry": "受限制", "visualizationSwitcher.emptyContent": "切換器中沒有可視化", "visualizationSwitcherToolbar.visualizationsList.header": "視覺化", "visualizationSwitcherToolbar.visualizationsList.tooltip": "視覺化", @@ -809,6 +823,7 @@ "granularity.hour": "小時", "granularity.minute": "分分鐘", "widget.options.menu.export": "匯出", + "insightMenu.restrictedWidget.options": "受限制的視覺化選項", "menu.close": "關閉選單", "controlButtons.asTable": "以表格形式顯示", "controlButtons.asOriginal": "顯示原始可視化", diff --git a/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/settingButton/DefaultSettingButton.tsx b/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/settingButton/DefaultSettingButton.tsx index 8b3450c9044..ea53e7e42fd 100644 --- a/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/settingButton/DefaultSettingButton.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/topBar/buttonBar/button/settingButton/DefaultSettingButton.tsx @@ -67,7 +67,6 @@ export function DefaultSettingButton({ } - width={300} anchor={ { + // a computed attribute's display form and a label that happen to share an identifier + const computedAttributeDf = { id: "shared_id", ref: idRef("shared_id", "computedAttribute") } as never; + const labelDf = { id: "shared_id", ref: idRef("shared_id", "displayForm") } as never; + + function sanitize(df: never): IAttributeFilter { + // mirrors useSanitizeAttributeFilter: the ref is rewritten to the canonical placeholder ref + return newPositiveAttributeFilter(displayFormPlaceholderRef(df), ["a"]); + } + + function placeholderRefIn(url: string) { + return getDashboardAttributeFilterPlaceholdersFromUrl(url)[0].ref; + } + + it("inserts a placeholder that matches the sanitized filter", () => { + const inserted = dashboardAttributeFilterToPlaceholder( + displayFormPlaceholderRef(computedAttributeDf), + ); + + expect(inserted).toBe("{dash_attribute_filter_selection(computed_attribute/shared_id)}"); + + const filter = sanitize(computedAttributeDf); + // this is the comparison useInvalidFilteringParametersIdentifiers makes; a mismatch here would + // report a perfectly good placeholder as an invalid parameter + expect( + areObjRefsEqual(displayFormPlaceholderRef(computedAttributeDf), placeholderRefIn(inserted)), + ).toBe(true); + expect(areObjRefsEqual(filterObjRef(filter), placeholderRefIn(inserted))).toBe(true); + }); + + it("does not match a label sharing the identifier", () => { + const inserted = dashboardAttributeFilterToPlaceholder( + displayFormPlaceholderRef(computedAttributeDf), + ); + const labelFilter = sanitize(labelDf); + + expect(areObjRefsEqual(filterObjRef(labelFilter), placeholderRefIn(inserted))).toBe(false); + }); + + it("keeps a display form placeholder unprefixed and matching its own filter", () => { + const inserted = dashboardAttributeFilterToPlaceholder(displayFormPlaceholderRef(labelDf)); + + expect(inserted).toBe("{dash_attribute_filter_selection(shared_id)}"); + + const filter = sanitize(labelDf); + expect(areObjRefsEqual(filterObjRef(filter), placeholderRefIn(inserted))).toBe(true); + }); + + it("names an invalid parameter with the text that is actually in the URL", () => { + // the warning quotes these back to the user, so a stripped identifier would name text the + // user cannot find anywhere in the URL they wrote + const url = dashboardAttributeFilterToPlaceholder(displayFormPlaceholderRef(computedAttributeDf)); + const { ref } = getDashboardAttributeFilterPlaceholdersFromUrl(url)[0]; + + expect(placeholderIdentifierText(ref)).toBe("computed_attribute/shared_id"); + expect(url).toContain(placeholderIdentifierText(ref)); + + expect(placeholderIdentifierText(displayFormPlaceholderRef(labelDf))).toBe("shared_id"); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidAttributeDisplayFormIdentifier.ts b/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidAttributeDisplayFormIdentifier.ts index 6b023ff2715..6aa1c675418 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidAttributeDisplayFormIdentifier.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidAttributeDisplayFormIdentifier.ts @@ -2,8 +2,11 @@ import { useMemo } from "react"; -import { type IAttributeDescriptor, areObjRefsEqual, idRef } from "@gooddata/sdk-model"; -import { getAttributeIdentifiersPlaceholdersFromUrl } from "@gooddata/sdk-model/internal"; +import { type IAttributeDescriptor, areObjRefsEqual } from "@gooddata/sdk-model"; +import { + getAttributeIdentifiersPlaceholdersFromUrl, + placeholderIdentifierText, +} from "@gooddata/sdk-model/internal"; import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js"; import { selectAllCatalogDisplayFormsMap } from "../../../../../model/store/catalog/catalogSelectors.js"; @@ -18,19 +21,22 @@ export function useInvalidAttributeDisplayFormIdentifiers( return useMemo(() => { if (isDrillToCustomUrlConfig(urlDrillTarget)) { const parameters = getAttributeIdentifiersPlaceholdersFromUrl(urlDrillTarget.customUrl); - return parameters - .filter(({ identifier }) => { - // parameter is invalid if either it points to display form that no longer exists - const relevantDf = displayForms.get(idRef(identifier, "displayForm")); - if (!relevantDf) { - return false; - } - // or if it points to an attribute that is no longer a valid drill target - return !attributes.some((attribute) => - areObjRefsEqual(relevantDf.attribute, attribute.attributeHeader.formOf), - ); - }) - .map(({ identifier }) => identifier); + return ( + parameters + .filter(({ ref }) => { + // parameter is invalid if either it points to display form that no longer exists + const relevantDf = displayForms.get(ref); + if (!relevantDf) { + return false; + } + // or if it points to an attribute that is no longer a valid drill target + return !attributes.some((attribute) => + areObjRefsEqual(relevantDf.attribute, attribute.attributeHeader.formOf), + ); + }) + // named as it is written in the URL, so the warning quotes text the user can find there + .map(({ ref }) => placeholderIdentifierText(ref)) + ); } return []; }, [displayForms, urlDrillTarget, attributes]); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidFilteringParametersIdentifiers.ts b/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidFilteringParametersIdentifiers.ts index 52cf3bda300..99cf7d72877 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidFilteringParametersIdentifiers.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insight/configuration/DrillTargets/useInvalidFilteringParametersIdentifiers.ts @@ -11,13 +11,13 @@ import { type IMeasureValueFilter, areObjRefsEqual, filterObjRef, - idRef, } from "@gooddata/sdk-model"; import { getDashboardAttributeFilterPlaceholdersFromUrl, getDashboardMeasureValueFilterPlaceholdersFromUrl, getInsightAttributeFilterPlaceholdersFromUrl, getInsightMeasureValueFilterPlaceholdersFromUrl, + placeholderIdentifierText, } from "@gooddata/sdk-model/internal"; import { useDashboardSelector } from "../../../../../model/react/DashboardStoreProvider.js"; @@ -59,40 +59,42 @@ export function useInvalidFilteringParametersIdentifiers( ); const invalidDashboardParameters = dashboardAttributeFilterParameters - .filter(({ identifier }) => { + // The placeholder's own ref is used rather than a display-form one rebuilt from the + // identifier: a computed attribute shares the shape of a display form but not its type, + // and the filters are sanitized to that same typed ref. + .filter(({ ref }) => { // parameter is invalid if either it points to display form that no longer exists - const relevantDf = displayForms.get(idRef(identifier, "displayForm")); + const relevantDf = displayForms.get(ref); if (!relevantDf) { return true; } return ( !dashboardFilters?.some((filter) => { - return areObjRefsEqual(filterObjRef(filter), idRef(identifier, "displayForm")); + return areObjRefsEqual(filterObjRef(filter), ref); }) && !attributeFilterConfigs?.some((config) => { - return ( - config.displayAsLabel && - areObjRefsEqual(config.displayAsLabel, idRef(identifier, "displayForm")) - ); + return config.displayAsLabel && areObjRefsEqual(config.displayAsLabel, ref); }) ); }) - .map(({ identifier }) => identifier); + // named as it is written in the URL, so the warning quotes text the user can find there + .map(({ ref }) => placeholderIdentifierText(ref)); const invalidInsightParameters = insightAttributeFilterParameters - .filter(({ identifier }) => { + .filter(({ ref }) => { // parameter is invalid if either it points to display form that no longer exists - const relevantDf = displayForms.get(idRef(identifier, "displayForm")); + const relevantDf = displayForms.get(ref); if (!relevantDf) { return true; } return !insightFilters?.some((filter) => { - return areObjRefsEqual(filterObjRef(filter), idRef(identifier, "displayForm")); + return areObjRefsEqual(filterObjRef(filter), ref); }); }) - .map(({ identifier }) => identifier); + // named as it is written in the URL, so the warning quotes text the user can find there + .map(({ ref }) => placeholderIdentifierText(ref)); const invalidDashboardMvfParameters = dashboardMeasureValueFilterParameters .filter(({ identifier }) => { diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/CustomUiMenuComponents.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/CustomUiMenuComponents.tsx index ed503d225f7..f0e93d80eba 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/CustomUiMenuComponents.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/CustomUiMenuComponents.tsx @@ -125,6 +125,20 @@ export function CustomUiMenuContentComponent({ item }: IUiMenuContentProps(); + const selector = createSelector((ctx) => ({ + parentItem: ctx.focusedItem ? getItemInteractiveParent(ctx.items, ctx.focusedItem.id) : undefined, + })); + const { parentItem } = useContextStore(selector); + + return parentItem ? : null; +} + export function CustomUiMenuHeaderComponent() { const { formatMessage } = useIntl(); const { useContextStore, createSelector } = typedUiMenuContextStore(); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/DashboardInsightMenuContainer.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/DashboardInsightMenuContainer.tsx index 89daa56a6bf..20ad7f33d16 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/DashboardInsightMenuContainer.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/DashboardInsightMenuContainer.tsx @@ -7,13 +7,21 @@ import { ItemsWrapper } from "@gooddata/sdk-ui-kit"; interface IDashboardInsightMenuContainerProps { children: ReactNode; ariaLabelledBy?: string; + + /** Name for a menu that has no title to point at, which is how a restricted widget's menu is named. */ + ariaLabel?: string; } const itemsWrapperStyle: CSSProperties = { width: "100%" }; export function DashboardInsightMenuContainer(props: IDashboardInsightMenuContainerProps) { return ( -
+
{props.children} diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/index.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/index.tsx index 970e55ef22a..34ee7e5e34a 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/index.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/index.tsx @@ -10,6 +10,8 @@ import { useState, } from "react"; +import { useIntl } from "react-intl"; + import { type IInsightWidget, objRefToString, widgetRef } from "@gooddata/sdk-model"; import { type IUiMenuContext, @@ -33,6 +35,7 @@ import { CustomUiMenuContentComponent, CustomUiMenuContentItemComponent, CustomUiMenuHeaderComponent, + CustomUiMenuHeaderComponentWithoutTitle, CustomUiMenuInteractiveItemComponent, type IMenuData, type IMenuItemData, @@ -159,10 +162,17 @@ export function DashboardInsightMenuBody({ onClose, renderMode, setSubmenu, + showTitle = true, }: IDashboardInsightMenuProps & { setSubmenu?: Dispatch>; renderMode: RenderMode; + /** + * Whether the menu names the widget it belongs to. Off for a widget the user is not allowed to + * see, whose stored title can name the object they have no access to. + */ + showTitle?: boolean; }) { + const intl = useIntl(); const widgetRefAsString = objRefToString(widgetRef(widget)); const latestWidgetRef = useRef(widget); latestWidgetRef.current = widget; @@ -202,6 +212,10 @@ export function DashboardInsightMenuBody({ const menuId = `insight-menu-${widgetRefAsString}`; const menuLabelId = `${menuId}-label`; + // without the title there is no element carrying menuLabelId, so the name goes on the menu itself + const menuLabel = showTitle + ? undefined + : intl.formatMessage({ id: "insightMenu.restrictedWidget.options" }); const getMaxHeight = (context: IUiMenuContext) => { if (getSelectedMenuId(context) === "Alerts") { @@ -212,7 +226,10 @@ export function DashboardInsightMenuBody({ return ( - + maxHeight={getMaxHeight} containerBottomPadding="small" @@ -221,12 +238,15 @@ export function DashboardInsightMenuBody({ InteractiveItem={CustomUiMenuInteractiveItemComponent} ContentItem={CustomUiMenuContentItemComponent} Content={CustomUiMenuContentComponent} - MenuHeader={CustomUiMenuHeaderComponent} + MenuHeader={ + showTitle ? CustomUiMenuHeaderComponent : CustomUiMenuHeaderComponentWithoutTitle + } shouldCloseOnSelect - ariaAttributes={{ - id: menuId, - "aria-labelledby": menuLabelId, - }} + ariaAttributes={ + menuLabel + ? { id: menuId, "aria-label": menuLabel } + : { id: menuId, "aria-labelledby": menuLabelId } + } onSelect={handleSelect} onLevelChange={handleSubmenuOpen} menuCtxData={{ diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/getDefaultInsightEditMenuItems.test.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/getDefaultInsightEditMenuItems.test.tsx new file mode 100644 index 00000000000..d785b058164 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/insightMenu/DefaultDashboardInsightMenu/getDefaultInsightEditMenuItems.test.tsx @@ -0,0 +1,49 @@ +// (C) 2026 GoodData Corporation + +import { describe, expect, it, vi } from "vitest"; + +import { type IInsightWidget, idRef } from "@gooddata/sdk-model"; + +import { getDefaultInsightEditMenuItems } from "./getDefaultInsightEditMenuItems.js"; + +const widget: IInsightWidget = { + type: "insight", + insight: idRef("insight-1", "insight"), + ref: idRef("widget-1"), + uri: "/widget-1", + identifier: "widget-1", + title: "Confidential revenue", + description: "", + drills: [], + ignoreDashboardFilters: [], +}; + +const dependencies = { + intl: { formatMessage: ({ id }: { id: string }) => id } as never, + dispatch: vi.fn(), + eventDispatch: vi.fn(), +}; + +describe("getDefaultInsightEditMenuItems", () => { + it("offers removal alone for a widget whose insight the editor cannot read", () => { + const items = getDefaultInsightEditMenuItems(widget, { + ...dependencies, + includeConfigurations: false, + includeInteractions: false, + }); + + // a restricted widget gets one entry, and no separator that would imply a missing one + expect(items.map((item) => item.itemId)).toEqual(["InteractionPanelRemove"]); + }); + + it("keeps configuration and interactions for a readable one", () => { + const items = getDefaultInsightEditMenuItems(widget, dependencies); + + expect(items.map((item) => item.itemId)).toEqual([ + "ConfigurationPanelSubmenu", + "InteractionPanelSubmenu", + "InteractionPanelRemoveSeparator", + "InteractionPanelRemove", + ]); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.test.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.test.tsx index b12503689af..e8fac595e94 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.test.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.test.tsx @@ -125,6 +125,30 @@ describe("DashboardWidgetInsightGuard", () => { vi.clearAllMocks(); }); + it("hands a restricted widget to the rendering the mode asks for, if any", () => { + function RestrictedStandIn() { + return
the edit mode stand-in
; + } + mockUseDashboardSelector.mockImplementation((selector: (state: DashboardState) => unknown) => + selector(stateWith({ ref: insightRef, type: "insight", reason: "forbidden" })), + ); + + render( + + + , + ); + + expect(screen.getByText("the edit mode stand-in")).toBeInTheDocument(); + expect(screen.queryByText("the default placeholder")).not.toBeInTheDocument(); + }); + it("renders the widget when its insight is available", () => { renderGuard(stateWith()); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.tsx index f29060935c6..fcd739972a1 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/DashboardWidgetInsightGuard.tsx @@ -12,21 +12,30 @@ import { type IDefaultDashboardInsightWidgetProps } from "./types.js"; interface IDashboardWidgetInsightGuardProps extends Omit { Component: ComponentType; + + /** + * What stands in for a widget the user is not allowed to see. Defaults to the placeholder tile, + * which is all a viewer can be offered; edit mode passes a rendering that keeps the selection and + * the remove entry. + */ + RestrictedComponent?: ComponentType>; } // Sometimes this component is rendered even before insights are ready, which blows up. // Since the behavior is nearly impossible to replicate reliably, let's be defensive here and not render // anything until the insights "catch up". export function DashboardWidgetInsightGuard(props: IDashboardWidgetInsightGuardProps) { - const { widget, screen, dashboardItemClasses, exportData, Component } = props; + const { widget, screen, dashboardItemClasses, exportData, Component, RestrictedComponent } = props; const insights = useDashboardSelector(selectInsightsMap); const isRestricted = useIsWidgetRestricted(widget); const { RestrictedPlaceholderComponentProvider } = useDashboardComponentsContext(); - // view, edit and export modes all reach the widget through this guard, so short-circuiting here is - // what strips the config panel, menu and selection from a restricted widget in edit mode + // view, edit and export modes all reach the widget through this guard, so this is the single + // short-circuit that keeps a restricted insight from reaching the widget in any of them if (isRestricted) { - return ( + return RestrictedComponent ? ( + + ) : ( ) { - return ; + return ( + + ); } /** diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.test.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.test.tsx new file mode 100644 index 00000000000..c64fc32d87a --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.test.tsx @@ -0,0 +1,154 @@ +// (C) 2026 GoodData Corporation + +import { fireEvent, render, screen } from "@testing-library/react"; +import { IntlProvider } from "react-intl"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { type IInsightWidget, idRef } from "@gooddata/sdk-model"; + +import { type IInsightMenuItem } from "../../insightMenu/types.js"; + +const mockSelection = vi.fn(); +const mockMenuItems = vi.fn(); +const mockPlaceholderProvider = vi.fn(); + +// `isolate: false` shares one module graph per worker, so the modules mocked below may already have +// been evaluated against their real dependencies by a test file that ran earlier in the same worker. +vi.hoisted(() => { + vi.resetModules(); +}); + +vi.mock("../../../../model/react/useWidgetSelection.js", () => ({ + useWidgetSelection: () => mockSelection(), +})); + +vi.mock("./useEditableInsightMenu.js", () => ({ + useEditableInsightMenu: (config: unknown) => mockMenuItems(config), +})); + +vi.mock("../../../dashboardContexts/DashboardComponentsContext.js", () => ({ + useDashboardComponentsContext: () => ({ + RestrictedPlaceholderComponentProvider: mockPlaceholderProvider, + }), +})); + +const { EditableRestrictedInsightWidget } = await import("./EditableRestrictedInsightWidget.js"); + +const widget: IInsightWidget = { + type: "insight", + insight: idRef("insight-1", "insight"), + ref: idRef("widget-1"), + uri: "/widget-1", + identifier: "widget-1", + title: "Confidential revenue", + description: "", + drills: [], + ignoreDashboardFilters: [], +}; + +const onRemove = vi.fn(); +const removeItem: IInsightMenuItem = { + type: "button", + itemId: "InteractionPanelRemove", + itemName: "Remove from dashboard", + tooltip: "", + icon: "gd-icon-trash", + disabled: false, + onClick: onRemove, +}; + +function PlaceholderStandIn() { + return
no access to this visualization
; +} + +const closeConfigPanel = vi.fn(); +const onSelected = vi.fn(); + +function renderWidget({ hasConfigPanelOpen = false }: { hasConfigPanelOpen?: boolean } = {}) { + mockSelection.mockReturnValue({ + isSelectable: true, + isSelected: hasConfigPanelOpen, + onSelected, + closeConfigPanel, + hasConfigPanelOpen, + }); + + return render( + + + , + ); +} + +describe("EditableRestrictedInsightWidget", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockPlaceholderProvider.mockImplementation(() => PlaceholderStandIn); + mockMenuItems.mockReturnValue({ menuItems: [removeItem] }); + }); + + it("shows the placeholder and never the stored title", () => { + renderWidget(); + + expect(screen.getByText("no access to this visualization")).toBeInTheDocument(); + expect(screen.queryByText("Confidential revenue")).not.toBeInTheDocument(); + }); + + it("asks for the menu of a widget with no readable insight, which is removal alone", () => { + renderWidget(); + + expect(mockMenuItems).toHaveBeenCalledWith(expect.objectContaining({ widget, insight: undefined })); + }); + + it("removes the widget when the entry is chosen", () => { + renderWidget({ hasConfigPanelOpen: true }); + + fireEvent.click(screen.getByText("Remove from dashboard")); + + expect(onRemove).toHaveBeenCalledTimes(1); + }); + + it("puts the entry in a menu that can be operated with the keyboard", () => { + renderWidget({ hasConfigPanelOpen: true }); + + // `hidden: true` because the overlay parks itself off-screen with visibility:hidden until it + // has measured its anchor, which never happens without layout + const menu = screen.getByRole("menu", { hidden: true }); + expect(screen.getByRole("menuitem", { hidden: true })).toHaveTextContent("Remove from dashboard"); + + // the menu holds the focus and points at the item through aria-activedescendant, so the key + // goes to the menu itself + fireEvent.keyDown(menu, { code: "Enter" }); + + expect(onRemove).toHaveBeenCalledTimes(1); + }); + + it("names the menu without naming the widget", () => { + renderWidget({ hasConfigPanelOpen: true }); + + // the titleless header renders no element to point at, so the name has to be on the menu + const menu = screen.getByRole("menu", { hidden: true }); + expect(menu).toHaveAttribute("aria-label", "Restricted visualization options"); + expect(menu).not.toHaveAttribute("aria-labelledby"); + expect(screen.getByRole("dialog", { hidden: true })).toHaveAttribute( + "aria-label", + "Restricted visualization options", + ); + }); + + it("offers no menu until the widget is selected", () => { + renderWidget(); + + expect(screen.queryByText("Remove from dashboard")).not.toBeInTheDocument(); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.tsx b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.tsx new file mode 100644 index 00000000000..85c1ea8ced2 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widget/widget/InsightWidget/EditableRestrictedInsightWidget.tsx @@ -0,0 +1,81 @@ +// (C) 2026 GoodData Corporation + +import cx from "classnames"; + +import { widgetRef } from "@gooddata/sdk-model"; + +import { useWidgetSelection } from "../../../../model/react/useWidgetSelection.js"; +import { useDashboardComponentsContext } from "../../../dashboardContexts/DashboardComponentsContext.js"; +import { DashboardItem } from "../../../presentationComponents/DashboardItems/DashboardItem.js"; +import { DashboardItemVisualization } from "../../../presentationComponents/DashboardItems/DashboardItemVisualization.js"; +import { DashboardInsightEditMenuBubble } from "../../insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/DashboardInsightEditMenuBubble.js"; +import { DashboardInsightMenuBody } from "../../insightMenu/DefaultDashboardInsightMenu/DashboardInsightMenu/index.js"; + +import { type IDefaultDashboardInsightWidgetProps } from "./types.js"; +import { useEditableInsightMenu } from "./useEditableInsightMenu.js"; + +/** + * Stands in for an insight widget the editor is not allowed to see. It keeps the selection and the + * context menu of an ordinary widget, because the editor may still want the widget gone; the menu is + * built by the standard builder for a widget with no readable insight, which leaves removal alone. + * + * It renders the standard menu body — keyboard handling, focus management and item rendering come + * with it — with its title suppressed, because the widget title can name the object the editor may + * not see. It does not go through `InsightMenuComponentProvider`: a replacement menu would have no + * reason to honour that. + */ +export function EditableRestrictedInsightWidget({ + widget, + screen, + dashboardItemClasses, +}: Omit) { + const { isSelectable, isSelected, onSelected, closeConfigPanel, hasConfigPanelOpen } = useWidgetSelection( + widgetRef(widget), + ); + const { RestrictedPlaceholderComponentProvider } = useDashboardComponentsContext(); + const Content = RestrictedPlaceholderComponentProvider(widget); + const { menuItems } = useEditableInsightMenu({ + closeMenu: closeConfigPanel, + insight: undefined, + widget, + }); + + return ( + + + hasConfigPanelOpen ? ( + + + + ) : null + } + > + {({ clientHeight, clientWidth }) => ( +
+ +
+ )} +
+
+ ); +} diff --git a/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.test.tsx b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.test.tsx new file mode 100644 index 00000000000..2592fa7b826 --- /dev/null +++ b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.test.tsx @@ -0,0 +1,64 @@ +// (C) 2026 GoodData Corporation + +import { render, screen } from "@testing-library/react"; +import { IntlProvider } from "react-intl"; +import { describe, expect, it, vi } from "vitest"; + +import { type IInsightWidget, idRef } from "@gooddata/sdk-model"; + +import { DefaultWidgetDeleteDialog } from "./DefaultWidgetDeleteDialog.js"; + +const widget: IInsightWidget = { + type: "insight", + insight: idRef("insight-1", "insight"), + ref: idRef("widget-1"), + uri: "/widget-1", + identifier: "widget-1", + title: "Confidential revenue", + description: "", + drills: [], + ignoreDashboardFilters: [], +}; + +const messages = { + "deleteWidgetDialog.headline": "Delete widget?", + "deleteWidgetDialog.objectsMessage": "Objects associated with the widget {title} will stop working:", + "deleteWidgetDialog.objectsMessage.restricted": "Objects associated with this widget will stop working:", + "deleteWidgetDialog.alerts": "Alerts", + "deleteWidgetDialog.schedules": "Schedules", + cancel: "Cancel", + delete: "Delete", +}; + +function renderDialog({ isRestricted = false }: { isRestricted?: boolean } = {}) { + return render( + + + , + ); +} + +describe("DefaultWidgetDeleteDialog", () => { + it("never names a widget the user is not allowed to see", () => { + renderDialog({ isRestricted: true }); + + expect(screen.queryByText(/Confidential revenue/)).not.toBeInTheDocument(); + expect( + screen.getByText("Objects associated with this widget will stop working:"), + ).toBeInTheDocument(); + }); + + it("names an ordinary widget, which is what makes the warning useful", () => { + renderDialog(); + + expect(screen.getByText(/Confidential revenue/)).toBeInTheDocument(); + }); +}); diff --git a/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.tsx b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.tsx index 92ee1dda3c3..36b991595cc 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.tsx +++ b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/DefaultWidgetDeleteDialog.tsx @@ -9,6 +9,7 @@ import { ConfirmDialog } from "@gooddata/sdk-ui-kit"; import { eagerRemoveSectionItemByWidgetRef } from "../../model/commands/layout.js"; import { useDashboardDispatch, useDashboardSelector } from "../../model/react/DashboardStoreProvider.js"; +import { useIsWidgetRestricted } from "../../model/react/useIsWidgetRestricted.js"; import { dispatchAndWaitFor } from "../../model/store/_infra/dispatchAndWaitFor.js"; import { selectDashboardUserAutomationAlertsInContext, @@ -30,6 +31,9 @@ const deleteMessages = defineMessages({ objects: { id: "deleteWidgetDialog.objectsMessage", }, + objectsRestricted: { + id: "deleteWidgetDialog.objectsMessage.restricted", + }, alerts: { id: "deleteWidgetDialog.alerts", }, @@ -62,6 +66,7 @@ export function useWidgetDeleteDialogProps(): IWidgetDeleteDialogProps { }, [dispatch, widget]); const isVisible = useDashboardSelector(selectIsWidgetDeleteDialogOpen); + const isRestricted = useIsWidgetRestricted(widget!); const alerts = useDashboardSelector( selectDashboardUserAutomationAlertsInContext(widget?.localIdentifier), ); @@ -76,6 +81,7 @@ export function useWidgetDeleteDialogProps(): IWidgetDeleteDialogProps { onCancel, onDelete, widget, + isRestricted, }; } @@ -89,6 +95,7 @@ export function DefaultWidgetDeleteDialog({ onDelete, onCancel, widget, + isRestricted = false, }: IWidgetDeleteDialogProps): ReactElement | null { const intl = useIntl(); @@ -113,7 +120,11 @@ export function DefaultWidgetDeleteDialog({ submitButtonText={intl.formatMessage(deleteMessages.delete)} >
- + {isRestricted ? ( + + ) : ( + + )}
    {messages.map((message) => (
  • diff --git a/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/types.ts b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/types.ts index abbb3f83ce2..6cb705fdfd4 100644 --- a/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/types.ts +++ b/libs/sdk-ui-dashboard/src/presentation/widgetDeleteDialog/types.ts @@ -14,6 +14,12 @@ export interface IWidgetDeleteDialogProps { onDelete: () => void; onCancel: () => void; widget: IWidget | undefined; + + /** + * Whether the widget renders something the current user is not allowed to see. Such a widget must + * not be named: its stored title can be the name of the object they have no access to. + */ + isRestricted?: boolean; } /** diff --git a/libs/sdk-ui-dashboard/styles/scss/filterBar.scss b/libs/sdk-ui-dashboard/styles/scss/filterBar.scss index d28a680cf93..fe9473db85b 100644 --- a/libs/sdk-ui-dashboard/styles/scss/filterBar.scss +++ b/libs/sdk-ui-dashboard/styles/scss/filterBar.scss @@ -152,7 +152,6 @@ $attribute-filter-drag-handle-left: 10px; .dash-filters-all { $default_padding: 10px; $default_space: 3px; - $filter_item_height: 41px; // `.attribute-filter-button` in sdk-ui-filters, which exports no variable position: relative; width: calc(100% - 150px); // minus filters configuration buttons panel width @@ -221,8 +220,7 @@ $attribute-filter-drag-handle-left: 10px; } .dash-filters-restricted { - display: inline-block; - vertical-align: top; + display: flex; margin: 7px 11px 7px $default_space; } @@ -231,25 +229,36 @@ $attribute-filter-drag-handle-left: 10px; display: inline-flex; align-items: center; gap: 5px; - min-height: $filter_item_height; - padding: 4px 10px; + padding: 6px 10px; + border: none; border-radius: var(--gd-message-warning-borderRadius, 3px); background: var( --gd-message-warning-backgroundColor, var(--gd-palette-warning-base-t85, rgba(241, 134, 0, 0.15)) ); color: var(--gd-message-warning-textColor, kit-variables.$gd-palette-warning-base); + font-family: inherit; font-size: 12px; line-height: 16px; white-space: nowrap; cursor: default; + &--actionable { + cursor: pointer; + } + .gd-icon-lock { font-size: 14px; } } } +.dash-filters-restricted-tooltip { + display: flex; + flex-direction: column; + gap: 5px; +} + .button-filter-bar-show-all { height: 24px; padding: 5px 24px; diff --git a/libs/sdk-ui-dashboard/styles/scss/insightsNotSavedDialog.scss b/libs/sdk-ui-dashboard/styles/scss/insightsNotSavedDialog.scss new file mode 100644 index 00000000000..7c0d3340e61 --- /dev/null +++ b/libs/sdk-ui-dashboard/styles/scss/insightsNotSavedDialog.scss @@ -0,0 +1,26 @@ +// (C) 2020-2025 GoodData Corporation +@use "variables"; +@use "@gooddata/sdk-ui-kit/styles/scss/variables" as kit-variables; + +.gdc-insight-not-saved-dialog { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 10px; + + .gd-ui-kit-button { + font-weight: 700; + } + + ul { + padding: 0; + margin: -5px 0 0 18px; + list-style: none; + + li { + display: flex; + align-items: center; + gap: 5px; + } + } +} diff --git a/libs/sdk-ui-dashboard/styles/scss/main.scss b/libs/sdk-ui-dashboard/styles/scss/main.scss index 2dd558859a7..f3b513dbad4 100644 --- a/libs/sdk-ui-dashboard/styles/scss/main.scss +++ b/libs/sdk-ui-dashboard/styles/scss/main.scss @@ -53,3 +53,4 @@ @use "scheduleTimezone"; @use "pdfExportDialog"; @use "kdaDialog"; +@use "insightsNotSavedDialog"; diff --git a/libs/sdk-ui-dashboard/vitest.config.ts b/libs/sdk-ui-dashboard/vitest.config.ts index 8a216acf5be..70dfdcbf1b6 100644 --- a/libs/sdk-ui-dashboard/vitest.config.ts +++ b/libs/sdk-ui-dashboard/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-ext/.dependency-cruiser.js b/libs/sdk-ui-ext/.dependency-cruiser.js index 4a641fa7b0e..7772bdb2b9c 100644 --- a/libs/sdk-ui-ext/.dependency-cruiser.js +++ b/libs/sdk-ui-ext/.dependency-cruiser.js @@ -27,7 +27,6 @@ const GeoChartNextSeparationRules = [ }, ]; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ ...DefaultRules.filter((rule) => rule.name !== "not-to-lodash-get"), diff --git a/libs/sdk-ui-ext/api/sdk-ui-ext.api.md b/libs/sdk-ui-ext/api/sdk-ui-ext.api.md index 763395bc3e5..a4acca2b5c5 100644 --- a/libs/sdk-ui-ext/api/sdk-ui-ext.api.md +++ b/libs/sdk-ui-ext/api/sdk-ui-ext.api.md @@ -1368,6 +1368,9 @@ export function useApplyObjectPermissions(): (target: IObjectPermissionsObject, export function useCfDateFilterOptions(backend: IAnalyticalBackend | undefined, workspace: string | undefined, enabled: boolean): IDateFilterOptionsByType | undefined; +// @internal +export function useCopyObjectPermissions(): (from: IObjectPermissionsObject, to: IObjectPermissionsObject) => Promise; + // @internal export function useInsightPagedList(input: IUsePagedDropdownConfig): IUsePagedDropdownResult; diff --git a/libs/sdk-ui-ext/package.json b/libs/sdk-ui-ext/package.json index 15318ece817..279e44dfcaa 100644 --- a/libs/sdk-ui-ext/package.json +++ b/libs/sdk-ui-ext/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-ext", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Extensions", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -141,12 +141,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "svgo": "2.8.3", "typescript": "7.0.2", diff --git a/libs/sdk-ui-ext/src/index.ts b/libs/sdk-ui-ext/src/index.ts index 77404dcf324..bb76c297a2f 100644 --- a/libs/sdk-ui-ext/src/index.ts +++ b/libs/sdk-ui-ext/src/index.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package contains various extensions on top of the stable components included in GoodData.UI. @@ -354,6 +354,7 @@ export type { ObjectSharePermissionLevel, } from "./share/objectShareController.types.js"; export { useApplyObjectPermissions } from "./share/useApplyObjectPermissions.js"; +export { useCopyObjectPermissions } from "./share/useCopyObjectPermissions.js"; export { accessListToSummary, draftToSummary, diff --git a/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx b/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx index d2d4b48a1be..e958a6ab559 100644 --- a/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx +++ b/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/CfSelect.tsx @@ -87,11 +87,11 @@ export function CfSelect({ value, items, onSelect, placeholder accessibilityConfig={{ popupType: "listbox" }} /> )} - renderBody={({ closeDropdown, ariaAttributes }) => ( + renderBody={({ closeDropdown, ariaAttributes, triggerId }) => ( , null> items={listItems} selectedItemId={value} - ariaAttributes={ariaAttributes} + ariaAttributes={{ ...ariaAttributes, "aria-labelledby": triggerId }} InteractiveItemComponent={CfListItem} onClose={closeDropdown} onSelect={(listItem) => onSelect(listItem.data.value)} diff --git a/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/conditionalFormattingColors.test.ts b/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/conditionalFormattingColors.test.ts index b00082835f2..61c2c835dce 100644 --- a/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/conditionalFormattingColors.test.ts +++ b/libs/sdk-ui-ext/src/internal/components/configurationControls/conditionalFormatting/conditionalFormattingColors.test.ts @@ -38,8 +38,6 @@ describe("colorToHex", () => { it("round-trips a preset hex through hexToColor -> colorToHex", () => { const color = hexToColor("#3DB36B"); expect(color).toBeDefined(); - if (color) { - expect(colorToHex(color)).toBe("#3DB36B"); - } + expect(colorToHex(color!)).toBe("#3DB36B"); }); }); diff --git a/libs/sdk-ui-ext/src/internal/index.ts b/libs/sdk-ui-ext/src/internal/index.ts index db3ec1b41b3..d698db64767 100644 --- a/libs/sdk-ui-ext/src/internal/index.ts +++ b/libs/sdk-ui-ext/src/internal/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { Axis } from "./constants/axis.js"; export { DrillablePredicatesUtils } from "./utils/drillablePredicates.js"; diff --git a/libs/sdk-ui-ext/src/internal/translations/zh-HK.json b/libs/sdk-ui-ext/src/internal/translations/zh-HK.json index 467e1bf4fb1..a980c758dfa 100644 --- a/libs/sdk-ui-ext/src/internal/translations/zh-HK.json +++ b/libs/sdk-ui-ext/src/internal/translations/zh-HK.json @@ -1,23 +1,23 @@ { "or": "或", - "dashboard.bucket.measures_title.column": "計量", - "dashboard.bucket.measures_title.bar": "計量", - "dashboard.bucket.measures_title.line": "計量", - "dashboard.bucket.measures_title.area": "計量", - "dashboard.bucket.measures_title.pie": "計量", - "dashboard.bucket.measures_title.funnel": "計量", - "dashboard.bucket.measures_title.pyramid": "計量", - "dashboard.bucket.measures_title.treemap": "計量", - "dashboard.bucket.measures_title.donut": "計量", - "dashboard.bucket.measures_title.heatmap": "計量", - "dashboard.bucket.measures_title.headline": "計量", + "dashboard.bucket.measures_title.column": "指標", + "dashboard.bucket.measures_title.bar": "指標", + "dashboard.bucket.measures_title.line": "指標", + "dashboard.bucket.measures_title.area": "指標", + "dashboard.bucket.measures_title.pie": "指標", + "dashboard.bucket.measures_title.funnel": "指標", + "dashboard.bucket.measures_title.pyramid": "指標", + "dashboard.bucket.measures_title.treemap": "指標", + "dashboard.bucket.measures_title.donut": "指標", + "dashboard.bucket.measures_title.heatmap": "指標", + "dashboard.bucket.measures_title.headline": "指標", "dashboard.bucket.measures_subtitle.headline": "主要", - "dashboard.bucket.secondary_measures_title.headline": "計量", + "dashboard.bucket.secondary_measures_title.headline": "指標", "dashboard.bucket.secondary_measures_subtitle.headline": "次要", - "dashboard.bucket.measures_title.scatter": "計量", - "dashboard.bucket.secondary_measures_title.scatter": "計量", - "dashboard.bucket.measures_title.mekko": "計量", - "dashboard.bucket.secondary_measures_title.mekko": "計量", + "dashboard.bucket.measures_title.scatter": "指標", + "dashboard.bucket.secondary_measures_title.scatter": "指標", + "dashboard.bucket.measures_title.mekko": "指標", + "dashboard.bucket.secondary_measures_title.mekko": "指標", "dashboard.bucket.measures_subtitle.mekko": "寬度", "dashboard.bucket.secondary_measures_subtitle.mekko": "高度", "dashboard.bucket.view_title.mekko": "查看方式", @@ -26,12 +26,12 @@ "dashboard.bucket.secondary_measures_subtitle.scatter": "Y軸", "dashboard.bucket.attribute_title.scatter": "查看方式", "dashboard.bucket.segment_title.scatter": "細分依據", - "dashboard.bucket.measures_title.combo": "計量", + "dashboard.bucket.measures_title.combo": "指標", "dashboard.bucket.measures_subtitle.combo": "欄", - "dashboard.bucket.secondary_measures_title.combo": "計量", + "dashboard.bucket.secondary_measures_title.combo": "指標", "dashboard.bucket.secondary_measures_subtitle.combo": "線", - "dashboard.bucket.measures_title.xirr": "計量", - "dashboard.bucket.measures_title.waterfall": "計量", + "dashboard.bucket.measures_title.xirr": "指標", + "dashboard.bucket.measures_title.waterfall": "指標", "dashboard.bucket.attribute_title.xirr": "日期屬性", "dashboard.bucket.attribute_from_title.sankey": "屬性", "dashboard.bucket.attribute_from_subtitle.sankey": "從", @@ -44,19 +44,19 @@ "dashboard.bucket.combo.subtitle.column": "作為列", "dashboard.bucket.combo.subtitle.line": "作為綫", "dashboard.bucket.combo.subtitle.area": "作為區域", - "dashboard.bucket.measures_title.bubble": "計量", - "dashboard.bucket.secondary_measures_title.bubble": "計量", - "dashboard.bucket.tertiary_measures_title.bubble": "計量", + "dashboard.bucket.measures_title.bubble": "指標", + "dashboard.bucket.secondary_measures_title.bubble": "指標", + "dashboard.bucket.tertiary_measures_title.bubble": "指標", "dashboard.bucket.measures_subtitle.bubble": "X軸", "dashboard.bucket.secondary_measures_subtitle.bubble": "Y軸", "dashboard.bucket.tertiary_measures_subtitle.bubble": "大小", "dashboard.bucket.location_title.pushpin": "位置", "dashboard.bucket.area_title.choropleth": "區域", - "dashboard.bucket.size_title.pushpin": "計量", + "dashboard.bucket.size_title.pushpin": "指標", "dashboard.bucket.size_subtitle.pushpin": "大小", - "dashboard.bucket.color_title.pushpin": "計量", + "dashboard.bucket.color_title.pushpin": "指標", "dashboard.bucket.color_subtitle.pushpin": "顏色", - "dashboard.bucket.metric_title.pushpin": "計量", + "dashboard.bucket.metric_title.pushpin": "指標", "dashboard.bucket.view_title.bubble": "查看方式", "dashboard.bucket.view_title.pie": "查看方式", "dashboard.bucket.view_title.treemap": "查看方式", @@ -76,24 +76,24 @@ "dashboard.bucket.stack_title.area": "堆疊依據", "dashboard.bucket.stack_title.heatmap": "列", "dashboard.bucket.segment_title.line": "細分依據", - "dashboard.bucket.measures_title.radar": "計量", + "dashboard.bucket.measures_title.radar": "指標", "dashboard.bucket.trend_title.radar": "查看方式", "dashboard.bucket.segment_title.radar": "細分依據", "dashboard.bucket.segment_title.treemap": "細分依據", "dashboard.bucket.segment_title.pushpin": "細分依據", - "dashboard.bucket.metric_segment_by_warning": "若要新增其他計量,請移除{icons} 來自 ''分段依據''", - "dashboard.bucket.metric_stack_by_warning": "若要新增其他計量,請移除{icons} 來自 ''堆疊''", - "dashboard.bucket.metric_view_by_warning": "若要新增其他計量,請移除{icons} 來自 ''查看者''", - "dashboard.bucket.category_view_by_warning": "要按另一個屬性查看,可視化只能有一個計量", + "dashboard.bucket.metric_segment_by_warning": "若要新增其他指標,請移除{icons} 來自 ''分段依據''", + "dashboard.bucket.metric_stack_by_warning": "若要新增其他指標,請移除{icons} 來自 ''堆疊''", + "dashboard.bucket.metric_view_by_warning": "若要新增其他指標,請移除{icons} 來自 ''查看者''", + "dashboard.bucket.category_view_by_warning": "要按另一個屬性查看,可視化只能有一個指標", "dashboard.bucket.view_stack_by_warning": "若要新增附加屬性,請移除{icons} 來自 ''堆疊依據''", - "dashboard.bucket.category_stack_by_warning": "若要堆疊,可視化效果只能有一個計量", - "dashboard.bucket.stack_view_by_warning": "若要堆疊,可視化效果只能有一個屬性喺視圖中", - "dashboard.bucket.measure_stack_by_warning": "要按屬性堆疊,可視化效果只能有一個計量", - "dashboard.bucket.category_columns_warning": "無法添加列:限制為{oldLimit}計量和{oldRowsLimit}行。 按照限制添加更多", + "dashboard.bucket.category_stack_by_warning": "若要堆疊,可視化效果只能有一個指標", + "dashboard.bucket.stack_view_by_warning": "若要堆疊,可視化只能在檢視依據中有一個屬性", + "dashboard.bucket.measure_stack_by_warning": "要按屬性堆疊,可視化效果只能有一個指標", + "dashboard.bucket.category_columns_warning": "無法添加列:限制為{oldLimit}指標和{oldRowsLimit}行。 按照限制添加更多", "dashboard.bucket.category_measures_rows_warning": "要添加更多{limit}內容,請由列部分中刪除所有項目", - "dashboard.bucket.category_category_by_warning": "要查看,可視化效果只能有一個計量", - "dashboard.bucket.category_segment_by_warning": "若要進行區段,可視化效果只能有一個計量", - "dashboard.bucket.measures_title.table": "計量", + "dashboard.bucket.category_category_by_warning": "要查看,可視化效果只能有一個指標", + "dashboard.bucket.category_segment_by_warning": "若要進行區段,可視化效果只能有一個指標", + "dashboard.bucket.measures_title.table": "指標", "dashboard.bucket.measures_title.sankey": "度量", "dashboard.bucket.measures_title.dependencywheel": "度量", "dashboard.bucket.attribute_title.table": "列", @@ -101,10 +101,10 @@ "dashboard.bucket.attribute_title.repeater": "列", "dashboard.bucket.view_title.repeater": "查看方式", "dashboard.bucket.columns_title.repeater": "列", - "dashboard.error.missing_primary_bucket_item.heading": "可視化效果中冇主要計量", - "dashboard.error.missing_primary_bucket_item.text": "將主要計量添加到可視化效果,或切換到表。\n完成後,您將能夠儲存它。", + "dashboard.error.missing_primary_bucket_item.heading": "可視化中沒有主要指標", + "dashboard.error.missing_primary_bucket_item.text": "將主要指標添加到可視化效果,或切換到表。\n完成後,您將能夠儲存它。", "dashboard.xirr.error.invalid_buckets.heading": "配置不完整", - "dashboard.xirr.error.invalid_buckets.text": "確保您已選擇一個計量和一個日期屬性", + "dashboard.xirr.error.invalid_buckets.text": "確保您已選擇一個指標和一個日期屬性", "properties.unsupported": "此可視化效果不支援配置", "properties.config.not_applicable": "配置面板不適用於可視化的此配置", "properties.not_applicable": "屬性不適用於可視化的此配置", @@ -115,7 +115,7 @@ "properties.total.title": "總", "properties.total.name": "名字", "properties.total.tooltip": "把所有值的總和作為末尾的一列相加。", - "properties.total.measures.tooltip": "在計量中禁用“係總計”選項,以把所有值的總和作為末尾的一列相加。", + "properties.total.measures.tooltip": "在指標中禁用“是總計”選項,以把所有值的總和作為末尾的一列相加。", "properties.color.total": "總", "properties.color.positive": "正數 ", "properties.color.negative": "負數", @@ -125,8 +125,8 @@ "properties.canvas.labels.percentages.info": "在每個切片上顯示百分比,最頂部的切片為100%,其他切片為該值的分數。", "properties.canvas.labels.percentages.disabled": "由於隱藏了數據標籤,百分比被隱藏了。", "properties.canvas.continuousLine.label": "連續線", - "properties.canvas.continuousLine.tooltip": "喺具有缺失值嘅之間畫一條綫。", - "properties.canvas.distinctPointShapes.label": "不同嘅啲形狀", + "properties.canvas.continuousLine.tooltip": "在缺少值的點之間繪製連線。", + "properties.canvas.distinctPointShapes.label": "不同的點形狀", "properties.canvas.distinctPointShapes.tooltip": "屬性不適用於可視化的此配置", "properties.canvas.labels.subsectionTitle": "標籤", "properties.canvas.dataLabels.auto": "自動(默認)", @@ -170,7 +170,7 @@ "properties.forecastConfidence.90": "90%", "properties.forecastConfidence.95": "95%", "properties.forecastSliced.title": "顯示部分預測。", - "properties.forecastSliced.description": "可用數據點唔會涵蓋成個選定時間段。", + "properties.forecastSliced.description": "可用的數據點未涵蓋整個選定期間。", "properties.anomalies.title": "異常偵測", "properties.anomaliesDisabled.title": "此存儲桶組合不適用於異常偵測。", "properties.anomalySensitivity.low": "低", @@ -188,9 +188,9 @@ "properties.clustering.amount.placeholder": "3", "properties.clustering.threshold": "門檻", "properties.clustering.threshold.placeholder": "0.03", - "properties.clustering.threshold.tooltip": "閾值係肯定何時對數據進行分組的限制。 瞭解更多信息", + "properties.clustering.threshold.tooltip": "閾值是決定何時將數據分組的限制。 了解更多", "properties.clustering.amount.partial.title": "顯示部分聚類", - "properties.clustering.amount.partial.description": "可用嘅數據啲唔會覆蓋成個選定嘅金額。", + "properties.clustering.amount.partial.description": "可用的數據點未涵蓋整個選定的金額。", "properties.clustering.disabled": "聚類需要2個量度同查看依據,而不需要細分依據。", "properties.canvas.totalLabels": "標籤總數", "properties.canvas.totalLabels.auto": "自動(默認)", @@ -205,7 +205,7 @@ "properties.axis.rotation": "旋轉", "properties.axis.format": "格式", "properties.axis.format.inherit": "繼承", - "properties.axis.format.info.inherit": "該格式繼承自可視化效果中的第一個計量。", + "properties.axis.format.info.inherit": "該格式繼承自可視化效果中的第一個指標。", "properties.axis.name": "名字", "properties.axis.name.position": "位置", "properties.axis.name.position.left": "左", @@ -305,17 +305,17 @@ "properties.fill.solid": "實心(預設)", "properties.fill.pattern": "模式", "properties.fill.outline": "輪廓", - "properties.colors.unsupported": "此可視化配置冇顏色", + "properties.colors.unsupported": "此可視化配置沒有顏色", "export_unsupported.colors": "可視化效果與自定義顏色不兼容。 要把可視化效果作為報告打開,請單擊“配置”中的“重置顏色”->“顏色”。", - "dashboard.bucket.measures_title.bullet": "計量", - "dashboard.bucket.secondary_measures_title.bullet": "計量", - "dashboard.bucket.tertiary_measures_title.bullet": "計量", + "dashboard.bucket.measures_title.bullet": "指標", + "dashboard.bucket.secondary_measures_title.bullet": "指標", + "dashboard.bucket.tertiary_measures_title.bullet": "指標", "dashboard.bucket.measures_subtitle.bullet": "主要", "dashboard.bucket.secondary_measures_subtitle.bullet": "目標", "dashboard.bucket.tertiary_measures_subtitle.bullet": "比較級", "sorting.disabled.explanation.attribute": "對於此可視化配置,無法進行排序。", - "sorting.disabled.explanation.measure": "你必須至少添加一個屬性才能對可視化效果進行排序。 你仲可以喺“計量”部分中調整項目嘅位置,以更改它們在可視化效果中嘅位置。", - "properties.metrics.title": "計量", + "sorting.disabled.explanation.measure": "您必須至少新增一個屬性才能對可視化進行排序。 您也可以在“指標”部分中調整項目的位置,以變更其在可視化中的位置。", + "properties.metrics.title": "指標", "properties.metrics.position": "位置", "properties.metrics.position.columns": "列(默認)", "properties.metrics.position.rows": "列", @@ -357,21 +357,21 @@ "properties.interactions.drillDown": "向下切入", "properties.interactions.drillIntoURL": "切入到URL", "properties.interactions.alerts": "警示", - "properties.interactions.alerts.tooltip": "使用者能夠喺儀錶板上為此可視化創建警示。", + "properties.interactions.alerts.tooltip": "讓使用者能夠在儀表板上為此可視化建立警示。", "properties.interactions.scheduledExports": "排程匯出", - "properties.interactions.scheduledExports.tooltip": "使用者能夠喺功能板上為此可視化創建排程匯出。", + "properties.interactions.scheduledExports.tooltip": "讓使用者能夠在儀表板上為此可視化建立排程匯出。", "properties.interactions.kda": "主要驅動因素分析", - "properties.interactions.kda.tooltip": " 分析哪些因素推動所選指標的變化。系統會按影響程度識別並排序主要驅動因素,協助你在無需手動操作的情況下理解績效變化。", + "properties.interactions.kda.tooltip": " 分析哪些因素推動所選指標的變化。系統會按影響程度識別並排序主要驅動因素,協助您在無需手動操作的情況下理解績效變化。", "properties.comparison.title": "比較", "properties.comparison.calculationType.title": "計算公式為", "properties.comparison.calculationType.as.change": "改變", "properties.comparison.calculationType.as.ratio": "率", "properties.comparison.calculationType.as.difference": "差異", "properties.comparison.calculationType.as.changeDifference": "變化(差異)", - "properties.comparison.calculationType.as.change.tooltip.useIn": "計算主要計量值同次要計量值之間的相對變化。", - "properties.comparison.calculationType.as.ratio.tooltip.useIn": "量化主要計量值喺次要計量值中嘅份額。", - "properties.comparison.calculationType.as.difference.tooltip.useIn": "計算主要計量值同次要指標值之間的絕對差異。", - "properties.comparison.calculationType.as.changeDifference.tooltip.useIn": "計算主要指標值和次要計量值之間的相對變化同絕對差值。", + "properties.comparison.calculationType.as.change.tooltip.useIn": "計算主要指標值同次要指標值之間的相對變化。", + "properties.comparison.calculationType.as.ratio.tooltip.useIn": "量化主要指標值在次要指標值中的佔比。", + "properties.comparison.calculationType.as.difference.tooltip.useIn": "計算主要指標值同次要指標值之間的絕對差異。", + "properties.comparison.calculationType.as.changeDifference.tooltip.useIn": "計算主要指標值和次要指標值之間的相對變化同絕對差值。", "properties.comparison.calculationType.tooltip.formula.section": "公式", "properties.comparison.calculationType.tooltip.formulaChange.section": "公式 (相對變化)", "properties.comparison.calculationType.tooltip.formulaDifference.section": "公式 (絕對差)", @@ -406,7 +406,7 @@ "properties.comparison.measureNumber.format.template.conditionalColors": "條件顏色", "properties.comparison.measureNumber.format.template.trendSymbols": "趨勢符號", "properties.comparison.measureNumber.format.template.timeFromSeconds": "時間(以秒為單位)", - "properties.comparison.measureNumber.format.template.zeroInsteadOfNull": "零值而唔係空白值", + "properties.comparison.measureNumber.format.template.zeroInsteadOfNull": "以零代替空白值", "properties.comparison.valueSubSection.title": "值", "properties.comparison.format.title": "格式", "properties.comparison.subFormat.title": "(格式)", @@ -428,7 +428,7 @@ "properties.comparison.position.top": "返回頁首", "properties.comparison.position.right": "右 ", "properties.comparison.position.left": "左", - "visualizations.headline.tertiary.title": "啱", + "visualizations.headline.tertiary.title": "對比", "gs.date.yesterday": "昨日", "notifications.panel.button.label": "通知", "notifications.panel.notifications.list.label": "通知", @@ -516,27 +516,27 @@ "automations.menu.trigger": "立即傳送", "automations.menu.copyId": "複製ID", "automations.message.copyId.success": "ID已複製到剪貼板", - "automations.message.schedule.delete.success": "成功! 你排程的電子郵件已刪除。", + "automations.message.schedule.delete.success": "成功! 您的預約電子郵件已刪除。", "automations.message.schedule.delete.error": "錯誤! 無法刪除您的預定電子郵件。", - "automations.message.schedule.unsubscribe.success": "成功! 你已取消訂閱預定的電子郵件。", + "automations.message.schedule.unsubscribe.success": "成功! 您已取消訂閱預定的電子郵件。", "automations.message.schedule.unsubscribe.error": "錯誤! 無法取消訂閱訂的電子郵件。", "automations.message.schedule.bulk.delete.success": "成功! 您的預定電子郵件已被刪除。", "automations.message.schedule.bulk.delete.error": "錯誤! 無法刪除已安排的電子郵件。", - "automations.message.schedule.bulk.unsubscribe.success": "成功! 你已取消訂閱預定的電子郵件。", + "automations.message.schedule.bulk.unsubscribe.success": "成功! 您已取消訂閱預定的電子郵件。", "automations.message.schedule.bulk.unsubscribe.error": "錯誤! 未能取消訂閱訂的電子郵件。", - "automations.message.schedule.pause.success": "成功! 你預定的電子郵件已暫停。", + "automations.message.schedule.pause.success": "成功! 您預定的電子郵件已暫停。", "automations.message.schedule.pause.error": "錯誤! 無法暫停您的預定電子郵件。", "automations.message.schedule.bulk.pause.success": "成功! 您的計劃電子郵件已暫停。", "automations.message.schedule.bulk.pause.error": "錯誤! 無法暫停您的預定電子郵件。", - "automations.message.schedule.resume.success": "成功! 你預定的電子郵件已恢復。", + "automations.message.schedule.resume.success": "成功! 您預定的電子郵件已恢復。", "automations.message.schedule.resume.error": "錯誤! 無法恢復預定的電子郵件。", - "automations.message.schedule.bulk.resume.success": "成功! 你計劃的電子郵件已恢復。", + "automations.message.schedule.bulk.resume.success": "成功! 您計劃的電子郵件已恢復。", "automations.message.schedule.bulk.resume.error": "錯誤! 無法恢復預定的電子郵件。", "automations.message.schedule.trigger.success": "成功!您的預約電子郵件已傳送。", "automations.message.schedule.trigger.error": "錯誤!無法傳送您的預約電子郵件。", "automations.message.alert.delete.success": "成功! 您的提醒已刪除。", "automations.message.alert.delete.error": "錯誤! 無法刪除警報。", - "automations.message.alert.unsubscribe.success": "成功! 你已取消訂閱警報。", + "automations.message.alert.unsubscribe.success": "成功! 您已取消訂閱警報。", "automations.message.alert.unsubscribe.error": "錯誤! 無法取消訂閱警報。", "automations.message.alert.pause.success": "成功! 您的警報已暫停。", "automations.message.alert.pause.error": "錯誤! 無法暫停警報。", @@ -544,7 +544,7 @@ "automations.message.alert.resume.error": "錯誤! 無法恢復警報。", "automations.message.alert.bulk.delete.success": "成功! 您的警報已被刪除。", "automations.message.alert.bulk.delete.error": "錯誤! 無法刪除警報。", - "automations.message.alert.bulk.unsubscribe.success": "成功! 你已取消訂閱警報。", + "automations.message.alert.bulk.unsubscribe.success": "成功! 您已取消訂閱警報。", "automations.message.alert.bulk.unsubscribe.error": "錯誤! 無法取消訂閱警報。", "automations.message.alert.bulk.pause.success": "成功! 您的警報已暫停。", "automations.message.alert.bulk.pause.error": "錯誤! 無法暫停警報。", @@ -556,17 +556,17 @@ "automations.confirmDialog.delete.alert.headline": "刪除警報?", "automations.confirmDialog.delete.alert.content": "警報 {title} 將被永久刪除。", "automations.confirmDialog.unsubscribe.schedule.headline": "取消訂閱時間表?", - "automations.confirmDialog.unsubscribe.schedule.content": "你將被取消訂閱時間表{title}。", + "automations.confirmDialog.unsubscribe.schedule.content": "您將被取消訂閱時間表{title}。", "automations.confirmDialog.unsubscribe.alert.headline": "取消訂閱Alert?", - "automations.confirmDialog.unsubscribe.alert.content": "你將取消訂閱警報{title}。", + "automations.confirmDialog.unsubscribe.alert.content": "您將取消訂閱警報{title}。", "automations.confirmDialog.bulkDelete.schedule.headline": "刪除計劃?", - "automations.confirmDialog.bulkDelete.schedule.content": "你確定要刪除選定的計劃啊?", + "automations.confirmDialog.bulkDelete.schedule.content": "您確定要刪除選定的計劃啊?", "automations.confirmDialog.bulkDelete.alert.headline": "刪除警報", - "automations.confirmDialog.bulkDelete.alert.content": "你確定要刪除選定的警報啊?", + "automations.confirmDialog.bulkDelete.alert.content": "您確定要刪除選定的警報啊?", "automations.confirmDialog.bulkUnsubscribe.schedule.headline": "取消訂閱時間表?", - "automations.confirmDialog.bulkUnsubscribe.schedule.content": "你確定要取消訂閱選定的時間表呀?", + "automations.confirmDialog.bulkUnsubscribe.schedule.content": "您確定要取消訂閱選定的時間表呀?", "automations.confirmDialog.bulkUnsubscribe.alert.headline": "取消訂閱警報", - "automations.confirmDialog.bulkUnsubscribe.alert.content": "你確定要取消訂閱選定的警報呀?", + "automations.confirmDialog.bulkUnsubscribe.alert.content": "您確定要取消訂閱選定的警報呀?", "automations.confirmDialog.pause.schedule.headline": "暫停計劃?", "automations.confirmDialog.pause.schedule.content": "日程將 {title} 暫停。", "automations.confirmDialog.pause.alert.headline": "暫停警報?", @@ -576,11 +576,11 @@ "automations.confirmDialog.resume.alert.headline": "簡歷提醒?", "automations.confirmDialog.resume.alert.content": "警報 {title} 將恢復。", "automations.confirmDialog.bulkPause.schedule.headline": "暫停時間表?", - "automations.confirmDialog.bulkPause.schedule.content": "你確定要暫停選定的計劃啊?", + "automations.confirmDialog.bulkPause.schedule.content": "您確定要暫停選定的計劃啊?", "automations.confirmDialog.bulkPause.alert.headline": "暫停警報?", "automations.confirmDialog.bulkPause.alert.content": "是否確定要暫停選定的警報?", "automations.confirmDialog.bulkResume.schedule.headline": "簡歷時間表?", - "automations.confirmDialog.bulkResume.schedule.content": "你確定要恢復選定的計劃啊?", + "automations.confirmDialog.bulkResume.schedule.content": "您確定要恢復選定的計劃啊?", "automations.confirmDialog.bulkResume.alert.headline": "簡歷提醒?", "automations.confirmDialog.bulkResume.alert.content": "是否確定要恢復選定的警報?", "automations.confirmDialog.button.delete": "刪除", @@ -588,7 +588,7 @@ "automations.confirmDialog.button.cancel": "取消", "automations.emptyState.schedule.title": "無定期出口", "automations.emptyState.schedule.description": "在儀表板或視覺化上建立排程匯出。", - "automations.emptyState.alert.title": "冇警報", + "automations.emptyState.alert.title": "沒有警示", "automations.emptyState.alert.description": "在任何儀表板上創建有關可視化的警報。", "automations.accessibility.selectAll.alerts": "選擇所有警報", "automations.accessibility.selectAll.schedules": "選取所有預定的匯出", @@ -656,7 +656,7 @@ "properties.conditionalFormatting.dialog.useDefaultRule": "使用預設規則", "properties.conditionalFormatting.dialog.useDefaultRuleInfo": "對此項目使用預設規則。關閉此選項以覆寫預設規則。", "properties.conditionalFormatting.dialog.ruleCannotBeDeleted": "無法刪除此規則。請在規則清單中停用它。", - "properties.conditionalFormatting.dialog.target": "幾時", + "properties.conditionalFormatting.dialog.target": "當", "properties.conditionalFormatting.dialog.selectTarget": "選擇測量或屬性", "properties.conditionalFormatting.dialog.condition": "條件", "properties.conditionalFormatting.dialog.format": "格式", diff --git a/libs/sdk-ui-ext/src/internal/utils/comparisonHelper.test.ts b/libs/sdk-ui-ext/src/internal/utils/comparisonHelper.test.ts index 88800e7ae13..d685764031e 100644 --- a/libs/sdk-ui-ext/src/internal/utils/comparisonHelper.test.ts +++ b/libs/sdk-ui-ext/src/internal/utils/comparisonHelper.test.ts @@ -53,7 +53,17 @@ describe("comparisonHelper", () => { calculationType: CalculateAs.RATIO, }, }); - const SPECS: any = [ + + type Spec = [ + string, + { + calculationType: CalculationType | null; + props: IVisualizationProperties; + }, + { format: string | null; labels: Record }, + ]; + + const SPECS: Spec[] = [ [ "Should use default calculation type [DIFFERENCE]", { calculationType: CalculateAs.DIFFERENCE, props: {} }, @@ -86,20 +96,15 @@ describe("comparisonHelper", () => { ], ]; - it.each(SPECS)("%s", ((...args: any[]) => { - const [_condition, data, expected] = args; - const typedData = data as { - calculationType: CalculationType; - props: IVisualizationProperties; - }; + it.each(SPECS)("%s", (_condition, data, expected) => { const { defaultFormat, defaultLabelKeys } = getComparisonDefaultValues( - typedData.calculationType, - typedData.props, + data.calculationType as unknown as CalculationType, + data.props, ); expect(defaultFormat).toEqual(expected.format); expect(defaultLabelKeys).toEqual(expected.labels); - }) as any); + }); }); describe("getPresets", () => { diff --git a/libs/sdk-ui-ext/src/share/objectShareController.helpers.ts b/libs/sdk-ui-ext/src/share/objectShareController.helpers.ts index 9bf381d08db..ce6b6bbc92c 100644 --- a/libs/sdk-ui-ext/src/share/objectShareController.helpers.ts +++ b/libs/sdk-ui-ext/src/share/objectShareController.helpers.ts @@ -2,6 +2,7 @@ import { type AccessGranteeDetail, + type AccessGranularPermission, type IAvailableAccessGrantee, type IGranularAccessGrantee, type IObjectAccessList, @@ -628,6 +629,42 @@ export function draftToPermissions(draft: IObjectShareDraft): IGranularAccessGra return permissions; } +/** + * The permissions to POST to reproduce an object's own access elsewhere. Inherited grants are + * left out; rule grants are merged, since a hierarchy lists one per granting workspace. + */ +export function accessListToPermissions(list: IObjectAccessList): IGranularAccessGrantee[] { + const permissions: IGranularAccessGrantee[] = []; + const rulePermissions: AccessGranularPermission[] = []; + for (const grant of list.grants) { + if (isGranularUserAccess(grant) && grant.permissions.length > 0) { + permissions.push({ + type: "granularUser", + granteeRef: grant.user.ref, + permissions: grant.permissions, + inheritedPermissions: [], + }); + } else if (isGranularUserGroupAccess(grant) && grant.permissions.length > 0) { + permissions.push({ + type: "granularGroup", + granteeRef: grant.userGroup.ref, + permissions: grant.permissions, + inheritedPermissions: [], + }); + } else if (grant.type === "allWorkspaceUsers") { + rulePermissions.push(...grant.permissions); + } + } + if (rulePermissions.length > 0) { + permissions.push({ + type: "allWorkspaceUsers", + permissions: Array.from(new Set(rulePermissions)), + inheritedPermissions: [], + }); + } + return permissions; +} + /** Stable empty-labels default so the hook's default arg doesn't churn identities. */ export const NO_LABELS: IObjectShareLabel[] = []; diff --git a/libs/sdk-ui-ext/src/share/useCopyObjectPermissions.ts b/libs/sdk-ui-ext/src/share/useCopyObjectPermissions.ts new file mode 100644 index 00000000000..67d35034cbc --- /dev/null +++ b/libs/sdk-ui-ext/src/share/useCopyObjectPermissions.ts @@ -0,0 +1,41 @@ +// (C) 2026 GoodData Corporation + +import { useCallback } from "react"; + +import type { IObjectPermissionsObject } from "@gooddata/sdk-backend-spi"; +import { useBackendStrict, useWorkspaceStrict } from "@gooddata/sdk-ui"; + +import { accessListToPermissions } from "./objectShareController.helpers.js"; + +/** + * Reproduces one object's own access on another. + * + * @remarks + * Inherited grants are left out; they follow the grantee. False when the access could + * not be read or written. + * + * @internal + */ +export function useCopyObjectPermissions(): ( + from: IObjectPermissionsObject, + to: IObjectPermissionsObject, +) => Promise { + const backend = useBackendStrict(); + const workspace = useWorkspaceStrict(); + + return useCallback( + async (from: IObjectPermissionsObject, to: IObjectPermissionsObject): Promise => { + const service = backend.workspace(workspace).objectPermissions(); + try { + const permissions = accessListToPermissions(await service.getAccessList(from)); + if (permissions.length > 0) { + await service.manageObjectPermissions(to, permissions); + } + return true; + } catch { + return false; + } + }, + [backend, workspace], + ); +} diff --git a/libs/sdk-ui-ext/src/typings.d.ts b/libs/sdk-ui-ext/src/typings.d.ts index d4e18e18001..6f9aef68e99 100644 --- a/libs/sdk-ui-ext/src/typings.d.ts +++ b/libs/sdk-ui-ext/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2022 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/libs/sdk-ui-ext/vitest.config.ts b/libs/sdk-ui-ext/vitest.config.ts index 8a216acf5be..70dfdcbf1b6 100644 --- a/libs/sdk-ui-ext/vitest.config.ts +++ b/libs/sdk-ui-ext/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-filters/.dependency-cruiser.js b/libs/sdk-ui-filters/.dependency-cruiser.js index 487a46443d0..13c14e0d16c 100644 --- a/libs/sdk-ui-filters/.dependency-cruiser.js +++ b/libs/sdk-ui-filters/.dependency-cruiser.js @@ -9,7 +9,6 @@ import { moduleWithDependencies, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ // not-to-unresolvable and non-standard-import are replaced below: @rc-component/picker's package.json diff --git a/libs/sdk-ui-filters/package.json b/libs/sdk-ui-filters/package.json index 55e49372a50..02259e6e11c 100644 --- a/libs/sdk-ui-filters/package.json +++ b/libs/sdk-ui-filters/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-filters", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Filter Components", "license": "MIT", "author": "GoodData Corporation", @@ -109,12 +109,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-filters/src/DateFilter/utils/DateFilterOptionMapping.test.ts b/libs/sdk-ui-filters/src/DateFilter/utils/DateFilterOptionMapping.test.ts index efd0507fa21..a0860b4ed14 100644 --- a/libs/sdk-ui-filters/src/DateFilter/utils/DateFilterOptionMapping.test.ts +++ b/libs/sdk-ui-filters/src/DateFilter/utils/DateFilterOptionMapping.test.ts @@ -49,10 +49,7 @@ describe("matchDateFilterToDateFilterOptionWithPreference", () => { ); expect(excludeCurrentPeriod).toBe(false); expect(dateFilterOption.type).toBe("absoluteForm"); - if (dateFilterOption.type === "absoluteForm") { - expect(dateFilterOption.from).toBe("2026-07-01"); - expect(dateFilterOption.to).toBe("2026-09-30"); - } + expect(dateFilterOption).toMatchObject({ from: "2026-07-01", to: "2026-09-30" }); }); }); diff --git a/libs/sdk-ui-filters/src/index.ts b/libs/sdk-ui-filters/src/index.ts index 953400ac6ec..a19f0b15e42 100644 --- a/libs/sdk-ui-filters/src/index.ts +++ b/libs/sdk-ui-filters/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides several React components related to filters. diff --git a/libs/sdk-ui-filters/src/internal.ts b/libs/sdk-ui-filters/src/internal.ts index 5227a2b2d1a..21d8c1b44d0 100644 --- a/libs/sdk-ui-filters/src/internal.ts +++ b/libs/sdk-ui-filters/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2023-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { DEFAULT_LOCALE } from "./DateFilter/utils/FormattingUtils.js"; export { AbsoluteDateFilterForm } from "./DateFilter/AbsoluteDateFilterForm/AbsoluteDateFilterForm.js"; diff --git a/libs/sdk-ui-filters/vitest.config.ts b/libs/sdk-ui-filters/vitest.config.ts index e2ce92d7f89..37502abba25 100644 --- a/libs/sdk-ui-filters/vitest.config.ts +++ b/libs/sdk-ui-filters/vitest.config.ts @@ -12,7 +12,6 @@ const require = createRequire(import.meta.url); // require.resolve picks the exports map's "require" condition, i.e. the lib/ CJS entry. const rcPickerLibDir = dirname(realpathSync(require.resolve("@rc-component/picker"))); -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-gen-ai/package.json b/libs/sdk-ui-gen-ai/package.json index ff5ce558868..a7b9c81eb5b 100644 --- a/libs/sdk-ui-gen-ai/package.json +++ b/libs/sdk-ui-gen-ai/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-gen-ai", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData GenAI SDK", "license": "MIT", "author": "GoodData Corporation", @@ -130,12 +130,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "svgo": "2.8.3", "typescript": "7.0.2", diff --git a/libs/sdk-ui-gen-ai/src/components/Input.tsx b/libs/sdk-ui-gen-ai/src/components/Input.tsx index 5640fe77c8f..b7c2cb4adf8 100644 --- a/libs/sdk-ui-gen-ai/src/components/Input.tsx +++ b/libs/sdk-ui-gen-ai/src/components/Input.tsx @@ -16,7 +16,11 @@ import { makeUserItem, makeUserMessage, } from "../model.js"; -import { agentSwitchingEnabledSelector } from "../store/chatWindow/chatWindowSelectors.js"; +import { + agentSwitchingEnabledSelector, + inputValueSelector, +} from "../store/chatWindow/chatWindowSelectors.js"; +import { setInputValueAction } from "../store/chatWindow/chatWindowSlice.js"; import { asyncProcessSelector, conversationMessagesSelector, @@ -82,7 +86,7 @@ function InputComponent({ autofocus = false, canManage, canAnalyze, targetRef }: const isAssistantLoading = isLoading || loading === "restoring"; const isEmpty = conversation ? !items?.length && !isLoading : !messages?.length && !isLoading; - const [value, setValue] = useState(""); + const value = useSelector((state: RootState) => inputValueSelector(state)); const [areAgentsBusy, setAreAgentsBusy] = useState(true); const [hasNoAgents, setHasNoAgents] = useState(false); const [editorApi, setApi] = useState(null); @@ -115,7 +119,7 @@ function InputComponent({ autofocus = false, canManage, canAnalyze, targetRef }: ]); } dispatch(newMessageAction(item)); - setValue(""); + dispatch(setInputValueAction({ value: "" })); }; const handleKeyDown = (e: KeyboardEvent) => { @@ -188,7 +192,9 @@ function InputComponent({ autofocus = false, canManage, canAnalyze, targetRef }: beforeExtensions={beforeExtensions} extensions={extensions} onApi={setApi} - onChange={setValue} + onChange={(value) => { + dispatch(setInputValueAction({ value })); + }} onFocus={() => { setFocused(true); }} diff --git a/libs/sdk-ui-gen-ai/src/components/intelligence/panel/IntelligenceFooter.tsx b/libs/sdk-ui-gen-ai/src/components/intelligence/panel/IntelligenceFooter.tsx index 113ace2e8ff..629816f84cf 100644 --- a/libs/sdk-ui-gen-ai/src/components/intelligence/panel/IntelligenceFooter.tsx +++ b/libs/sdk-ui-gen-ai/src/components/intelligence/panel/IntelligenceFooter.tsx @@ -27,7 +27,6 @@ export function IntelligenceFooter({ traceId }: IIntelligenceFooterProps) { {label} diff --git a/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.test.tsx b/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.test.tsx new file mode 100644 index 00000000000..d956e4f5205 --- /dev/null +++ b/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.test.tsx @@ -0,0 +1,194 @@ +// (C) 2026 GoodData Corporation + +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi"; + +import { ConfigProvider, type LinkHandlerEvent } from "../../ConfigContext.js"; + +import { CustomHyperlinkComponent } from "./CustomHyperlink.js"; + +const escapedText = "http://www\\.seznam\\.cz"; +const plainText = "http://www.seznam.cz"; + +type RenderCustomHyperlinkOptions = { + href?: string; + text?: string; + allowNativeLinks?: boolean; + canManage?: boolean; + canAnalyze?: boolean; + linkHandler?: (event: LinkHandlerEvent) => string | undefined; + settings?: IUserWorkspaceSettings; +}; + +function renderCustomHyperlinkComponent({ + href = "gooddata://dashboard?ws=workspace_1&id=dashboard_1", + text = escapedText, + allowNativeLinks = true, + canManage = true, + canAnalyze = false, + linkHandler, + settings, +}: RenderCustomHyperlinkOptions = {}) { + return render( + + + , + ); +} + +describe("CustomHyperlinkComponent", () => { + it.each([ + ["empty href", ""], + ["malformed encoded href", "%E0%A4%A"], + ["not an URL", "not-a-url"], + ["unsupported protocol", "https://www.gooddata.com/?ws=workspace_1&id=dashboard_1"], + ["missing workspace", "gooddata://dashboard?id=dashboard_1"], + ["missing id", "gooddata://dashboard?ws=workspace_1"], + ["unsupported object type", "gooddata://dataset?ws=workspace_1&id=dataset_1"], + ])("renders plain text and no link for %s", (_description, href) => { + renderCustomHyperlinkComponent({ href }); + + expect(screen.getByText(plainText)).toBeInTheDocument(); + expect(screen.queryByRole("link")).not.toBeInTheDocument(); + }); + + it("renders plain text for metric when user cannot manage or analyze", () => { + renderCustomHyperlinkComponent({ + href: "gooddata://metric?ws=workspace_1&id=metric_1", + canManage: false, + canAnalyze: false, + }); + + expect(screen.getByText(plainText)).toBeInTheDocument(); + expect(screen.queryByRole("link")).not.toBeInTheDocument(); + }); + + it("renders plain text for visualization when user cannot manage or analyze", () => { + renderCustomHyperlinkComponent({ + href: "gooddata://visualization?ws=workspace_1&id=insight_1", + canManage: false, + canAnalyze: false, + }); + + expect(screen.getByText(plainText)).toBeInTheDocument(); + expect(screen.queryByRole("link")).not.toBeInTheDocument(); + }); + + it("renders metric link when user can analyze", () => { + renderCustomHyperlinkComponent({ + href: "gooddata://metric?ws=workspace_1&id=metric_1", + canManage: false, + canAnalyze: true, + }); + + expect(screen.getByRole("link")).toHaveAttribute( + "href", + "/workspace/workspace_1/metrics/metric/metric_1", + ); + }); + + it.each<[string, string, Partial, string]>([ + [ + "dashboard using legacy shell URL", + "gooddata://dashboard?ws=workspace_1&id=dashboard_1", + {}, + "/dashboards/#/workspace/workspace_1/dashboard/dashboard_1", + ], + [ + "dashboard using workspace shell URL", + "gooddata://dashboard?ws=workspace_1&id=dashboard_1", + { enableShellApplication_dashboards: true }, + "/workspace/workspace_1/dashboards/#/dashboard/dashboard_1", + ], + [ + "visualization using legacy shell URL", + "gooddata://visualization?ws=workspace_1&id=insight_1", + {}, + "/analyze/#/workspace_1/insight_1/edit", + ], + [ + "visualization using workspace shell URL", + "gooddata://visualization?ws=workspace_1&id=insight_1", + { enableShellApplication_analyticalDesigner: true }, + "/workspace/workspace_1/analyze/#/insight_1/edit", + ], + [ + "metric", + "gooddata://metric?ws=workspace_1&id=metric_1", + {}, + "/workspace/workspace_1/metrics/metric/metric_1", + ], + ])("renders native link for %s", (_description, href, settings, expectedHref) => { + renderCustomHyperlinkComponent({ + href, + allowNativeLinks: true, + settings: settings as IUserWorkspaceSettings, + }); + + expect(screen.getByRole("link")).toHaveAttribute("href", expectedHref); + expect(screen.getByText(plainText)).toBeInTheDocument(); + }); + + it("renders non-native clickable span when native links are disabled", () => { + const { container } = renderCustomHyperlinkComponent({ allowNativeLinks: false }); + + expect(screen.queryByRole("link")).not.toBeInTheDocument(); + const hyperlinkElement = container.querySelector(".gd-hyperlink"); + expect(hyperlinkElement).toBeInTheDocument(); + expect(hyperlinkElement?.tagName.toLowerCase()).toBe("span"); + }); + + it("calls linkHandler with complete event payload on click", () => { + const linkHandler = vi.fn((_event: LinkHandlerEvent) => undefined); + renderCustomHyperlinkComponent({ linkHandler }); + + fireEvent.click(screen.getByRole("link"), { metaKey: true }); + + expect(linkHandler).toHaveBeenCalledTimes(1); + expect(linkHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: "dashboard", + id: "dashboard_1", + workspaceId: "workspace_1", + itemUrl: "/dashboards/#/workspace/workspace_1/dashboard/dashboard_1", + newTab: true, + action: "open", + }), + ); + + const event = linkHandler.mock.calls[0][0]; + expect(event.preventDefault).toEqual(expect.any(Function)); + }); + + it("does not fail when linkHandler is not provided", () => { + renderCustomHyperlinkComponent({ linkHandler: undefined }); + + expect(() => fireEvent.click(screen.getByRole("link"))).not.toThrow(); + }); + + it("supports URL-encoded gooddata links", () => { + renderCustomHyperlinkComponent({ + href: encodeURIComponent("gooddata://dashboard?ws=workspace_1&id=dashboard_1"), + }); + + expect(screen.getByRole("link")).toHaveAttribute( + "href", + "/dashboards/#/workspace/workspace_1/dashboard/dashboard_1", + ); + }); + + it("load URL-encoded http links, but change them to plain text", () => { + renderCustomHyperlinkComponent({ + href: encodeURIComponent("http://www\\.seznam\\.cz"), + }); + + expect(screen.getByText("http://www.seznam.cz")).toBeInTheDocument(); + }); +}); diff --git a/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.tsx b/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.tsx index ea15f87175e..c218853d3a9 100644 --- a/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.tsx +++ b/libs/sdk-ui-gen-ai/src/components/messages/contents/CustomHyperlink.tsx @@ -8,6 +8,7 @@ import { type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi"; import { settingsSelector } from "../../../store/chatWindow/chatWindowSelectors.js"; import { type LinkHandlerEvent, useConfig } from "../../ConfigContext.js"; +import { removeMarkdown } from "../../utils/markdownUtils.js"; export type CustomHyperlinkOwnProps = { href: string; @@ -30,13 +31,20 @@ export function CustomHyperlinkComponent({ href, text, settings }: CustomHyperli const enableShellApplication_dashboards = Boolean(settings?.enableShellApplication_dashboards); const canManageMetrics = canManage || canAnalyze; const canManageVisualisations = canManage || canAnalyze; + const plainText = removeMarkdown(text); const parsedRef = useMemo(() => { if (!href) { return null; } - const url = new URL(href); + let url; + try { + const decodedHref = decodeURIComponent(href); + url = new URL(decodedHref); + } catch { + return null; + } if (url.protocol !== "gooddata:") { return null; @@ -71,7 +79,7 @@ export function CustomHyperlinkComponent({ href, text, settings }: CustomHyperli }, [href, enableShellApplication_analyticalDesigner, enableShellApplication_dashboards]); if (!parsedRef) { - return text; + return plainText; } const handleLinkClick = (e: MouseEvent) => { @@ -93,25 +101,25 @@ export function CustomHyperlinkComponent({ href, text, settings }: CustomHyperli // If the link is for a metric and the user cannot manage metrics, // we do not render the link if (parsedRef.type === "metric" && !canManageMetrics) { - return text; + return plainText; } // If the link is for a visualization and the user cannot manage visualizations, // we do not render the link if (parsedRef.type === "visualization" && !canManageVisualisations) { - return text; + return plainText; } if (allowNativeLinks) { return ( - {text} + {plainText} ); } return ( - {text} + {plainText} ); } diff --git a/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.test.tsx b/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.test.tsx index e5a5d12c894..b915b3cc597 100644 --- a/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.test.tsx +++ b/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.test.tsx @@ -7,29 +7,63 @@ import { render } from "@testing-library/react"; import { Provider } from "react-redux"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { type IInsight, type ISeparators, idRef, newBucket, newMeasure } from "@gooddata/sdk-model"; +import { + type IInsight, + type ISeparators, + idRef, + newAttribute, + newBucket, + newMeasure, +} from "@gooddata/sdk-model"; import { IntlWrapper } from "../../../localization/IntlWrapper.js"; import { type IChatConversationLocalItem } from "../../../model.js"; import { ConversationVisualisation } from "./ConversationVisualisation.js"; +type RecordedProps = { + config?: { + separators?: ISeparators; + total?: unknown; + legend?: unknown; + stackMeasures?: unknown; + stackMeasuresToPercent?: unknown; + }; + measures?: unknown[]; + viewBy?: unknown; + stackBy?: unknown; + trendBy?: unknown; + segmentBy?: unknown; + measure?: unknown; + rows?: unknown; + columns?: unknown; +}; + const rendered: { component: string; separators: ISeparators | undefined }[] = []; +const lastProps = new Map(); function recorder(component: string) { - return function Recorder({ config }: { config?: { separators?: ISeparators } }) { - rendered.push({ component, separators: config?.separators }); + return function Recorder(props: RecordedProps) { + rendered.push({ component, separators: props.config?.separators }); + lastProps.set(component, props); return null; }; } vi.mock("@gooddata/sdk-ui-charts", () => ({ + AreaChart: recorder("AreaChart"), BarChart: recorder("BarChart"), ColumnChart: recorder("ColumnChart"), LineChart: recorder("LineChart"), PieChart: recorder("PieChart"), + DonutChart: recorder("DonutChart"), + PyramidChart: recorder("PyramidChart"), + FunnelChart: recorder("FunnelChart"), + RadarChart: recorder("RadarChart"), ScatterPlot: recorder("ScatterPlot"), Headline: recorder("Headline"), + Heatmap: recorder("Heatmap"), + WaterfallChart: recorder("WaterfallChart"), })); vi.mock("@gooddata/sdk-ui-pivot", () => ({ @@ -81,13 +115,16 @@ function visualizationOf(visualizationUrl: string): IInsight { function renderVisualisation( visualizationUrl: string, props: { isTable?: boolean; enableNewPivotTable?: boolean } = {}, + insight: Partial = {}, ) { + const base = visualizationOf(visualizationUrl); + return render( ({}) })}> @@ -99,15 +136,23 @@ function renderVisualisation( describe("ConversationVisualisation", () => { beforeEach(() => { rendered.length = 0; + lastProps.clear(); }); it.each([ + ["local:area", "AreaChart"], ["local:bar", "BarChart"], ["local:column", "ColumnChart"], ["local:line", "LineChart"], ["local:pie", "PieChart"], + ["local:donut", "DonutChart"], + ["local:pyramid", "PyramidChart"], + ["local:funnel", "FunnelChart"], + ["local:radar", "RadarChart"], ["local:scatter", "ScatterPlot"], ["local:headline", "Headline"], + ["local:waterfall", "WaterfallChart"], + ["local:heatmap", "Heatmap"], ["local:table", "PivotTableNext"], ])("passes the workspace separators to %s", (visualizationUrl, component) => { renderVisualisation(visualizationUrl); @@ -126,4 +171,131 @@ describe("ConversationVisualisation", () => { expect(rendered).toEqual([{ component: "PivotTable", separators }]); }); + + it.each([ + ["local:pie", "PieChart"], + ["local:donut", "DonutChart"], + ["local:pyramid", "PyramidChart"], + ["local:funnel", "FunnelChart"], + ])("lets the legend of %s collapse into a popup in the narrow chat panel", (url, component) => { + renderVisualisation(url); + + expect(lastProps.get(component)?.config?.legend).toEqual({ + responsive: "autoPositionWithPopup", + }); + }); + + it("leaves the area stacking to the chart when the insight does not configure it", () => { + renderVisualisation("local:area"); + + expect(lastProps.get("AreaChart")?.config).toMatchObject({ + stackMeasures: undefined, + stackMeasuresToPercent: undefined, + }); + }); + + it("carries over the area stacking the insight configures", () => { + renderVisualisation( + "local:area", + {}, + { properties: { controls: { stackMeasures: true, stackMeasuresToPercent: true } } }, + ); + + expect(lastProps.get("AreaChart")?.config).toMatchObject({ + stackMeasures: true, + stackMeasuresToPercent: true, + }); + }); + + it("maps the radar trend bucket to trendBy and the segment bucket to segmentBy", () => { + const trendAttribute = newAttribute("category"); + const segmentAttribute = newAttribute("campaign"); + + renderVisualisation( + "local:radar", + {}, + { + buckets: [ + newBucket("measures", newMeasure("m1")), + newBucket("trend", trendAttribute), + newBucket("segment", segmentAttribute), + ], + }, + ); + + expect(lastProps.get("RadarChart")).toMatchObject({ + measures: [newMeasure("m1")], + trendBy: trendAttribute, + segmentBy: segmentAttribute, + }); + }); + + it("renders a radar of several measures with neither trend nor segment", () => { + renderVisualisation( + "local:radar", + {}, + { buckets: [newBucket("measures", newMeasure("m1"), newMeasure("m2"))] }, + ); + + expect(lastProps.get("RadarChart")).toMatchObject({ + measures: [newMeasure("m1"), newMeasure("m2")], + trendBy: undefined, + segmentBy: undefined, + }); + }); + + it("renders a waterfall broken down by an attribute and carries over its total config", () => { + const viewAttribute = newAttribute("category"); + + renderVisualisation( + "local:waterfall", + {}, + { + buckets: [newBucket("measures", newMeasure("m1")), newBucket("view", viewAttribute)], + properties: { controls: { total: { name: "Total" } } }, + }, + ); + + expect(lastProps.get("WaterfallChart")).toMatchObject({ + measures: [newMeasure("m1")], + viewBy: viewAttribute, + config: { total: { name: "Total" } }, + }); + }); + + it("maps the heatmap view bucket to rows and the stack bucket to columns", () => { + const rowAttribute = newAttribute("type"); + const columnAttribute = newAttribute("campaign"); + + renderVisualisation( + "local:heatmap", + {}, + { + buckets: [ + newBucket("measures", newMeasure("m1")), + newBucket("view", rowAttribute), + newBucket("stack", columnAttribute), + ], + }, + ); + + expect(lastProps.get("Heatmap")).toMatchObject({ + measure: newMeasure("m1"), + rows: rowAttribute, + columns: columnAttribute, + }); + }); + + it("renders a waterfall of several measures with no viewBy", () => { + renderVisualisation( + "local:waterfall", + {}, + { buckets: [newBucket("measures", newMeasure("m1"), newMeasure("m2"))] }, + ); + + expect(lastProps.get("WaterfallChart")).toMatchObject({ + measures: [newMeasure("m1"), newMeasure("m2")], + viewBy: undefined, + }); + }); }); diff --git a/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.tsx b/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.tsx index 2c1ba6575ea..9bf13eb595b 100644 --- a/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.tsx +++ b/libs/sdk-ui-gen-ai/src/components/messages/conversationContents/ConversationVisualisation.tsx @@ -27,7 +27,22 @@ import { type OnExportReady, type OnFiredDrillEvent, } from "@gooddata/sdk-ui"; -import { BarChart, ColumnChart, Headline, LineChart, PieChart, ScatterPlot } from "@gooddata/sdk-ui-charts"; +import { + AreaChart, + BarChart, + ColumnChart, + DonutChart, + FunnelChart, + Headline, + Heatmap, + type ITotalConfig, + LineChart, + PieChart, + PyramidChart, + RadarChart, + ScatterPlot, + WaterfallChart, +} from "@gooddata/sdk-ui-charts"; import { type IDashboardKeyDriverCombinationItem, getKdaKeyDriverCombinations, @@ -73,6 +88,13 @@ const legendTooltipOptions = { }, }; +const SLICED_CHARTS: Record = { + "local:pie": PieChart, + "local:donut": DonutChart, + "local:pyramid": PyramidChart, + "local:funnel": FunnelChart, +}; + export type ConversationVisualisationProps = { message: IChatConversationLocalItem; visualization: NonNullable; @@ -233,6 +255,24 @@ export function ConversationVisualisation({ } switch (visualization.insight.visualizationUrl) { + case "local:area": + return renderAreaChart( + intl.locale, + visualization, + bucketsData, + filters, + sorts, + colorPalette, + handleSdkError, + handleSuccess, + handlerDrill, + { + drillableItems, + enableAccessibleChartTooltip, + execConfig, + separators, + }, + ); case "local:bar": return renderBarChart( intl.locale, @@ -288,7 +328,11 @@ export function ConversationVisualisation({ }, ); case "local:pie": - return renderPieChart( + case "local:donut": + case "local:pyramid": + case "local:funnel": + return renderSlicedChart( + SLICED_CHARTS[visualization.insight.visualizationUrl], intl.locale, bucketsData, filters, @@ -342,6 +386,58 @@ export function ConversationVisualisation({ separators, }, ); + case "local:radar": + return renderRadarChart( + intl.locale, + bucketsData, + filters, + sorts, + colorPalette, + handleSdkError, + handleSuccess, + handlerDrill, + { + drillableItems, + enableAccessibleChartTooltip, + execConfig, + separators, + }, + ); + case "local:heatmap": + return renderHeatmap( + intl.locale, + bucketsData, + filters, + sorts, + colorPalette, + handleSdkError, + handleSuccess, + handlerDrill, + { + drillableItems, + enableAccessibleChartTooltip, + execConfig, + separators, + }, + ); + case "local:waterfall": + return renderWaterfallChart( + intl.locale, + visualization, + bucketsData, + filters, + sorts, + colorPalette, + handleSdkError, + handleSuccess, + handlerDrill, + { + drillableItems, + enableAccessibleChartTooltip, + execConfig, + separators, + }, + ); case "local:headline": return renderHeadline( intl.locale, @@ -385,6 +481,53 @@ function useKpiTheme() { ); } +const renderAreaChart = ( + locale: string, + visualization: NonNullable, + buckets: ReturnType, + filters: IFilter[], + sortBy: ISortItem[], + colorPalette: IColorPalette | undefined, + onError: OnError, + onSuccess: OnExportReady, + onDrill: OnFiredDrillEvent, + props: { + drillableItems?: ExplicitDrill[]; + enableAccessibleChartTooltip?: boolean; + execConfig?: IExecutionConfig; + separators?: ISeparators; + }, +) => { + const { metrics, view, stack } = buckets; + const controls = visualization.insight.properties["controls"]; + + return ( + + ); +}; + const renderBarChart = ( locale: string, buckets: ReturnType, @@ -538,7 +681,8 @@ const renderLineChart = ( ); }; -const renderPieChart = ( +const renderSlicedChart = ( + Chart: typeof PieChart, locale: string, buckets: ReturnType, filters: IFilter[], @@ -558,7 +702,7 @@ const renderPieChart = ( const { metrics, view } = buckets; return ( - , + filters: IFilter[], + sortBy: ISortItem[], + colorPalette: IColorPalette | undefined, + onError: OnError, + onSuccess: OnExportReady, + onDrill: OnFiredDrillEvent, + props: { + drillableItems?: ExplicitDrill[]; + enableAccessibleChartTooltip?: boolean; + execConfig?: IExecutionConfig; + separators?: ISeparators; + }, +) => { + const { metrics, trend, segment } = buckets; + + return ( + + ); +}; + +const renderHeatmap = ( + locale: string, + buckets: ReturnType, + filters: IFilter[], + sortBy: ISortItem[], + colorPalette: IColorPalette | undefined, + onError: OnError, + onSuccess: OnExportReady, + onDrill: OnFiredDrillEvent, + props: { + drillableItems?: ExplicitDrill[]; + enableAccessibleChartTooltip?: boolean; + execConfig?: IExecutionConfig; + separators?: ISeparators; + }, +) => { + const { metrics, view, stack } = buckets; + + return ( + + ); +}; + +const renderWaterfallChart = ( + locale: string, + visualization: NonNullable, + buckets: ReturnType, + filters: IFilter[], + sortBy: ISortItem[], + colorPalette: IColorPalette | undefined, + onError: OnError, + onSuccess: OnExportReady, + onDrill: OnFiredDrillEvent, + props: { + drillableItems?: ExplicitDrill[]; + enableAccessibleChartTooltip?: boolean; + execConfig?: IExecutionConfig; + separators?: ISeparators; + }, +) => { + const { metrics, view } = buckets; + const total = visualization.insight.properties["controls"]?.["total"] as ITotalConfig | undefined; + + return ( + + ); +}; + const renderTable = ( locale: string, buckets: ReturnType, diff --git a/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.test.ts b/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.test.ts index 7c1c82c62c3..45426da7799 100644 --- a/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.test.ts +++ b/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.test.ts @@ -77,6 +77,13 @@ describe("removeMarkdown", () => { expect(removeMarkdown("Word1 Word2")).toBe("Word1 Word2"); }); + it("should unescape markdown escaped characters", () => { + expect(removeMarkdown("http://www\\.seznam\\.cz")).toBe("http://www.seznam.cz"); + expect(removeMarkdown("Escaped \\#header and \\_value\\_ and \\\\ slash")).toBe( + "Escaped #header and _value_ and \\ slash", + ); + }); + it("should handle complex markdown", () => { const input = ` # Title diff --git a/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.ts b/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.ts index 9daecdf73e2..eae9e70d039 100644 --- a/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.ts +++ b/libs/sdk-ui-gen-ai/src/components/utils/markdownUtils.ts @@ -1,6 +1,7 @@ // (C) 2025-2026 GoodData Corporation const MARKDOWN_ESCAPE_PATTERN = /(\{[^{}]*\})|[\\`*_{}[\]()#+\-.!]/g; +const MARKDOWN_UNESCAPE_PATTERN = /\\([\\`*_{}[\]()#+\-.!])/g; export function escapeMarkdown(text: string): string { return text.replace(MARKDOWN_ESCAPE_PATTERN, (match, inBraces: string) => inBraces || `\\${match}`); @@ -11,14 +12,15 @@ export function escapeMarkdown(text: string): string { */ export function removeMarkdown(text: string): string { return text - .replace(/(\*\*|__)(.*?)\1/g, "$2") // bold - .replace(/(\*|_)(.*?)\1/g, "$2") // italic + .replace(/(?\s+(.*)/gm, "$1") // blockquotes .replace(/^\s*[-*+]\s+(.*)/gm, "$1") // unordered lists .replace(/^\s*\d+\.\s+(.*)/gm, "$1") // ordered lists + .replace(MARKDOWN_UNESCAPE_PATTERN, "$1") // unescape markdown escaped characters .replace(/(\r\n|\n|\r)/gm, " ") // replace newlines with space .replace(/ {2,}/g, " ") // remove double spaces .trim(); diff --git a/libs/sdk-ui-gen-ai/src/context.ts b/libs/sdk-ui-gen-ai/src/context.ts index 60b11511806..cd47420a942 100644 --- a/libs/sdk-ui-gen-ai/src/context.ts +++ b/libs/sdk-ui-gen-ai/src/context.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files import { mergeContexts } from "./context/build.js"; import { diff --git a/libs/sdk-ui-gen-ai/src/index.ts b/libs/sdk-ui-gen-ai/src/index.ts index 8f2bfe2b1ce..80253a2110d 100644 --- a/libs/sdk-ui-gen-ai/src/index.ts +++ b/libs/sdk-ui-gen-ai/src/index.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides React components for GoodData's GenAI features. diff --git a/libs/sdk-ui-gen-ai/src/internal.ts b/libs/sdk-ui-gen-ai/src/internal.ts index 18d65f4afb7..b63743d855e 100644 --- a/libs/sdk-ui-gen-ai/src/internal.ts +++ b/libs/sdk-ui-gen-ai/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files import { makeTextContents, makeUserMessage } from "./model.js"; import { diff --git a/libs/sdk-ui-gen-ai/src/localization/bundles/zh-HK.json b/libs/sdk-ui-gen-ai/src/localization/bundles/zh-HK.json index 58d50bce041..42a2bcd681f 100644 --- a/libs/sdk-ui-gen-ai/src/localization/bundles/zh-HK.json +++ b/libs/sdk-ui-gen-ai/src/localization/bundles/zh-HK.json @@ -25,8 +25,8 @@ "gd.gen-ai.visualization.error.data_too_large": "資料過大。 請套用篩選器以減少資料。", "gd.gen-ai.visualization.unavailable": "無法顯示可視化。", "gd.gen-ai.permission-error": "權限不足", - "gd.gen-ai.permission-error.description": "你無權訪問聊天機械人。 請與您的管理員聯繫以請求訪問權限。", - "gd.gen-ai.input-placeholder": "喺呢度提問…", + "gd.gen-ai.permission-error.description": "您無權訪問聊天機械人。 請與您的管理員聯繫以請求訪問權限。", + "gd.gen-ai.input-placeholder": "在此提問…", "gd.gen-ai.input-label.mac": "提出您的問題。 使用control+space或cmd+I獲取建議。", "gd.gen-ai.input-label.win": "提出您的問題。 使用control+space或control+I獲取建議。", "gd.gen-ai.context.add_context": "新增上下文", @@ -67,18 +67,18 @@ "gd.gen-ai.reasoning.thinking.description": "為複雜分析問題提供較慢但更詳盡的解答。", "gd.gen-ai.feedback.like": "將此消息標記為有用", "gd.gen-ai.feedback.dislike": "將此消息標記為無用", - "gd.gen-ai.feedback.popup.title": "我哋收到你哋嘅意見", - "gd.gen-ai.feedback.popup.reasons-label": "你話我哋知乜嘢出咗問題", - "gd.gen-ai.feedback.popup.description-label": "唔該你講下發生咗乜嘢事", + "gd.gen-ai.feedback.popup.title": "我們已收到您的意見", + "gd.gen-ai.feedback.popup.reasons-label": "告訴我們出了什麼問題", + "gd.gen-ai.feedback.popup.description-label": "請描述發生了什麼事", "gd.gen-ai.feedback.popup.cancel": "取消", "gd.gen-ai.feedback.popup.send": "發送", "gd.gen-ai.feedback.popup.close": "關閉反饋對話框", - "gd.gen-ai.feedback.reason.forgot-context": "唔記得之前嘅上下文", + "gd.gen-ai.feedback.reason.forgot-context": "忘記了先前的上下文", "gd.gen-ai.feedback.reason.ignored-instructions": "無視或拒絕指示", "gd.gen-ai.feedback.reason.factually-incorrect": "事實錯誤", "gd.gen-ai.feedback.reason.answer-too-long": "答案需時過長", "gd.gen-ai.feedback.reason.other": "其他", - "gd.gen-ai.disclaimer": "AI助手可能會犯錯誤。 在依賴呢啲答案之前檢查。", + "gd.gen-ai.disclaimer": "AI助手可能會犯錯。 在依賴這些答案之前請先檢查。", "gd.gen-ai.dialog.label": "AI助手對話", "gd.gen-ai.header.reset-tooltip": "重置", "gd.gen-ai.header.conversations-tooltip": "最近對話", @@ -86,14 +86,14 @@ "gd.gen-ai.header.contract-tooltip": "收縮", "gd.gen-ai.header.expand-tooltip": "擴大", "gd.gen-ai.header.close-tooltip": "關閉助手", - "gd.gen-ai.welcome.line-1": "嘿,你好", - "gd.gen-ai.welcome.line-2": "我該點樣幫助你法?", + "gd.gen-ai.welcome.line-1": "嘿,您好", + "gd.gen-ai.welcome.line-2": "我該怎樣幫助您?", "gd.gen-ai.welcome.option-1.title": "搜索儀表板或可視化", - "gd.gen-ai.welcome.option-1.answer": "當然,你希望睇到点样嘅儀錶板或可視化效果?", + "gd.gen-ai.welcome.option-1.answer": "當然,您想看到什麼樣的儀表板或可視化?", "gd.gen-ai.welcome.option-2.title": "創建新的可視化", "gd.gen-ai.welcome.option-2.answer": "當然,您想創建点样的可視化效果?", "gd.gen-ai.welcome.option-3.title": "答業務問題", - "gd.gen-ai.welcome.option-3.answer": "當然,您想答乜嘢商業問題?", + "gd.gen-ai.welcome.option-3.answer": "當然,您想回答什麼商業問題?", "gd.gen-ai.semantic-search.title": "搜尋", "gd.gen-ai.alert-proposal.title": "通知", "gd.gen-ai.alert-proposal.summary.title": "名字", @@ -177,12 +177,12 @@ "gd.gen-ai.save-dialog.label": "名字", "gd.gen-ai.save-dialog.title": "為可視化效果命名", "gd.gen-ai.save-dialog.description": "為新的可視化效果指定一個新的描述性名稱,方便日後容易找到。", - "gd.gen-ai.save-dialog.description.explore": "要打開此可視化,需要保存它先。 你可以喺下面為其命名。", + "gd.gen-ai.save-dialog.description.explore": "若要開啟此可視化,必須先儲存。 您可以在下方為其命名。", "gd.gen-ai.autocomplete.type": "類型", "gd.gen-ai.autocomplete.dataset": "數據", "gd.gen-ai.autocomplete.id": "ID", "gd.gen-ai.autocomplete.attribute": "屬性", - "gd.gen-ai.autocomplete.metric": "計量", + "gd.gen-ai.autocomplete.metric": "指標", "gd.gen-ai.autocomplete.fact": "事實", "gd.gen-ai.autocomplete.date": "數據", "gd.gen-ai.autocomplete.insight": "可視化", @@ -196,7 +196,7 @@ "gd.gen-ai.visualisation.menu.button.save_as_visualisation": "保存可視化效果", "gd.gen-ai.visualisation.menu.button.save_as_new_visualisation": "另存為新...", "gd.gen-ai.visualisation.menu.button.open_in_analyze": "在Analyze中打開", - "gd.gen-ai.visualisation.menu.button.open_in_analyze.description": "這會要求你先儲存視覺化,然後才能打開它。", + "gd.gen-ai.visualisation.menu.button.open_in_analyze.description": "這會要求您先儲存視覺化,然後才能打開它。", "gd.gen-ai.visualisation.menu.button.copy_visualisation_link": "複製可視化效果link", "gd.gen-ai.messages.label": "聊天消息", "gd.gen-ai.message.label.user": "用戶:", @@ -206,8 +206,8 @@ "gd.gen-ai.message.reasoning.tool-result": "正在從工具收集資料", "gd.gen-ai.message.reasoned.tool-result": "已從工具收集資料", "gd.gen-ai.skip-messages-history": "跳到AI助手輸入", - "gd.gen-ai.skip-navigation": "跳过導航", - "gd.gen-ai.changeAnalysis.default_message": "呢度係 {title}{range} 之間嘅關鍵驅動因素分析。", + "gd.gen-ai.skip-navigation": "跳過導航", + "gd.gen-ai.changeAnalysis.default_message": "以下是 {title}{range} 之間的關鍵驅動因素分析。", "gd.gen-ai.kda.default_message": "以下是 **{title}** 在 **{range}** 期間的關鍵驅動因素分析。", "gd.gen-ai.changeAnalysis.splitter": "和", "gd.gen-ai.changeAnalysis.explain_the_change": "開分析", @@ -252,7 +252,7 @@ "gd.gen-ai.interactionIntelligence.detail.searched": "已搜尋", "gd.gen-ai.interactionIntelligence.detail.found": "找到", "gd.gen-ai.interactionIntelligence.detail.used": "已使用", - "gd.gen-ai.interactionIntelligence.detail.metrics": "計量", + "gd.gen-ai.interactionIntelligence.detail.metrics": "指標", "gd.gen-ai.interactionIntelligence.detail.groupedBy": "分組依據", "gd.gen-ai.interactionIntelligence.detail.filteredBy": "篩選依據", "gd.gen-ai.interactionIntelligence.detail.visualization": "可視化", @@ -273,7 +273,7 @@ "gd.gen-ai.interactionIntelligence.detail.output.searchResults": "搜尋結果", "gd.gen-ai.interactionIntelligence.detail.output.alertProposal": "警示建議", "drill.kda.from": "從", - "drill.kda.in": "係", + "drill.kda.in": "在", "drill.kda.drop": "下降 {where} {title}", "drill.kda.increase": "增加 {where} {title}", "drill.kda.no_change": "無變化 {where} {title}", diff --git a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSelectors.ts b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSelectors.ts index ef94ffb32d0..3be7d5d985c 100644 --- a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSelectors.ts +++ b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSelectors.ts @@ -130,6 +130,11 @@ export const contextObjectsSearchSelector: (state: RootState) => string = create (state) => state.contextObjectsSearch, ); +export const inputValueSelector: (state: RootState) => string = createSelector( + chatWindowSliceSelector, + (state) => state.inputValue, +); + export const keyDriverAnalysisSelector: (state: RootState) => IKdaDefinition | undefined = createSelector( chatWindowSliceSelector, (state) => state.keyDriverAnalysis, diff --git a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.test.ts b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.test.ts index 6753b901070..ccfbad3a676 100644 --- a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.test.ts +++ b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.test.ts @@ -13,6 +13,8 @@ import { selectedContextReferencesAction, setAmbientUserContextAction, setCatalogItemsActions, + setInputValueAction, + setOpenAction, setUserContextAction, } from "./chatWindowSlice.js"; @@ -158,6 +160,38 @@ describe.each([ }); }); +describe("input value state", () => { + it("should preserve input value across unrelated actions", () => { + const state = stateWith( + contextSetupOn, + setInputValueAction({ value: "Revenue by product" }), + setOpenAction({ isOpen: true }), + ); + + expect(state.inputValue).toBe("Revenue by product"); + }); + + it("should clear input value on startNewConversationAction", () => { + const state = stateWith( + contextSetupOn, + setInputValueAction({ value: "Revenue by product" }), + startNewConversationAction(), + ); + + expect(state.inputValue).toBe(""); + }); + + it("should not clear input value on clearThreadAction", () => { + const state = stateWith( + contextSetupOn, + setInputValueAction({ value: "Revenue by product" }), + clearThreadAction(), + ); + + expect(state.inputValue).toBe(""); + }); +}); + describe("selectedContextReferencesAction", () => { it("should update selected context and update references in the store context", () => { const state = stateWith( diff --git a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.ts b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.ts index 9e4617302ba..1c690e0e3f9 100644 --- a/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.ts +++ b/libs/sdk-ui-gen-ai/src/store/chatWindow/chatWindowSlice.ts @@ -96,6 +96,10 @@ type ChatWindowSliceState = { * Context related to the chat. */ context: StoreContext; + /** + * Current unsent input value. + */ + inputValue: string; /** * Whether the chat runs against the caller's preview agent. In preview mode the assistant * is pinned to that single agent, so agent switching is not applicable. @@ -143,6 +147,7 @@ const initialState: ChatWindowSliceState = { ambient: undefined, active: undefined, }, + inputValue: "", isPreview: undefined, allowInteractionIntelligence: undefined, }; @@ -222,6 +227,9 @@ const chatWindowSlice = createSlice({ setCatalogItemsActions: (state, { payload }: PayloadAction) => { state.catalogItems = castDraft(payload); }, + setInputValueAction: (state, { payload: { value } }: PayloadAction<{ value: string }>) => { + state.inputValue = value; + }, initContextObjectsAction: (state) => state, loadContextObjectsNextPageAction: (state, _action: PayloadAction<{ kind: ContextObjectKind }>) => state, @@ -368,8 +376,14 @@ const chatWindowSlice = createSlice({ }; builder - .addCase(startNewConversationAction, resetContextToAmbient) - .addCase(clearThreadAction, resetContextToAmbient); + .addCase(startNewConversationAction, (state) => { + resetContextToAmbient(state); + state.inputValue = ""; + }) + .addCase(clearThreadAction, (state) => { + resetContextToAmbient(state); + state.inputValue = ""; + }); }, }); @@ -385,6 +399,7 @@ export const { setObjectTypesAction, setTagsAction, setCatalogItemsActions, + setInputValueAction, initContextObjectsAction, loadContextObjectsNextPageAction, setContextObjectsAction, diff --git a/libs/sdk-ui-gen-ai/src/store/localStorage.ts b/libs/sdk-ui-gen-ai/src/store/localStorage.ts index 9edbd7e6ea3..cbe7dbe7e49 100644 --- a/libs/sdk-ui-gen-ai/src/store/localStorage.ts +++ b/libs/sdk-ui-gen-ai/src/store/localStorage.ts @@ -4,6 +4,7 @@ import type { Message } from "../model.js"; const OPENED_KEY = "gd-gen-ai-is-opened"; const MESSAGES_KEY_PREFIX = "gd-gen-ai-messages"; +const LAST_ACTIVE_KEY_PREFIX = "gd-gen-ai-last-active"; export function getIsOpened() { if (typeof localStorage !== "undefined") { @@ -71,3 +72,28 @@ export function clearCachedMessages(workspaceId: string): void { // Fail silently } } + +function getLastActiveKey(workspaceId: string): string { + return `${LAST_ACTIVE_KEY_PREFIX}-${workspaceId}`; +} + +export function getLastActiveAt(workspaceId: string): number | undefined { + try { + const raw = globalThis.localStorage?.getItem(getLastActiveKey(workspaceId)); + if (!raw) { + return undefined; + } + const parsed = Number(raw); + return Number.isFinite(parsed) ? parsed : undefined; + } catch { + return undefined; + } +} + +export function setLastActiveAt(workspaceId: string, timestamp: number): void { + try { + globalThis.localStorage?.setItem(getLastActiveKey(workspaceId), String(timestamp)); + } catch { + // Fail silently + } +} diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.test.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.test.ts new file mode 100644 index 00000000000..1cfc242b107 --- /dev/null +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.test.ts @@ -0,0 +1,88 @@ +// (C) 2026 GoodData Corporation + +import { beforeEach, describe, expect, it } from "vitest"; + +import { settingsSelector } from "../chatWindow/chatWindowSelectors.js"; +import { getLastActiveAt } from "../localStorage.js"; + +import { + CONVERSATION_SESSION_TIMEOUT_MS, + consumeStaleConversationSession, + isConversationSessionStale, + touchConversationSession, +} from "./conversationSession.js"; + +const WORKSPACE = "ws-1"; +const NOW = 1_800_000_000_000; +const MULTI_SETTINGS = { + enableAiAgenticConversations: true, + enableAiAgenticMultiConversations: true, +}; + +function runConsume(settings: unknown): boolean { + const gen = consumeStaleConversationSession(); + const selectors: unknown[] = []; + let next = gen.next(); + while (!next.done) { + const effect = next.value as { type: string; payload: any }; + if (effect.type === "SELECT") { + selectors.push(effect.payload.selector); + next = gen.next(settings); + } else if (effect.type === "GET_CONTEXT") { + next = gen.next(WORKSPACE); + } else { + next = gen.next(); + } + } + expect(selectors).toEqual([settingsSelector]); + return next.value; +} + +describe("conversation session staleness", () => { + beforeEach(() => { + localStorage.clear(); + }); + + describe("isConversationSessionStale", () => { + it("is stale once the inactivity window elapsed", () => { + touchConversationSession(WORKSPACE, NOW - CONVERSATION_SESSION_TIMEOUT_MS - 1); + + expect(isConversationSessionStale(WORKSPACE, NOW)).toBe(true); + }); + + it("is not stale within the inactivity window", () => { + touchConversationSession(WORKSPACE, NOW - CONVERSATION_SESSION_TIMEOUT_MS + 1); + + expect(isConversationSessionStale(WORKSPACE, NOW)).toBe(false); + }); + + it("is not stale when no timestamp is stored, to keep the previous resume behaviour", () => { + expect(isConversationSessionStale(WORKSPACE, NOW)).toBe(false); + }); + + it("tracks workspaces separately", () => { + touchConversationSession("ws-other", NOW - CONVERSATION_SESSION_TIMEOUT_MS - 1); + + expect(isConversationSessionStale(WORKSPACE, NOW)).toBe(false); + }); + }); + + describe("consumeStaleConversationSession", () => { + const longAgo = () => Date.now() - CONVERSATION_SESSION_TIMEOUT_MS - 1; + + it("reports a stale session and refreshes the timer, so a repeated open agrees", () => { + touchConversationSession(WORKSPACE, longAgo()); + + expect(runConsume(MULTI_SETTINGS)).toBe(true); + expect(runConsume(MULTI_SETTINGS)).toBe(false); + }); + + it("never reports stale outside the multi-conversation flow", () => { + const stored = longAgo(); + touchConversationSession(WORKSPACE, stored); + + expect(runConsume({ enableAiAgenticConversations: true })).toBe(false); + expect(getLastActiveAt(WORKSPACE)).toBe(stored); + }); + }); +}); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.ts new file mode 100644 index 00000000000..2d0c389b7c0 --- /dev/null +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/conversationSession.ts @@ -0,0 +1,53 @@ +// (C) 2026 GoodData Corporation + +import { getContext, select } from "redux-saga/effects"; + +import { type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi"; + +import { settingsSelector } from "../chatWindow/chatWindowSelectors.js"; +import { getLastActiveAt, setLastActiveAt } from "../localStorage.js"; + +/** + * How long the assistant may sit unused before the next open counts as a new session. + * @internal + */ +export const CONVERSATION_SESSION_TIMEOUT_MS = 8 * 60 * 60 * 1000; + +/** + * Whether the next open should start a fresh conversation instead of resuming the last one. + * A missing timestamp keeps the previous resume behaviour. + * @internal + */ +export function isConversationSessionStale(workspaceId: string, now: number = Date.now()): boolean { + const lastActiveAt = getLastActiveAt(workspaceId); + + return lastActiveAt !== undefined && now - lastActiveAt > CONVERSATION_SESSION_TIMEOUT_MS; +} + +/** + * @internal + */ +export function touchConversationSession(workspaceId: string, now: number = Date.now()): void { + setLastActiveAt(workspaceId, now); +} + +/** Refresh the session timer on assistant interaction (not on open). @internal */ +export function* onConversationActivity() { + const workspace: string = yield getContext("workspace"); + + touchConversationSession(workspace); +} + +/** Read staleness and refresh the timer on open. Only one open path calls this per opening. @internal */ +export function* consumeStaleConversationSession(): Generator { + const settings: IUserWorkspaceSettings | undefined = yield select(settingsSelector); + if (!settings?.enableAiAgenticConversations || !settings?.enableAiAgenticMultiConversations) { + return false; + } + + const workspace: string = yield getContext("workspace"); + const stale = isConversationSessionStale(workspace); + touchConversationSession(workspace); + + return stale; +} diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts index 2bdf3270043..9bffaeef22c 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/index.ts @@ -11,6 +11,7 @@ import { import { clearThreadAction, deleteConversationAction, + evaluateMessageCompleteAction, evaluateMessageUpdateAction, loadThreadAction, newMessageAction, @@ -19,10 +20,12 @@ import { saveVisualisationRenderStatusAction, saveVisualizationAction, saveVisualizationSuccessAction, + setCurrentConversationAction, setUserFeedback, setVerboseAction, } from "../messages/messagesSlice.js"; +import { onConversationActivity } from "./conversationSession.js"; import { loadAgents } from "./loadAgents.js"; import { loadCatalogItems } from "./loadCatalogItems.js"; import { loadColorPalette } from "./loadColorPalette.js"; @@ -74,6 +77,10 @@ export function* rootSaga() { yield takeLatest(setContextObjectsSearchAction.type, reloadContextObjects); //others yield takeEvery(setVerboseAction.type, onVerboseStore); + yield takeEvery( + [newMessageAction.type, evaluateMessageCompleteAction.type, setCurrentConversationAction.type], + onConversationActivity, + ); yield fork(onEvent); yield call(loadColorPalette); yield call(loadSettings); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.test.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.test.ts index 1c754d1b28f..9df2c1922af 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.test.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.test.ts @@ -9,23 +9,31 @@ import { asyncProcessSelector, conversationMessagesSelector, conversationSelector, - conversationsLoadedSelector, + loadedSelector, } from "../messages/messagesSelectors.js"; -import { setCurrentConversationAction, setMessagesAction } from "../messages/messagesSlice.js"; +import { + setCurrentConversationAction, + setMessagesAction, + startNewConversationAction, +} from "../messages/messagesSlice.js"; +import { consumeStaleConversationSession } from "./conversationSession.js"; import { onChatOpenSync } from "./onChatOpenSync.js"; // Drives the saga generator without redux-saga's runtime: SELECT effects are answered from a // per-selector queue (so a selector sampled twice can return different values across the await), -// CALL effects are answered in order from `callResults`, and PUT actions are collected. +// CALL effects are answered in order from `callResults`, and PUT actions are collected. The +// session-staleness CALL is matched by reference, so it stays out of the positional queue. function runOnChatOpenSync({ isOpen, selects, callResults, + staleSession = false, }: { isOpen: boolean; selects: Map; callResults: unknown[]; + staleSession?: boolean; }): PayloadAction[] { // Loosely typed so the manual driver can feed SELECT/CALL results into next() regardless of the // saga's inferred effect types. @@ -49,6 +57,10 @@ function runOnChatOpenSync({ } next = gen.next(queue.shift()); } else if (effect.type === "CALL") { + if (effect.payload.fn === consumeStaleConversationSession) { + next = gen.next(staleSession); + continue; + } const result = callResults[callIndex++]; if (result instanceof Error) { next = gen.throw(result); @@ -78,7 +90,7 @@ describe("onChatOpenSync", () => { callResults: [latest, [{ id: "i1", localId: "i1" }], undefined], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], // sampled twice: pre-fetch guard, then post-fetch re-check [asyncProcessSelector, [undefined, undefined]], // current (pre-fetch) then currentNow (post-fetch) — both the persisted conv-a @@ -99,7 +111,7 @@ describe("onChatOpenSync", () => { callResults: [latest, [{ localId: "i1" }]], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], // pre-fetch: previous persisted conversation; post-fetch: a fresh unsent draft (no id) [conversationSelector, [conv("conv-b"), conv("", "draft-1")]], @@ -123,7 +135,7 @@ describe("onChatOpenSync", () => { callResults: [current, items, undefined], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], [conversationSelector, [current, current]], [conversationMessagesSelector, [[{ id: "i1", localId: "i1" }]]], @@ -143,7 +155,7 @@ describe("onChatOpenSync", () => { callResults: [current, items], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], [conversationSelector, [current, current]], [conversationMessagesSelector, [[{ id: "i1", localId: "i1" }]]], @@ -162,7 +174,7 @@ describe("onChatOpenSync", () => { callResults: [latest, [{ id: "i1", localId: "i1" }]], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, "evaluating"]], // process started during fetch [conversationSelector, [conv("conv-a"), conv("conv-a")]], [conversationMessagesSelector, [[]]], @@ -178,7 +190,7 @@ describe("onChatOpenSync", () => { callResults: [new Error("Failed to fetch")], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], [conversationSelector, [conv("conv-a"), conv("conv-a")]], [conversationMessagesSelector, [[]]], @@ -196,7 +208,7 @@ describe("onChatOpenSync", () => { callResults: [current, items, undefined], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], [conversationSelector, [current, current]], [ @@ -215,6 +227,37 @@ describe("onChatOpenSync", () => { expect(types).toContain(setMessagesAction.type); }); + it("does not consume the session timer while onThreadLoad is still in flight (GDAI-2142)", () => { + const puts = runOnChatOpenSync({ + isOpen: true, + staleSession: true, + callResults: [], + selects: new Map([ + [settingsSelector, [AGENTIC_SETTINGS]], + [loadedSelector, [false]], + ]), + }); + + expect(puts).toHaveLength(0); + }); + + it("starts a new conversation instead of re-syncing when the session went stale (GDAI-2142)", () => { + const puts = runOnChatOpenSync({ + isOpen: true, + staleSession: true, + callResults: [], + selects: new Map([ + [settingsSelector, [AGENTIC_SETTINGS]], + [loadedSelector, [true]], + [asyncProcessSelector, [undefined]], + [conversationSelector, [conv("conv-a")]], + [conversationMessagesSelector, [[]]], + ]), + }); + + expect(puts.map((a) => a.type)).toEqual([startNewConversationAction.type]); + }); + it("updates messages when conversation switches even if item IDs match", () => { const current = conv("conv-a"); const latest = conv("conv-b"); @@ -224,7 +267,7 @@ describe("onChatOpenSync", () => { callResults: [latest, items, undefined], selects: new Map([ [settingsSelector, [AGENTIC_SETTINGS]], - [conversationsLoadedSelector, [true]], + [loadedSelector, [true]], [asyncProcessSelector, [undefined, undefined]], [conversationSelector, [current, current]], [conversationMessagesSelector, [[{ id: "i1", localId: "i1" }]]], diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.ts index b97e5ba3e2a..6d1aa03234f 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onChatOpenSync.ts @@ -20,14 +20,16 @@ import { asyncProcessSelector, conversationMessagesSelector, conversationSelector, - conversationsLoadedSelector, + loadedSelector, } from "../messages/messagesSelectors.js"; import { loadConversationsSuccessAction, setCurrentConversationAction, setMessagesAction, + startNewConversationAction, } from "../messages/messagesSlice.js"; +import { consumeStaleConversationSession } from "./conversationSession.js"; import { convertToLocalContent } from "./converters/toLocalContent.js"; import { notifyDefinitionReceived } from "./onDefinitionReceivedTrigger.js"; @@ -62,9 +64,9 @@ export function* onChatOpenSync({ payload: { isOpen } }: PayloadAction<{ isOpen: return; } - // The very first open is handled by the regular thread loading; only re-sync afterwards. - const conversationsLoaded: boolean = yield select(conversationsLoadedSelector); - if (!conversationsLoaded) { + // Initial load is owned by onThreadLoad; skip until it finishes to avoid a double stale check. + const loaded: boolean = yield select(loadedSelector); + if (!loaded) { return; } @@ -87,6 +89,15 @@ export function* onChatOpenSync({ payload: { isOpen } }: PayloadAction<{ isOpen: return; } + // Reopening after a long idle period starts a fresh conversation rather than re-syncing the + // previous one. Before the fetches: there is nothing to sync into a conversation + // we are about to leave behind. + const staleSession: boolean = yield call(consumeStaleConversationSession); + if (staleSession) { + yield put(startNewConversationAction()); + return; + } + try { const latest: IChatConversationLocal | undefined = yield call(fetchLatestConversation, current); diff --git a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts index 48eaf292a74..9762ed8b50e 100644 --- a/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts +++ b/libs/sdk-ui-gen-ai/src/store/sideEffects/onThreadLoad.ts @@ -42,6 +42,7 @@ import { } from "../messages/messagesSlice.js"; import { createEmptyConversation } from "../utils.js"; +import { consumeStaleConversationSession } from "./conversationSession.js"; import { interactionsToMessages } from "./converters/interactionsToMessages.js"; import { convertToLocalContent } from "./converters/toLocalContent.js"; import { notifyDefinitionReceived } from "./onDefinitionReceivedTrigger.js"; @@ -268,6 +269,19 @@ function* fetchCurrentConversation() { return; } + // Opening after a long idle period starts a fresh conversation instead of resuming the last + // one. Applies to an explicit pick too - selecting one refreshes the timer. + const staleSession: boolean = yield call(consumeStaleConversationSession); + if (staleSession) { + yield put( + loadConversationSuccessAction({ + currentConversation: createEmptyConversation(), + conversationItems: [], + }), + ); + return; + } + const api = backend.workspace(workspace).genAI().getChatConversations({ isPreview }); //Sort by createion date, because normally there are pinned on top diff --git a/libs/sdk-ui-geo/.dependency-cruiser.js b/libs/sdk-ui-geo/.dependency-cruiser.js index 26e29b5f91a..2510c0ea5a3 100644 --- a/libs/sdk-ui-geo/.dependency-cruiser.js +++ b/libs/sdk-ui-geo/.dependency-cruiser.js @@ -40,7 +40,6 @@ const GeoNextOldSeparationRules = [ }, ]; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules, ...GeoNextOldSeparationRules], options: DefaultOptions, diff --git a/libs/sdk-ui-geo/package.json b/libs/sdk-ui-geo/package.json index 3bcd66b6d6b..5849111184b 100644 --- a/libs/sdk-ui-geo/package.json +++ b/libs/sdk-ui-geo/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-geo", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Geo Charts", "license": "MIT", "author": "GoodData Corporation", @@ -105,12 +105,13 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-geo/src/index.ts b/libs/sdk-ui-geo/src/index.ts index 76678df7ccf..5c8fc42dbf4 100644 --- a/libs/sdk-ui-geo/src/index.ts +++ b/libs/sdk-ui-geo/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides the components that you can use to visualize location-based data. diff --git a/libs/sdk-ui-geo/src/internal/index.ts b/libs/sdk-ui-geo/src/internal/index.ts index a45008ffcb4..6e7d7c5731a 100644 --- a/libs/sdk-ui-geo/src/internal/index.ts +++ b/libs/sdk-ui-geo/src/internal/index.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * Internal entrypoint for `@gooddata/sdk-ui-geo`. diff --git a/libs/sdk-ui-geo/src/typings.d.ts b/libs/sdk-ui-geo/src/typings.d.ts index c3d37b47950..39396d29451 100644 --- a/libs/sdk-ui-geo/src/typings.d.ts +++ b/libs/sdk-ui-geo/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2020 GoodData Corporation +// (C) 2020-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "custom-event" { export default CustomEvent; diff --git a/libs/sdk-ui-geo/vitest.config.ts b/libs/sdk-ui-geo/vitest.config.ts index 7a2409a5cd6..cec43c95cfd 100644 --- a/libs/sdk-ui-geo/vitest.config.ts +++ b/libs/sdk-ui-geo/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-kit/.dependency-cruiser.js b/libs/sdk-ui-kit/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-kit/.dependency-cruiser.js +++ b/libs/sdk-ui-kit/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-kit/api/sdk-ui-kit.api.md b/libs/sdk-ui-kit/api/sdk-ui-kit.api.md index 0bb1a96a936..de38e801c33 100644 --- a/libs/sdk-ui-kit/api/sdk-ui-kit.api.md +++ b/libs/sdk-ui-kit/api/sdk-ui-kit.api.md @@ -1915,7 +1915,7 @@ export function IconTrash(input: IIconProps): JSX.Element; export function IconTreeMap(input: IIconProps): JSX.Element; // @internal (undocumented) -export type IconType = "aiAgent" | "aiAgentDisabled" | "brain" | "brainDisabled" | "check" | "checkCircle" | "certification" | "plus" | "plusCircle" | "sync" | "alert" | "alertPaused" | "close" | "cross" | "edit" | "crossCircle" | "question" | "chevronUp" | "chevronRight" | "chevronDown" | "chevronLeft" | "date" | "navigateUp" | "navigateDown" | "navigateRight" | "navigateLeft" | "download" | "slack" | "expand" | "exclamationCircle" | "infoCircle" | "book" | "visible" | "invisible" | "lock" | "unlock" | "ai" | "aiFill" | "drawer" | "drawerEmpty" | "prohibited" | "dropDown" | "dropRight" | "clock" | "clockPaused" | "questionMark" | "upload" | "expandRectangle" | "file" | "number" | "code" | "user" | "userPlus" | "users" | "magic" | "tab" | "pauseCircle" | "filter" | "timer" | "mail" | "envelope" | "copy" | "rain" | "earth" | "geoCollection" | "geoCollectionUpload" | "minimize" | "shrink" | "copyright" | "ellipsis" | "pencil" | "folder" | "folderSmall" | "folderPlus" | "trash" | "arrowUp" | "arrowRight" | "arrowDown" | "arrowLeft" | "undo" | "redo" | "trendDown" | "trendUp" | "save" | "minus" | "minusCircle" | "percent" | "enter" | "enterRight" | "money" | "ghost" | "warning" | "home" | "settings" | "search" | "university" | "building" | "printer" | "picture" | "visualization" | "dashboard" | "metric" | "fact" | "ldmAttribute" | "ldmKey" | "ldmLabel" | "sharp" | "attribute" | "horn" | "cw" | "ccw" | "table" | "directionColumn" | "directionRow" | "alignLeft" | "alignCenter" | "alignRight" | "alignTop" | "alignMiddle" | "alignBottom" | "bold" | "italic" | "imageContain" | "imageCover" | "imageFill" | "header" | "genai" | "genai2" | "explainai" | "hiddenForAi" | "box" | "ellipsisVertical" | "list" | "drillTo" | "hierarchy" | "history" | "history2" | "thumbsUp" | "thumbsDown" | "send" | "visualizationArea" | "visualizationTable" | "visualizationTreemap" | "visualizationScatter" | "visualizationDonut" | "visualizationHeadline" | "visualizationColumn" | "visualizationLine" | "visualizationPyramid" | "visualizationFunnel" | "visualizationHeatmap" | "visualizationBubble" | "visualizationPie" | "visualizationBar" | "visualizationCombo" | "visualizationBullet" | "visualizationWaterfall" | "visualizationDependencywheel" | "visualizationSankey" | "visualizationPushpin" | "visualizationRepeater" | "visualizationXirr" | "link" | "externalLink" | "click" | "fileXlsx" | "filePptx" | "filePdf" | "fileImage" | "fileCsvFormatted" | "fileCsvRaw" | "aiDocument" | "recommendation" | "streamUp" | "streamDown" | "stream" | "density" | "parameter" | "pin" | "unpin" | "speechBubble" | "pieChart" | "timezone" | "sidePanelCollapse" | "sidePanelExpand"; +export type IconType = "aiAgent" | "aiAgentDisabled" | "brain" | "brainDisabled" | "check" | "checkCircle" | "certification" | "plus" | "plusCircle" | "sync" | "alert" | "alertPaused" | "close" | "cross" | "edit" | "crossCircle" | "question" | "chevronUp" | "chevronRight" | "chevronDown" | "chevronLeft" | "date" | "navigateUp" | "navigateDown" | "navigateRight" | "navigateLeft" | "download" | "slack" | "expand" | "exclamationCircle" | "infoCircle" | "book" | "visible" | "invisible" | "lock" | "unlock" | "ai" | "aiFill" | "drawer" | "drawerEmpty" | "prohibited" | "dropDown" | "dropRight" | "clock" | "clockPaused" | "questionMark" | "upload" | "expandRectangle" | "file" | "number" | "code" | "user" | "userPlus" | "users" | "magic" | "tab" | "pauseCircle" | "filter" | "timer" | "mail" | "envelope" | "copy" | "rain" | "earth" | "geoCollection" | "geoCollectionUpload" | "minimize" | "shrink" | "copyright" | "ellipsis" | "pencil" | "folder" | "folderSmall" | "folderPlus" | "trash" | "arrowUp" | "arrowRight" | "arrowDown" | "arrowLeft" | "levelUp" | "undo" | "redo" | "trendDown" | "trendUp" | "save" | "minus" | "minusCircle" | "percent" | "enter" | "enterRight" | "money" | "ghost" | "warning" | "home" | "settings" | "search" | "university" | "building" | "printer" | "picture" | "visualization" | "dashboard" | "metric" | "fact" | "ldmAttribute" | "ldmKey" | "ldmLabel" | "sharp" | "attribute" | "horn" | "cw" | "ccw" | "table" | "directionColumn" | "directionRow" | "alignLeft" | "alignCenter" | "alignRight" | "alignTop" | "alignMiddle" | "alignBottom" | "bold" | "italic" | "imageContain" | "imageCover" | "imageFill" | "header" | "genai" | "genai2" | "explainai" | "hiddenForAi" | "box" | "ellipsisVertical" | "list" | "drillTo" | "hierarchy" | "history" | "history2" | "thumbsUp" | "thumbsDown" | "send" | "visualizationArea" | "visualizationTable" | "visualizationTreemap" | "visualizationScatter" | "visualizationDonut" | "visualizationHeadline" | "visualizationColumn" | "visualizationLine" | "visualizationPyramid" | "visualizationFunnel" | "visualizationHeatmap" | "visualizationBubble" | "visualizationPie" | "visualizationBar" | "visualizationCombo" | "visualizationBullet" | "visualizationWaterfall" | "visualizationDependencywheel" | "visualizationSankey" | "visualizationPushpin" | "visualizationRepeater" | "visualizationXirr" | "link" | "externalLink" | "click" | "fileXlsx" | "filePptx" | "filePdf" | "fileImage" | "fileCsvFormatted" | "fileCsvRaw" | "aiDocument" | "recommendation" | "streamUp" | "streamDown" | "stream" | "density" | "parameter" | "pin" | "unpin" | "speechBubble" | "pieChart" | "timezone" | "sidePanelCollapse" | "sidePanelExpand"; // @internal (undocumented) export function IconUndo(input: IIconProps): JSX.Element; @@ -6702,6 +6702,7 @@ export interface IUiDropdownBodyRenderProps { id: string; }; closeDropdown: () => void; + triggerId: string; } // @internal diff --git a/libs/sdk-ui-kit/package.json b/libs/sdk-ui-kit/package.json index e3235d3603a..f51ebe18574 100644 --- a/libs/sdk-ui-kit/package.json +++ b/libs/sdk-ui-kit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-kit", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - UI Building Components", "license": "MIT", "author": "GoodData Corporation", @@ -148,12 +148,13 @@ "kefir": "3.8.8", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "svgo": "2.8.3", "typescript": "7.0.2", diff --git a/libs/sdk-ui-kit/scripts/validateUiComponents.js b/libs/sdk-ui-kit/scripts/validateUiComponents.js index 7bedc034ee3..62906d4b7f5 100644 --- a/libs/sdk-ui-kit/scripts/validateUiComponents.js +++ b/libs/sdk-ui-kit/scripts/validateUiComponents.js @@ -1,5 +1,6 @@ -// (C) 2025 GoodData Corporation -/* eslint-disable no-console */ +// (C) 2025-2026 GoodData Corporation + +// oxlint-disable eslint-js/no-console import fs from "fs"; import path from "path"; diff --git a/libs/sdk-ui-kit/src/@ui/@types/icon.ts b/libs/sdk-ui-kit/src/@ui/@types/icon.ts index 3d084816536..9aed6d5ec64 100644 --- a/libs/sdk-ui-kit/src/@ui/@types/icon.ts +++ b/libs/sdk-ui-kit/src/@ui/@types/icon.ts @@ -83,6 +83,7 @@ export type IconType = | "arrowRight" | "arrowDown" | "arrowLeft" + | "levelUp" | "undo" | "redo" | "trendDown" diff --git a/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.test.tsx b/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.test.tsx new file mode 100644 index 00000000000..9606cc3725b --- /dev/null +++ b/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.test.tsx @@ -0,0 +1,71 @@ +// (C) 2026 GoodData Corporation + +import { screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { render } from "../../../test/render.js"; +import { UiListbox } from "../UiListbox/UiListbox.js"; + +import { type IUiDropdownProps } from "./types.js"; +import { UiDropdown } from "./UiDropdown.js"; + +function renderDropdown( + accessibilityConfig: IUiDropdownProps["accessibilityConfig"], + { nameAfterTrigger = false }: { nameAfterTrigger?: boolean } = {}, +) { + return render( + ( + + )} + renderBody={({ ariaAttributes, triggerId }) => ( + {}} + /> + )} + />, + ); +} + +describe("UiDropdown", () => { + // A listbox inside a listbox is not allowed; the body is the widget, the panel only holds it. + it("leaves a widget role to the body", () => { + renderDropdown({ triggerRole: "combobox", popupRole: "listbox" }); + + const trigger = screen.getByRole("combobox", { name: "Open" }); + const listbox = screen.getByRole("listbox"); + expect(listbox).toHaveAttribute("id", trigger.getAttribute("aria-controls")); + expect(document.querySelector(".gd-ui-kit-floating-element")).not.toHaveAttribute("role"); + }); + + it("lets a body without a name of its own take the trigger's", () => { + renderDropdown({ triggerRole: "button", popupRole: "listbox" }, { nameAfterTrigger: true }); + + expect(screen.getByRole("listbox", { name: "Open" })).toBeInTheDocument(); + }); + + it("makes the panel itself the dialog, named after the trigger", () => { + renderDropdown({ triggerRole: "button", popupRole: "dialog" }); + + const trigger = screen.getByRole("button", { name: "Open" }); + const dialog = screen.getByRole("dialog"); + expect(dialog).toHaveClass("gd-ui-kit-floating-element"); + expect(dialog).toHaveAttribute("aria-labelledby", trigger.parentElement!.id); + }); +}); diff --git a/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.tsx b/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.tsx index f05962a8a0d..f1b39397917 100644 --- a/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiDropdown/UiDropdown.tsx @@ -127,9 +127,10 @@ export function UiDropdown({ ); // Button render props + const popupRole = accessibilityConfig?.popupRole ?? "dialog"; + const buttonRenderProps = useMemo(() => { const triggerRole = accessibilityConfig?.triggerRole ?? "button"; - const popupRole = accessibilityConfig?.popupRole ?? "dialog"; return { ref: buttonRef as RefObject, @@ -152,7 +153,7 @@ export function UiDropdown({ closeDropdown, toggleDropdown, accessibilityConfig?.triggerRole, - accessibilityConfig?.popupRole, + popupRole, ]); // Body render props @@ -162,8 +163,9 @@ export function UiDropdown({ ariaAttributes: { id: dropdownId, }, + triggerId: buttonId, }), - [closeDropdown, dropdownId], + [closeDropdown, dropdownId, buttonId], ); return ( @@ -191,10 +193,11 @@ export function UiDropdown({ ignoreClicksOn={ignoreClicksOnByClass} zIndex={zIndex} width={width} - accessibilityConfig={{ - role: accessibilityConfig?.popupRole ?? "dialog", - ariaLabelledBy: buttonId, - }} + // A dialog is the panel itself. A listbox, menu, tree or grid is the widget the + // body renders with its own role, and the same role on the panel would nest it. + accessibilityConfig={ + popupRole === "dialog" ? { role: popupRole, ariaLabelledBy: buttonId } : undefined + } > = { arrowLeft: ( ), + // Drawn on a 16 grid, so it is centred rather than rescaled: that keeps it the weight of + // the align and format glyphs it stands beside. + levelUp: ( + + + + ), undo: ( ), diff --git a/libs/sdk-ui-kit/src/@ui/UiMenu/UiMenuCustomContentKeyboard.test.tsx b/libs/sdk-ui-kit/src/@ui/UiMenu/UiMenuCustomContentKeyboard.test.tsx new file mode 100644 index 00000000000..8c24ef6a54f --- /dev/null +++ b/libs/sdk-ui-kit/src/@ui/UiMenu/UiMenuCustomContentKeyboard.test.tsx @@ -0,0 +1,79 @@ +// (C) 2026 GoodData Corporation + +import { type ReactNode } from "react"; + +import { fireEvent, render, screen } from "@testing-library/react"; +import { IntlProvider } from "react-intl"; +import { describe, expect, it, vi } from "vitest"; + +import { DEFAULT_LANGUAGE, DEFAULT_MESSAGES } from "@gooddata/sdk-ui"; + +import { type IUiMenuItem } from "./types.js"; +import { UiMenu } from "./UiMenu.js"; + +/** + * A submenu rendered as UiMenu custom content can host a rich text editor (a drill-to-URL editor, a + * MAQL editor). Such an editor puts the caret in a contenteditable rather than in a form control, + * and its own arrow keys must not reach the menu: ArrowLeft there means "move the caret", not + * "leave this submenu". + */ +describe("UiMenu custom content keyboard handling", () => { + const messages = DEFAULT_MESSAGES[DEFAULT_LANGUAGE]; + + function renderMenuWithContent(editor: ReactNode) { + const items: IUiMenuItem[] = [ + { + type: "content", + id: "content1", + stringTitle: "Interactions", + data: undefined, + Component: () =>
    {editor}
    , + }, + ]; + + const result = render( + + + , + ); + + // open the custom content submenu + fireEvent.click(screen.getByText("Interactions")); + + return result; + } + + it("keeps the submenu open when ArrowLeft is pressed inside a contenteditable editor", () => { + renderMenuWithContent( +
    + https://example.com +
    , + ); + + const content = screen.getByTestId("custom-content"); + screen.getByTestId("editor").focus(); + + fireEvent.keyDown(content, { code: "ArrowLeft" }); + + expect(screen.getByTestId("custom-content")).toBeInTheDocument(); + }); + + it("leaves the submenu when ArrowLeft is pressed outside any text entry", () => { + renderMenuWithContent(); + + const content = screen.getByTestId("custom-content"); + screen.getByTestId("editor").focus(); + + fireEvent.keyDown(content, { code: "ArrowLeft" }); + + expect(screen.queryByTestId("custom-content")).not.toBeInTheDocument(); + }); +}); diff --git a/libs/sdk-ui-kit/src/@ui/UiTabs/UiTabs.test.tsx b/libs/sdk-ui-kit/src/@ui/UiTabs/UiTabs.test.tsx index defb8d13c1e..c827390f824 100644 --- a/libs/sdk-ui-kit/src/@ui/UiTabs/UiTabs.test.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiTabs/UiTabs.test.tsx @@ -113,5 +113,18 @@ describe("UiTabs", () => { expect(within(rows[0]).queryAllByRole("gridcell")).toHaveLength(1); expect(within(rows[1]).queryAllByRole("gridcell")).toHaveLength(2); }); + + it("should announce the tab list as the grid the trigger opens", async () => { + const { user } = render(); + + const trigger = await screen.findByRole("button", { name: "Show all tabs" }); + await user.click(trigger); + + const grid = screen.getByRole("grid"); + expect(trigger).toHaveAttribute("aria-haspopup", "grid"); + expect(trigger).toHaveAttribute("aria-controls", grid.id); + expect(grid).toHaveAttribute("aria-labelledby", trigger.parentElement!.id); + expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); + }); }); }); diff --git a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabs.tsx b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabs.tsx index 695f86865c8..fcfe6b21895 100644 --- a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabs.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabs.tsx @@ -115,7 +115,7 @@ export function DefaultUiTabsAllTabs< ariaAttributes={ariaAttributes} /> )} - renderBody={({ ariaAttributes }) => { + renderBody={({ ariaAttributes, triggerId }) => { return (
    @@ -146,7 +147,7 @@ export function DefaultUiTabsAllTabs< closeOnEscape accessibilityConfig={{ triggerRole: "button", - popupRole: "listbox", + popupRole: "grid", }} onOpenChange={handleOpenChange} isOpen={isOpen} diff --git a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabsButton.tsx b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabsButton.tsx index f09ce508212..b184c2b23e8 100644 --- a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabsButton.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsAllTabsButton.tsx @@ -17,7 +17,7 @@ function DefaultUiTabsAllTabsButtonNotWrapped< TTabProps extends Record = EmptyObject, TTabActionProps extends Record = EmptyObject, >( - { isOpen, onClick }: IUiTabComponentProps<"AllTabsButton", TTabProps, TTabActionProps>, + { isOpen, onClick, ariaAttributes }: IUiTabComponentProps<"AllTabsButton", TTabProps, TTabActionProps>, ref: Ref, ) { const intl = useIntl(); @@ -32,6 +32,7 @@ function DefaultUiTabsAllTabsButtonNotWrapped< ariaLabel: intl.formatMessage(messages["showAllTabs"]), ariaExpanded: isOpen, }} + ariaAttributes={ariaAttributes} onClick={onClick} ref={ref as Ref} disableAnimation @@ -44,6 +45,8 @@ function DefaultUiTabsAllTabsButtonNotWrapped< accessibilityConfig={{ ariaLabel: intl.formatMessage(messages["showAllTabs"]), ariaExpanded: isOpen, + ariaHaspopup: ariaAttributes?.["aria-haspopup"], + ariaControls: ariaAttributes?.["aria-controls"], }} onClick={onClick} ref={ref as Ref} diff --git a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsTabActions.tsx b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsTabActions.tsx index 5d030ea96e0..a0d35aff571 100644 --- a/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsTabActions.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiTabs/defaultComponents/DefaultUiTabsTabActions.tsx @@ -130,14 +130,14 @@ export function DefaultUiTabsTabActions< tabIndex={tabIndex} /> )} - renderBody={({ ariaAttributes, closeDropdown }) => { + renderBody={({ ariaAttributes, closeDropdown, triggerId }) => { return ( [0]> = {}) { + render(anchor} content="content" {...props} />); + return screen.getByRole(props.accessibilityConfig?.role ?? "tooltip"); +} + +describe("UiTooltip", () => { + it("caps the width when the caller sets none", () => { + expect(renderTooltip()).toHaveClass(MODIFIER); + }); + + it("drops the cap for an explicit pixel width", () => { + const tooltip = renderTooltip({ width: 480 }); + expect(tooltip).not.toHaveClass(MODIFIER); + expect(tooltip).toHaveStyle({ width: "480px" }); + }); + + it("drops the cap for a width matching the anchor", () => { + expect(renderTooltip({ width: "same-as-anchor" })).not.toHaveClass(MODIFIER); + }); + + it("drops the cap for popovers, which size themselves", () => { + expect(renderTooltip({ behaviour: "popover" })).not.toHaveClass(MODIFIER); + }); +}); diff --git a/libs/sdk-ui-kit/src/@ui/UiTooltip/UiTooltip.tsx b/libs/sdk-ui-kit/src/@ui/UiTooltip/UiTooltip.tsx index d17e3781d3c..bfefaf19d0b 100644 --- a/libs/sdk-ui-kit/src/@ui/UiTooltip/UiTooltip.tsx +++ b/libs/sdk-ui-kit/src/@ui/UiTooltip/UiTooltip.tsx @@ -219,6 +219,10 @@ export function UiTooltip({
    @@ -267,7 +267,7 @@ export function CsvDelimiterPicker({ closeOnEscape accessibilityConfig={{ triggerRole: "button", - popupRole: "listbox", + popupRole: "menu", }} /> diff --git a/libs/sdk-ui-kit/src/List/DateDatasetsListItem.test.tsx b/libs/sdk-ui-kit/src/List/DateDatasetsListItem.test.tsx index 9bd58760de8..424119797ad 100644 --- a/libs/sdk-ui-kit/src/List/DateDatasetsListItem.test.tsx +++ b/libs/sdk-ui-kit/src/List/DateDatasetsListItem.test.tsx @@ -5,7 +5,6 @@ import { type ReactNode, forwardRef, useImperativeHandle } from "react"; import { render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; -import type * as ShortenedTextModule from "../ShortenedText/ShortenedText.js"; import { type IShortenedTextHandle } from "../ShortenedText/ShortenedText.js"; import { DateDatasetsListItem } from "./DateDatasetsListItem.js"; @@ -14,8 +13,8 @@ const { recomputeShorteningSpy } = vi.hoisted(() => ({ recomputeShorteningSpy: v // ShortenedText exposes recomputeShortening only through its imperative ref handle, so the spy has // to be injected in place of the real component. -vi.mock("../ShortenedText/ShortenedText.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock(import("../ShortenedText/ShortenedText.js"), async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, diff --git a/libs/sdk-ui-kit/src/Menu/__snapshots__/Menu.test.tsx.snap b/libs/sdk-ui-kit/src/Menu/__snapshots__/Menu.test.tsx.snap index 5e5863295f4..7a1b4587af7 100644 --- a/libs/sdk-ui-kit/src/Menu/__snapshots__/Menu.test.tsx.snap +++ b/libs/sdk-ui-kit/src/Menu/__snapshots__/Menu.test.tsx.snap @@ -17,7 +17,7 @@ exports[`Menu snapshot > should match snapshot of menu with config 1`] = `
    should match snapshot of menu with portalTarget 1`] = `
    should match snapshot of nested menus 1`] = `
    (null); + const zIndex = useOverlayZIndexWithRegister(); + // The menu portals to , outside whatever opened it, so overlay/dialog outside-click // detection cannot attribute its clicks by DOM containment. Mark it as a floating element and // register the toggler as its anchor, so ownership resolves through the anchor chain (same @@ -98,6 +101,7 @@ export function PositionedMenuContent(props: IPositionedMenuContentProps) { position: "absolute", left: state.left, top: state.top, + ...(props.topLevelMenu ? { zIndex } : null), }} ref={setMenuElWithAnchorRegistry} {...{ [FLOATING_ELEMENT_DATA_ATTR]: true }} diff --git a/libs/sdk-ui-kit/src/RecurrenceForm/utils/utils.test.ts b/libs/sdk-ui-kit/src/RecurrenceForm/utils/utils.test.ts index 360c48a9518..8b53e4a6e7c 100644 --- a/libs/sdk-ui-kit/src/RecurrenceForm/utils/utils.test.ts +++ b/libs/sdk-ui-kit/src/RecurrenceForm/utils/utils.test.ts @@ -146,7 +146,7 @@ describe("transformCronExpressionToRecurrenceType with date", () => { const D2024_01_17_15 = new Date(2024, 0, 17, 15, 0, 0); const D2024_01_25_15 = new Date(2024, 0, 25, 15, 0, 0); - it.each([ + it.each<[string | undefined, boolean, boolean, Date | undefined, string, string?]>([ ["0 0 * ? * *", true, false, universal, "hourly"], ["0 0 * ? * *", false, false, universal, "cron"], ["0 0 1 ? * *", true, false, D2024_01_01_01, "daily"], @@ -181,27 +181,24 @@ describe("transformCronExpressionToRecurrenceType with date", () => { ["", true, true, undefined, "inherit"], [undefined, true, true, undefined, "inherit"], [undefined, true, false, undefined, "cron"], - ])("should correctly identify %s with hourly %s and date %s as %s", (( - cronExpression: string | undefined, - allowHourly: boolean, - allowInherit: boolean, - date: Date | undefined, - expected: string, - ) => { - expect( - transformCronExpressionToRecurrenceType( - date ?? null, - cronExpression, - allowHourly, - allowInherit, - "Monday", - ), - ).toEqual(expected); - }) as any); + ])( + "should correctly identify %s with hourly %s and date %s as %s", + (cronExpression, allowHourly, allowInherit, date, expected) => { + expect( + transformCronExpressionToRecurrenceType( + date ?? null, + cronExpression, + allowHourly, + allowInherit, + "Monday", + ), + ).toEqual(expected); + }, + ); }); describe("transformCronExpressionToRecurrenceType without date, default Monday", () => { - it.each([ + it.each<[string | undefined, boolean, boolean, string, string?]>([ ["0 0 * ? * *", true, false, "hourly"], ["0 0 * ? * *", false, false, "cron"], ["0 0 1 ? * *", true, false, "daily"], @@ -237,29 +234,27 @@ describe("transformCronExpressionToRecurrenceType without date, default Monday", ["", true, true, "inherit"], [undefined, true, true, "inherit"], [undefined, true, false, "cron"], - ])("should correctly identify %s with hourly %s as %s", (( - cronExpression: string | undefined, - allowHourly: boolean, - allowInherit: boolean, - expected: string, - ) => { - expect( - transformCronExpressionToRecurrenceType( - null, - cronExpression, - allowHourly, - allowInherit, - "Monday", - ), - ).toEqual(expected); - }) as any); + ])( + "should correctly identify %s with hourly %s as %s", + (cronExpression, allowHourly, allowInherit, expected) => { + expect( + transformCronExpressionToRecurrenceType( + null, + cronExpression, + allowHourly, + allowInherit, + "Monday", + ), + ).toEqual(expected); + }, + ); }); describe("transformRecurrenceTypeToDescription", () => { const intl = createIntlMock(); const d1 = new Date(2024, 7, 13, 22, 0, 0); - it.each([ + it.each<[string, Date | null, WeekStart, string]>([ [RECURRENCE_TYPES.HOURLY, null, "Monday", "At start of every hour"], [RECURRENCE_TYPES.HOURLY, null, "Sunday", "At start of every hour"], [RECURRENCE_TYPES.DAILY, null, "Monday", "At 12 AM every day"], @@ -283,12 +278,7 @@ describe("transformRecurrenceTypeToDescription", () => { [RECURRENCE_TYPES.CRON, d1, "Sunday", ""], [RECURRENCE_TYPES.INHERIT, d1, "Sunday", ""], - ])("should correctly describe %s", (( - recurrenceType: string, - date: Date | null, - weekStart: WeekStart, - expected: string, - ) => { + ])("should correctly describe %s", (recurrenceType, date, weekStart, expected) => { expect(transformRecurrenceTypeToDescription(intl, recurrenceType, date, weekStart)).toEqual(expected); - }) as any); + }); }); diff --git a/libs/sdk-ui-kit/src/index.ts b/libs/sdk-ui-kit/src/index.ts index 515108e56f5..5de0d1890de 100644 --- a/libs/sdk-ui-kit/src/index.ts +++ b/libs/sdk-ui-kit/src/index.ts @@ -1,6 +1,6 @@ // (C) 2020-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides various UI components used to build GoodData applications (such as buttons, icons, and so on). diff --git a/libs/sdk-ui-kit/src/typings.d.ts b/libs/sdk-ui-kit/src/typings.d.ts index 8c055b3d6a2..04c96f933e9 100644 --- a/libs/sdk-ui-kit/src/typings.d.ts +++ b/libs/sdk-ui-kit/src/typings.d.ts @@ -1,6 +1,6 @@ // (C) 2020-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports /// diff --git a/libs/sdk-ui-kit/src/utils/domUtilities.test.ts b/libs/sdk-ui-kit/src/utils/domUtilities.test.ts index 007662e9580..c02c33e94d8 100644 --- a/libs/sdk-ui-kit/src/utils/domUtilities.test.ts +++ b/libs/sdk-ui-kit/src/utils/domUtilities.test.ts @@ -1,10 +1,10 @@ // (C) 2020-2026 GoodData Corporation -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { type IRegion } from "../typings/domUtilities.js"; -import { region, removeFromDom } from "./domUtilities.js"; +import { isElementTextInput, region, removeFromDom } from "./domUtilities.js"; interface IConfig { height: number; @@ -173,3 +173,66 @@ describe("removeFromDom", () => { expect(document.body.children).not.toContain(elem); }); }); + +describe("isElementTextInput", () => { + // The suite runs with `isolate: false`, so anything left in the document leaks into later files. + const mounted: HTMLElement[] = []; + + afterEach(() => { + mounted.splice(0).forEach((host) => host.remove()); + }); + + function mount(html: string) { + const host = document.createElement("div"); + host.innerHTML = html; + document.body.appendChild(host); + mounted.push(host); + return host; + } + + it.each([ + ["a text input", '', true], + ["a number input", '', true], + ["a textarea", "", true], + ["a checkbox", '', false], + ["a button", "", false], + ["a plain div", "
    ", false], + ])("reports %s as %s", (_name, html, expected) => { + const element = mount(html).firstElementChild as HTMLElement; + + expect(isElementTextInput(element)).toBe(expected); + }); + + it("reports a contenteditable host as text input", () => { + const element = mount('
    ').firstElementChild as HTMLElement; + + expect(isElementTextInput(element)).toBe(true); + }); + + it("reports a descendant of a contenteditable host as text input", () => { + // The caret in a rich text editor lands on an inner node, not on the editable host itself. + const host = mount('
    text
    '); + + expect(isElementTextInput(host.querySelector(".line"))).toBe(true); + }); + + it("does not report a read-only contenteditable as text input", () => { + const element = mount('
    ').firstElementChild as HTMLElement; + + expect(isElementTextInput(element)).toBe(false); + }); + + it("does not report a non-editable island inside a contenteditable host as text input", () => { + const host = mount( + '
    ', + ); + + expect(isElementTextInput(host.querySelector(".widget"))).toBe(false); + }); + + it("handles missing elements and non-elements", () => { + expect(isElementTextInput(null)).toBe(false); + expect(isElementTextInput(undefined)).toBe(false); + expect(isElementTextInput({} as EventTarget)).toBe(false); + }); +}); diff --git a/libs/sdk-ui-kit/src/utils/domUtilities.ts b/libs/sdk-ui-kit/src/utils/domUtilities.ts index 6be277a2055..dcc646fad65 100644 --- a/libs/sdk-ui-kit/src/utils/domUtilities.ts +++ b/libs/sdk-ui-kit/src/utils/domUtilities.ts @@ -1,4 +1,4 @@ -// (C) 2020-2025 GoodData Corporation +// (C) 2020-2026 GoodData Corporation import { type KeyboardEvent } from "react"; @@ -144,7 +144,12 @@ export const isElementTextInput = (element: HTMLElement | EventTarget | null | u return ( tagNameInLowercase === "textarea" || - (tagNameInLowercase === "input" && (typeInLowercase === "text" || typeInLowercase === "number")) + (tagNameInLowercase === "input" && (typeInLowercase === "text" || typeInLowercase === "number")) || + // A rich text editor (CodeMirror, for one) puts the caret in a contenteditable host rather + // than in a form control, and the caret usually sits in a descendant of that host. Read the + // editability the browser resolved rather than the attribute: it accounts for inheritance and + // stays false for contenteditable="false", so a read-only editor does not count as text entry. + (element as HTMLElement).isContentEditable === true ); }; diff --git a/libs/sdk-ui-kit/vitest.config.ts b/libs/sdk-ui-kit/vitest.config.ts index 8081cdb9d6e..446560e3c98 100644 --- a/libs/sdk-ui-kit/vitest.config.ts +++ b/libs/sdk-ui-kit/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-loaders/.dependency-cruiser.js b/libs/sdk-ui-loaders/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-loaders/.dependency-cruiser.js +++ b/libs/sdk-ui-loaders/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-loaders/package.json b/libs/sdk-ui-loaders/package.json index fd006ba4a9b..726300b02d3 100644 --- a/libs/sdk-ui-loaders/package.json +++ b/libs/sdk-ui-loaders/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-loaders", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK Runtime Component Loaders", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -79,8 +79,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", diff --git a/libs/sdk-ui-loaders/src/dashboard/dashboardLoader.tsx b/libs/sdk-ui-loaders/src/dashboard/dashboardLoader.tsx index e5e955122e4..fe412150d5d 100644 --- a/libs/sdk-ui-loaders/src/dashboard/dashboardLoader.tsx +++ b/libs/sdk-ui-loaders/src/dashboard/dashboardLoader.tsx @@ -223,7 +223,7 @@ export class DashboardLoader implements IDashboardLoader { ): Promise<[IDashboardEngine, IDashboardPluginContract_V1[]]> => { const { engineLoader, pluginLoader, beforeLoad } = config; - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Loading engine and plugins..."); let engine: IDashboardEngine = newDashboardEngine(); @@ -250,7 +250,7 @@ export class DashboardLoader implements IDashboardLoader { ); } - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Initializing the plugins..."); const additionalPlugins = await initializeEmbeddedPlugins(ctx, this.embeddedPlugins); @@ -289,7 +289,7 @@ export class DashboardLoader implements IDashboardLoader { const [workspace, clientWorkspace] = this.resolveWorkspace(backend); invariant(workspace, "DashboardLoader is not configured with workspace to use and loader."); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Loading the dashboard..."); const { config } = this.baseProps; @@ -326,7 +326,7 @@ export class DashboardLoader implements IDashboardLoader { clientId: clientWorkspace?.client, }; - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Validating the plugins..."); const pluginsAreValid = @@ -416,7 +416,7 @@ async function initializeLoadedPlugins( try { if (plugin.onPluginLoaded) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug(`Calling onPluginLoaded on ${plugin.displayName}...`); const loadPromise = plugin.onPluginLoaded(ctx, parameters); diff --git a/libs/sdk-ui-loaders/src/dashboard/loadingStrategies/dynamicComponentLoaders.ts b/libs/sdk-ui-loaders/src/dashboard/loadingStrategies/dynamicComponentLoaders.ts index 0e8da462294..30afb670ee2 100644 --- a/libs/sdk-ui-loaders/src/dashboard/loadingStrategies/dynamicComponentLoaders.ts +++ b/libs/sdk-ui-loaders/src/dashboard/loadingStrategies/dynamicComponentLoaders.ts @@ -154,7 +154,7 @@ function addScriptTag(url: string): { element: HTMLScriptElement; promise: Promi } element.onload = () => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Dynamic Script Loaded: ${url}`); resolve(); }; diff --git a/libs/sdk-ui-loaders/src/dashboard/useDashboardLoader.ts b/libs/sdk-ui-loaders/src/dashboard/useDashboardLoader.ts index 5de45296592..54acc1b0639 100644 --- a/libs/sdk-ui-loaders/src/dashboard/useDashboardLoader.ts +++ b/libs/sdk-ui-loaders/src/dashboard/useDashboardLoader.ts @@ -123,7 +123,7 @@ export function useDashboardLoader(options: IDashboardLoadOptions): DashboardLoa const extraPluginsArr = Array.isArray(extraPlugins) ? extraPlugins : compact([extraPlugins]); initializeLoader(loader, baseProps, extraPluginsArr, clientWorkspace); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( `Dashboard loader initialized in ${loadingMode} mode to load ${ dashboardRef ? objRefToString(dashboardRef) : "empty dashboard" @@ -161,9 +161,9 @@ export function useDashboardLoader(options: IDashboardLoadOptions): DashboardLoa }); }, onSuccess: (result) => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Loaded dashboard engine", result.engine.version); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( `Dashboard engine ${result.engine.version} initialized with plugins`, result.plugins.map((plugin) => `${plugin.displayName}/${plugin.version}`).join(", "), diff --git a/libs/sdk-ui-loaders/src/index.ts b/libs/sdk-ui-loaders/src/index.ts index a6c71b1d366..3d39f810f81 100644 --- a/libs/sdk-ui-loaders/src/index.ts +++ b/libs/sdk-ui-loaders/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides various component loaders. diff --git a/libs/sdk-ui-pivot/.dependency-cruiser.js b/libs/sdk-ui-pivot/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-pivot/.dependency-cruiser.js +++ b/libs/sdk-ui-pivot/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-pivot/package.json b/libs/sdk-ui-pivot/package.json index 98d2cd2ac48..84911d336dd 100644 --- a/libs/sdk-ui-pivot/package.json +++ b/libs/sdk-ui-pivot/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-pivot", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Pivot Table", "license": "MIT", "author": "GoodData Corporation", @@ -105,12 +105,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-pivot/src/impl/eventHandlers/sortingEventHandlers.ts b/libs/sdk-ui-pivot/src/impl/eventHandlers/sortingEventHandlers.ts index c0d11d90627..d835ae06cee 100644 --- a/libs/sdk-ui-pivot/src/impl/eventHandlers/sortingEventHandlers.ts +++ b/libs/sdk-ui-pivot/src/impl/eventHandlers/sortingEventHandlers.ts @@ -33,7 +33,7 @@ export class SortingEventHandlers { const executionDefinition = getExecutionDefinition(); const totals = sanitizeDefTotals(executionDefinition, sortItems); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("onSortChanged", sortItems); pushDataGuard({ diff --git a/libs/sdk-ui-pivot/src/impl/stickyRowHandler.test.ts b/libs/sdk-ui-pivot/src/impl/stickyRowHandler.test.ts index c1520da54dd..7ca4f4e2a0e 100644 --- a/libs/sdk-ui-pivot/src/impl/stickyRowHandler.test.ts +++ b/libs/sdk-ui-pivot/src/impl/stickyRowHandler.test.ts @@ -73,14 +73,18 @@ describe("stickyRowHandler", () => { } function assertOnlyListedMethodsHaveBeenCalled(obj: any, exceptMethodNames: string[]) { - Object.getOwnPropertyNames(obj).forEach((propName) => { - if (typeof obj[propName] === "function") { - if (exceptMethodNames.indexOf(propName) >= 0) { - expect(obj[propName]).toHaveBeenCalled(); - } else { - expect(obj[propName]).not.toHaveBeenCalled(); - } - } + const methodNames = Object.getOwnPropertyNames(obj).filter( + (propName) => typeof obj[propName] === "function", + ); + + methodNames.forEach((propName) => { + const shouldHaveBeenCalled = exceptMethodNames.indexOf(propName) >= 0; + const hasBeenCalled = obj[propName].mock.calls.length > 0; + + expect({ method: propName, hasBeenCalled }).toEqual({ + method: propName, + hasBeenCalled: shouldHaveBeenCalled, + }); }); } diff --git a/libs/sdk-ui-pivot/src/impl/tableFacade.ts b/libs/sdk-ui-pivot/src/impl/tableFacade.ts index 832b81e3674..7e06eb0e6ca 100644 --- a/libs/sdk-ui-pivot/src/impl/tableFacade.ts +++ b/libs/sdk-ui-pivot/src/impl/tableFacade.ts @@ -232,7 +232,7 @@ export class TableFacade { return this.gridApi; } - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Attempting to obtain gridApi for a destructed table."); return undefined; }; @@ -275,7 +275,7 @@ export class TableFacade { }; private onExecutionTransformed = (newExecution: IPreparedExecution): void => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("onExecutionTransformed", newExecution.definition); this.transformedExecution = newExecution; this.onExecutionTransformedCallback?.(newExecution); @@ -812,14 +812,14 @@ export class TableFacade { if (this.originalExecution.fingerprint() === other.fingerprint()) { return true; } else { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("Original execution fingerprint does not match."); } if (this.transformedExecution) { const matchingTransformed = this.transformedExecution.fingerprint() === other.fingerprint(); if (!matchingTransformed) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( "transformed execution does not match", this.transformedExecution.definition, @@ -833,7 +833,7 @@ export class TableFacade { const matchingCurrentlyRendered = this.currentFingerprint === other.fingerprint(); if (!matchingCurrentlyRendered) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("current result does not match", this.currentResult.definition, other.definition); } diff --git a/libs/sdk-ui-pivot/src/impl/updates/componentUpdateAnalyzer.ts b/libs/sdk-ui-pivot/src/impl/updates/componentUpdateAnalyzer.ts index 26a21c8e609..aad7c83428d 100644 --- a/libs/sdk-ui-pivot/src/impl/updates/componentUpdateAnalyzer.ts +++ b/libs/sdk-ui-pivot/src/impl/updates/componentUpdateAnalyzer.ts @@ -1,4 +1,5 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation + import { isEqual } from "lodash-es"; import { type IPreparedExecution } from "@gooddata/sdk-backend-spi"; @@ -57,7 +58,7 @@ export class ComponentUpdateAnalyzer { ); if (!drillingIsSame) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( "drilling is different", prevProps.drillableItems, @@ -79,7 +80,7 @@ export class ComponentUpdateAnalyzer { ); if (!initializeForSameExec) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug( "initializer for different execution", this.context.props.execution, @@ -93,7 +94,7 @@ export class ComponentUpdateAnalyzer { this.context.props.execution.fingerprint() === prevProps.execution.fingerprint(); if (!prepExecutionSame) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("have to reinit table", this.context.props.execution, prevProps.execution); } @@ -170,7 +171,7 @@ export class ComponentUpdateAnalyzer { * Logs reinit debug information. */ public logReinitDebug(prevExecution: IPreparedExecution): void { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug("triggering reinit", this.context.props.execution.definition, prevExecution.definition); } } diff --git a/libs/sdk-ui-pivot/src/index.ts b/libs/sdk-ui-pivot/src/index.ts index d6077752797..93541386ef3 100644 --- a/libs/sdk-ui-pivot/src/index.ts +++ b/libs/sdk-ui-pivot/src/index.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides the PivotTable component that you can use to visualize your data in a table-based manner. diff --git a/libs/sdk-ui-pivot/src/next/index.ts b/libs/sdk-ui-pivot/src/next/index.ts index dd69386fc77..2c48380ace1 100644 --- a/libs/sdk-ui-pivot/src/next/index.ts +++ b/libs/sdk-ui-pivot/src/next/index.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { PivotTableNext } from "./PivotTableNext.js"; export { diff --git a/libs/sdk-ui-pivot/src/next/types/conditionalFormatting.ts b/libs/sdk-ui-pivot/src/next/types/conditionalFormatting.ts index 4f3106ba55b..b4794f2d393 100644 --- a/libs/sdk-ui-pivot/src/next/types/conditionalFormatting.ts +++ b/libs/sdk-ui-pivot/src/next/types/conditionalFormatting.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files -- re-export shim, types moved to @gooddata/sdk-model */ +// oxlint-disable no-barrel-files/no-barrel-files -- re-export shim, types moved to @gooddata/sdk-model import type { ConditionalFormattingEmptinessOperator, diff --git a/libs/sdk-ui-pivot/src/typings.d.ts b/libs/sdk-ui-pivot/src/typings.d.ts index d4e18e18001..6f9aef68e99 100644 --- a/libs/sdk-ui-pivot/src/typings.d.ts +++ b/libs/sdk-ui-pivot/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2022 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/libs/sdk-ui-pivot/vitest.config.ts b/libs/sdk-ui-pivot/vitest.config.ts index 1ca4aece83e..e5d839f8e44 100644 --- a/libs/sdk-ui-pivot/vitest.config.ts +++ b/libs/sdk-ui-pivot/vitest.config.ts @@ -8,7 +8,6 @@ import { defineConfig } from "vitest/config"; * module that other test files import for real) can get one by calling `vi.resetModules()` and importing * the module under test dynamically afterwards. */ -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-pluggable-application/.dependency-cruiser.js b/libs/sdk-ui-pluggable-application/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-pluggable-application/.dependency-cruiser.js +++ b/libs/sdk-ui-pluggable-application/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-pluggable-application/package.json b/libs/sdk-ui-pluggable-application/package.json index 6d5f8d78259..b5522fd4499 100644 --- a/libs/sdk-ui-pluggable-application/package.json +++ b/libs/sdk-ui-pluggable-application/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-pluggable-application", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK React helpers for pluggable applications", "license": "MIT", "author": "GoodData Corporation", @@ -73,8 +73,9 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "typescript": "7.0.2", diff --git a/libs/sdk-ui-pluggable-application/src/index.ts b/libs/sdk-ui-pluggable-application/src/index.ts index b158de9cf60..1e8c5bfc6b2 100644 --- a/libs/sdk-ui-pluggable-application/src/index.ts +++ b/libs/sdk-ui-pluggable-application/src/index.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * React helpers for pluggable application integration. diff --git a/libs/sdk-ui-pluggable-host/api/sdk-ui-pluggable-host.api.md b/libs/sdk-ui-pluggable-host/api/sdk-ui-pluggable-host.api.md index e67af4f4f6a..dd51eb00b85 100644 --- a/libs/sdk-ui-pluggable-host/api/sdk-ui-pluggable-host.api.md +++ b/libs/sdk-ui-pluggable-host/api/sdk-ui-pluggable-host.api.md @@ -4,8 +4,11 @@ ```ts +import { Component } from 'react'; +import { ErrorInfo } from 'react'; import { IAnalyticalBackend } from '@gooddata/sdk-backend-spi'; import { IHostUiNotification } from '@gooddata/sdk-pluggable-application-model'; +import { ILocale } from '@gooddata/sdk-model'; import { IPlatformContext } from '@gooddata/sdk-pluggable-application-model'; import { IPluggableApp } from '@gooddata/sdk-pluggable-application-model'; import { IPluggableAppTelemetryCallbacks } from '@gooddata/sdk-pluggable-application-model'; @@ -19,6 +22,20 @@ export function dispatchHostNotification(notification: IHostUiNotification): voi // @alpha (undocumented) export function getBackend(): IAnalyticalBackend; +// @alpha +export class HostErrorBoundary extends Component { + // (undocumented) + componentDidCatch(error: Error, info: ErrorInfo): void; + // (undocumented) + static getDerivedStateFromError(): Partial; + // (undocumented) + static getDerivedStateFromProps(props: IHostErrorBoundaryProps, state: IHostErrorBoundaryState): Partial | null; + // (undocumented) + render(): ReactNode; + // (undocumented) + state: IHostErrorBoundaryState; +} + // @alpha (undocumented) export interface IAppLifecycleCallbacks { // (undocumented) @@ -55,6 +72,24 @@ export interface IHostChromePricing { onUpsellButtonClick: () => void; } +// @alpha +export interface IHostErrorBoundaryProps { + // (undocumented) + children: ReactNode; + // (undocumented) + locale?: ILocale; + onError?: (error: string, context: string) => void; + resetKey?: string | number; +} + +// @alpha +export interface IHostErrorBoundaryState { + // (undocumented) + hasError: boolean; + // (undocumented) + resetKey?: string | number; +} + // @alpha (undocumented) export interface ILegacyLocation { // (undocumented) diff --git a/libs/sdk-ui-pluggable-host/package.json b/libs/sdk-ui-pluggable-host/package.json index d938819089d..11678e6133e 100644 --- a/libs/sdk-ui-pluggable-host/package.json +++ b/libs/sdk-ui-pluggable-host/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-pluggable-host", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome", "license": "MIT", "author": "GoodData Corporation", @@ -88,14 +88,15 @@ "jiti": "2.6.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "react-intl": "10.1.25", "react-router": "7.18.1", "rolldown": "1.0.3", - "sass": "1.100.0", + "sass": "1.104.0", "tslib": "2.8.1", "typescript": "7.0.2", "vite": "8.0.16", diff --git a/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.test.tsx b/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.test.tsx new file mode 100644 index 00000000000..4720b9da32a --- /dev/null +++ b/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.test.tsx @@ -0,0 +1,69 @@ +// (C) 2026 GoodData Corporation + +import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; + +import { HostErrorBoundary } from "./HostErrorBoundary.js"; + +function Bomb(): never { + throw new Error("boom"); +} + +describe("HostErrorBoundary", () => { + it("renders children when nothing throws", () => { + render( + +
    + , + ); + + expect(screen.getByTestId("content")).toBeInTheDocument(); + }); + + it("renders the error screen instead of unmounting the tree when a child throws", () => { + vi.spyOn(console, "error").mockImplementation(() => {}); + + const { container } = render( + + + , + ); + + expect(container.querySelector(".gd-host-root__error")).not.toBeNull(); + expect(screen.getByRole("heading")).toBeInTheDocument(); + expect(screen.queryByText(/boom/)).not.toBeInTheDocument(); + }); + + it("clears a caught error when resetKey changes", () => { + vi.spyOn(console, "error").mockImplementation(() => {}); + + const { container, rerender } = render( + + + , + ); + expect(container.querySelector(".gd-host-root__error")).not.toBeNull(); + + rerender( + +
    + , + ); + + expect(screen.getByTestId("recovered")).toBeInTheDocument(); + expect(container.querySelector(".gd-host-root__error")).toBeNull(); + }); + + it("reports the caught error via onError", () => { + vi.spyOn(console, "error").mockImplementation(() => {}); + const onError = vi.fn(); + + render( + + + , + ); + + expect(onError).toHaveBeenCalledWith(expect.stringContaining("boom"), expect.any(String)); + }); +}); diff --git a/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.tsx b/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.tsx new file mode 100644 index 00000000000..0eff417fef0 --- /dev/null +++ b/libs/sdk-ui-pluggable-host/src/components/HostErrorBoundary.tsx @@ -0,0 +1,93 @@ +// (C) 2026 GoodData Corporation + +import { Component, type ErrorInfo, type ReactNode } from "react"; + +import { FormattedMessage } from "react-intl"; + +import { type ILocale } from "@gooddata/sdk-model"; +import { DEFAULT_LANGUAGE } from "@gooddata/sdk-ui"; +import { bemFactory } from "@gooddata/sdk-ui-kit"; + +import { HostIntlProvider } from "../ui/HostIntlProvider.js"; + +import "./Root.scss"; + +const { e } = bemFactory("gd-host-root"); + +/** + * Properties of {@link HostErrorBoundary}. + * + * @alpha + */ +export interface IHostErrorBoundaryProps { + /** + * Reports the caught error together with the React component stack it was thrown from. + */ + onError?: (error: string, context: string) => void; + locale?: ILocale; + /** + * A caught error is cleared when this value changes, so the subtree gets a fresh + * mount attempt (e.g. on navigation) instead of showing the fallback forever. + */ + resetKey?: string | number; + children: ReactNode; +} + +/** + * State of {@link HostErrorBoundary}. + * + * @alpha + */ +export interface IHostErrorBoundaryState { + hasError: boolean; + resetKey?: string | number; +} + +/** + * Last-resort boundary for errors escaping to the host application root. The host page is + * composed of nested React roots rendering into DOM owned by the topmost root, so without + * a boundary React unmounts the entire tree — header included — leaving a blank page. + * + * @alpha + */ +export class HostErrorBoundary extends Component { + override state: IHostErrorBoundaryState = { hasError: false }; + + static getDerivedStateFromError(): Partial { + return { hasError: true }; + } + + static getDerivedStateFromProps( + props: IHostErrorBoundaryProps, + state: IHostErrorBoundaryState, + ): Partial | null { + if (props.resetKey !== state.resetKey) { + return { resetKey: props.resetKey, hasError: false }; + } + return null; + } + + override componentDidCatch(error: Error, info: ErrorInfo): void { + this.props.onError?.(String(error), info.componentStack ?? ""); + } + + override render(): ReactNode { + if (!this.state.hasError) { + return this.props.children; + } + + // Exception details go to telemetry (onError) and the console only — rendering them + // could expose internal backend details in the UI. + return ( + + {/* Not a
    : the app-slot fallback renders inside the chrome's
    , + and nested main landmarks break screen-reader landmark navigation. */} +
    +

    + +

    +
    + + ); + } +} diff --git a/libs/sdk-ui-pluggable-host/src/components/HostUiContainer.tsx b/libs/sdk-ui-pluggable-host/src/components/HostUiContainer.tsx index b7d383d001c..6a247536e17 100644 --- a/libs/sdk-ui-pluggable-host/src/components/HostUiContainer.tsx +++ b/libs/sdk-ui-pluggable-host/src/components/HostUiContainer.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { isEqual, omit } from "lodash-es"; import { type Root as ReactRoot, createRoot } from "react-dom/client"; import { type NavigateFunction } from "react-router"; @@ -31,6 +32,7 @@ import { PluggableApplicationRenderer } from "../ui/PluggableApplicationRenderer import "./HostUiContainer.scss"; import { resolveHostUiModule } from "../ui/resolveHostUiModule.js"; +import { HostErrorBoundary } from "./HostErrorBoundary.js"; import { runGuardedNavigation } from "./navigationGuard.js"; export interface IHostUiContainerProps { @@ -38,6 +40,7 @@ export interface IHostUiContainerProps { apps: PluggableApplicationRegistryItem[]; pathname: string; routerNavigate: NavigateFunction; + onError?: (error: string, context: string) => void; } /** @@ -45,7 +48,7 @@ export interface IHostUiContainerProps { * pluggable application into the host's app slot. Handles all lifecycle * updates (context, apps, pathname) via the host mount handle. */ -export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUiContainerProps) { +export function HostUiContainer({ ctx, apps, pathname, routerNavigate, onError }: IHostUiContainerProps) { const activeInternalApplication = useMemo( () => getActiveInternalApplication(apps, ctx, pathname), [apps, ctx, pathname], @@ -56,6 +59,30 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUi const appRootRef = useRef(undefined); const [hostReady, setHostReady] = useState(false); const latestMountStateRef = useAutoupdateRef({ ctx, apps, pathname }); + const onErrorRef = useAutoupdateRef(onError); + const reportError = useCallback( + (error: string, context: string) => onErrorRef.current?.(error, context), + [onErrorRef], + ); + + // Mounted apps hot-apply only auth from updateContext — every other context field + // (permissions, settings, entitlements, org permissions, …) is seeded into their stores + // at mount only. A refresh that lands ANY content change outside auth must therefore + // remount the app. Content comparison, not identity: every refresh produces new objects, + // and remounting on each token refresh would defeat keeping the app mounted at all. + // The color palette is also excluded: its load is not part of the platform-context gate, + // so it legitimately arrives after the first ready context — a styling update, not a + // reason to abort the mounted app. + const [appContextEpoch, setAppContextEpoch] = useState(0); + const prevAppContextRef = useRef(ctx); + useEffect(() => { + const prev = prevAppContextRef.current; + prevAppContextRef.current = ctx; + const epochFields = (c: IPlatformContext) => omit(c, ["auth", "colorPalette"]); + if (prev !== ctx && !isEqual(epochFields(prev), epochFields(ctx))) { + setAppContextEpoch((epoch) => epoch + 1); + } + }, [ctx]); const [headerOptions, setHeaderOptions] = useState(undefined); const [pageTitle, setPageTitle] = useState(undefined); @@ -189,6 +216,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUi replace: navigationMountRef.current.replace, onChatToggleRequested: navigationMountRef.current.onChatToggleRequested, onAskAiAssistant: navigationMountRef.current.onAskAiAssistant, + onError: reportError, }); handleRef.current = handle; @@ -222,7 +250,7 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUi }); }; // Mount only once; updates are pushed via handle - }, [latestMountStateRef, navigationMountRef]); + }, [latestMountStateRef, navigationMountRef, reportError]); // Push updates when context, apps, or pathname change after initial mount useEffect(() => { @@ -298,23 +326,33 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUi if (activeInternalApplication) { appRootRef.current.render( - - - , + // Separate React tree — the host application's boundary cannot catch errors + // thrown here, so the app slot carries its own. Keyed by app id: a caught + // error would otherwise keep the fallback shown after navigating to another app. + + + + + , ); } else { appRootRef.current.render(null); @@ -322,11 +360,13 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate }: IHostUi }, [ hostReady, activeInternalApplication, + appContextEpoch, ctx, onHeaderChange, onDocumentTitleChange, pathname, aiAssistantOpen, + reportError, requestOpenAi, requestCloseAi, setAiAssistantContext, diff --git a/libs/sdk-ui-pluggable-host/src/components/Root.tsx b/libs/sdk-ui-pluggable-host/src/components/Root.tsx index cb821ce014c..2d3a0950781 100644 --- a/libs/sdk-ui-pluggable-host/src/components/Root.tsx +++ b/libs/sdk-ui-pluggable-host/src/components/Root.tsx @@ -138,5 +138,13 @@ function ReadyRoot({ ctx, callbacks }: { ctx: IPlatformContext; callbacks?: IRoo ); } - return ; + return ( + + ); } diff --git a/libs/sdk-ui-pluggable-host/src/debug.ts b/libs/sdk-ui-pluggable-host/src/debug.ts index 54b31c4b59a..e88ccfd0697 100644 --- a/libs/sdk-ui-pluggable-host/src/debug.ts +++ b/libs/sdk-ui-pluggable-host/src/debug.ts @@ -7,7 +7,7 @@ import { isProduction } from "./lib/isProduction.js"; */ export function debugLog(message: string, ...args: unknown[]): void { if (!isProduction) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.debug(message, ...args); } } diff --git a/libs/sdk-ui-pluggable-host/src/index.ts b/libs/sdk-ui-pluggable-host/src/index.ts index a5ef2872688..bbd7d32005b 100644 --- a/libs/sdk-ui-pluggable-host/src/index.ts +++ b/libs/sdk-ui-pluggable-host/src/index.ts @@ -15,6 +15,11 @@ export { registerAppLifecycleCallbacks } from "./loader/pluggableApplicationsLoa export { mapLegacyUrlToHost, type ILegacyLocation } from "./loader/legacyRedirect.js"; export { Root, type IRootCallbacks } from "./components/Root.js"; +export { + HostErrorBoundary, + type IHostErrorBoundaryProps, + type IHostErrorBoundaryState, +} from "./components/HostErrorBoundary.js"; export { type IAppLifecycleCallbacks } from "./types/lifecycle.js"; export { diff --git a/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.test.ts b/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.test.ts index 2c2dfa7cf25..0a0533f8230 100644 --- a/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.test.ts +++ b/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.test.ts @@ -10,6 +10,10 @@ function stubHash(hash: string): void { vi.stubGlobal("window", { location: { hash } }); } +function stubLocation(search: string, hash: string): void { + vi.stubGlobal("window", { location: { search, hash } }); +} + describe("detectExportMode", () => { it.each([ "#/dashboard/id?mode=export", @@ -36,6 +40,37 @@ describe("detectExportMode", () => { expect(detectExportMode()).toBe(false); }); + // An app on a browser router (no hash) carries the query in the search string instead. Reports + // names the mode the backend exporter opens export_slideshow. + it.each([ + "?mode=export", + "?mode=export&slideWidth=1800&slideHeight=750", + "?MODE=EXPORT", + "?mode=export_slideshow&exportId=abc&pageWidth=1200", + "?pageWidth=1200&mode=export_slideshow", + "?mode=EXPORT_SLIDESHOW", + ])("accepts %s in the search string", (search) => { + stubLocation(search, ""); + + expect(detectExportMode()).toBe(true); + }); + + // The client-side export views are opened by people, not by the exporter, and keep the chrome. + it.each(["?mode=export_pdf", "?mode=export_pptx", "?mode=exported", "?mode=export_", "?widgetId=x", ""])( + "rejects %s in the search string", + (search) => { + stubLocation(search, ""); + + expect(detectExportMode()).toBe(false); + }, + ); + + it("accepts the reports slideshow mode in the hash as well", () => { + stubLocation("", "#/report/x?mode=export_slideshow"); + + expect(detectExportMode()).toBe(true); + }); + it("reports no export outside a browser", () => { vi.stubGlobal("window", undefined); diff --git a/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.ts b/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.ts index c841fa27e13..c019c3dc252 100644 --- a/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.ts +++ b/libs/sdk-ui-pluggable-host/src/platformContext/loadPlatformContext.ts @@ -44,19 +44,25 @@ function throwIfAborted(signal?: AbortSignal) { * * @internal - exported for testing */ +// Dashboards name their export render mode `export`, reports `export_slideshow`; both are what the +// backend exporter opens. +const EXPORT_MODE_VALUES = new Set(["export", "export_slideshow"]); + export function detectExportMode(): boolean { if (typeof window === "undefined") { return false; } - const hash = window.location.hash; - const query = hash.indexOf("?"); - if (query === -1) { - return false; - } - for (const [key, value] of new URLSearchParams(hash.slice(query + 1))) { - const name = key.toLowerCase(); - if ((name === "mode" || name === "displaymode") && value.toLowerCase() === "export") { - return true; + // A hash-routed app carries its query inside the fragment; a browser-routed one in the search + // string. Either may name an export mode. + const { search, hash } = window.location; + const hashQuery = hash.indexOf("?"); + const queries = [search ?? "", hashQuery === -1 ? "" : hash.slice(hashQuery + 1)]; + for (const query of queries) { + for (const [key, value] of new URLSearchParams(query)) { + const name = key.toLowerCase(); + if ((name === "mode" || name === "displaymode") && EXPORT_MODE_VALUES.has(value.toLowerCase())) { + return true; + } } } return false; diff --git a/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.test.tsx b/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.test.tsx new file mode 100644 index 00000000000..c20ce0ea161 --- /dev/null +++ b/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.test.tsx @@ -0,0 +1,132 @@ +// (C) 2026 GoodData Corporation + +import { renderHook } from "@testing-library/react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi"; +import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model"; + +import { getBackend } from "./backend.js"; +import { BackendPlatformContextProvider, useLoadPlatformContext } from "./useLoadPlatformContext.js"; +import { useWorkspaceColorPalette } from "./useWorkspaceColorPalette.js"; +import { useWorkspacePermissions } from "./useWorkspacePermissions.js"; +import { useWorkspaceSettings } from "./useWorkspaceSettings.js"; +import { useWorkspaceTheme } from "./useWorkspaceTheme.js"; + +vi.mock("react-router", () => ({ + useLocation: vi.fn(() => ({ pathname: "/workspace/ws-1/dashboards/" })), +})); + +vi.mock("./backend.js", () => ({ + getBackend: vi.fn(), +})); + +vi.mock("./useWorkspacePermissions.js", () => ({ useWorkspacePermissions: vi.fn() })); +vi.mock("./useWorkspaceSettings.js", () => ({ useWorkspaceSettings: vi.fn() })); +vi.mock("./useWorkspaceColorPalette.js", () => ({ useWorkspaceColorPalette: vi.fn() })); +vi.mock("./useWorkspaceTheme.js", () => ({ useWorkspaceTheme: vi.fn() })); + +const ctx = { + userSettings: { locale: "en-US" }, + theme: undefined, +} as unknown as IPlatformContext; + +function setBackendContextReady(): void { + vi.spyOn(BackendPlatformContextProvider, "subscribe").mockReturnValue(() => {}); + vi.spyOn(BackendPlatformContextProvider, "getResult").mockReturnValue({ state: "ready", ctx }); +} + +function setWorkspaceStates(state: "loading" | "ready"): void { + vi.mocked(useWorkspacePermissions).mockReturnValue( + state === "ready" ? { state, permissions: {} as never } : { state }, + ); + vi.mocked(useWorkspaceSettings).mockReturnValue( + state === "ready" ? { state, settings: { locale: "en-US" } as never } : { state }, + ); + vi.mocked(useWorkspaceColorPalette).mockReturnValue( + state === "ready" ? { state, colorPalette: undefined } : { state }, + ); + vi.mocked(useWorkspaceTheme).mockReturnValue(state === "ready" ? { state, theme: undefined } : { state }); +} + +describe("useLoadPlatformContext", () => { + beforeEach(() => { + vi.clearAllMocks(); + setBackendContextReady(); + vi.mocked(getBackend).mockReturnValue({} as IAnalyticalBackend); + }); + + it("reports loading while workspace data loads for the first time", () => { + setWorkspaceStates("loading"); + + const { result } = renderHook(() => useLoadPlatformContext()); + + expect(result.current.state).toBe("loading"); + }); + + it("keeps the previously rendered context while the same workspace refreshes", () => { + setWorkspaceStates("ready"); + const { result, rerender } = renderHook(() => useLoadPlatformContext()); + expect(result.current.state).toBe("ready"); + const readyCtx = (result.current as { state: "ready"; ctx: IPlatformContext }).ctx; + + setWorkspaceStates("loading"); + vi.mocked(getBackend).mockReturnValue({} as IAnalyticalBackend); + rerender(); + + expect(result.current.state).toBe("ready"); + expect((result.current as { state: "ready"; ctx: IPlatformContext }).ctx).toEqual(readyCtx); + }); + + it("merges a refreshed backend context into the kept workspace context", () => { + setWorkspaceStates("ready"); + const { result, rerender } = renderHook(() => useLoadPlatformContext()); + expect(result.current.state).toBe("ready"); + + const refreshedCtx = { ...ctx, auth: { type: "refreshed" } } as unknown as IPlatformContext; + vi.spyOn(BackendPlatformContextProvider, "getResult").mockReturnValue({ + state: "ready", + ctx: refreshedCtx, + }); + setWorkspaceStates("loading"); + rerender(); + + expect(result.current.state).toBe("ready"); + expect( + (result.current as { state: "ready"; ctx: IPlatformContext & { auth: unknown } }).ctx.auth, + ).toEqual({ type: "refreshed" }); + }); + + it("shows the loader instead of reusing workspace context for a different principal", () => { + setWorkspaceStates("ready"); + const { result, rerender } = renderHook(() => useLoadPlatformContext()); + expect(result.current.state).toBe("ready"); + + const otherPrincipalCtx = { + ...ctx, + user: { login: "someone.else" }, + } as unknown as IPlatformContext; + vi.spyOn(BackendPlatformContextProvider, "getResult").mockReturnValue({ + state: "ready", + ctx: otherPrincipalCtx, + }); + setWorkspaceStates("loading"); + rerender(); + + expect(result.current.state).toBe("loading"); + }); + + it("falls back to the loader when the workspace changes", async () => { + const { useLocation } = vi.mocked(await import("react-router")); + + setWorkspaceStates("ready"); + const { result, rerender } = renderHook(() => useLoadPlatformContext()); + expect(result.current.state).toBe("ready"); + + useLocation.mockReturnValue({ pathname: "/workspace/ws-2/dashboards/" } as never); + setWorkspaceStates("loading"); + rerender(); + + expect(result.current.state).toBe("loading"); + }); +}); diff --git a/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.ts b/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.ts index 45bfac50b23..34388209f9c 100644 --- a/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.ts +++ b/libs/sdk-ui-pluggable-host/src/platformContext/useLoadPlatformContext.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -import { useMemo, useSyncExternalStore } from "react"; +import { useEffect, useMemo, useState, useSyncExternalStore } from "react"; import { useLocation } from "react-router"; @@ -56,7 +56,11 @@ export function useLoadPlatformContext(): IPlatformContextLoadResult { + const [lastReady, setLastReady] = useState< + { workspaceId: string | undefined; result: IPlatformContextLoadResult } | undefined + >(undefined); + + const result = useMemo>(() => { if (backendContext.state !== "ready") { return backendContext; } @@ -119,6 +123,71 @@ export function useLoadPlatformContext(): IPlatformContextLoadResult { + if (result.state === "ready") { + setLastReady({ workspaceId, result }); + } else if (result.state === "error") { + setLastReady(undefined); + } + }, [result, workspaceId]); + + // Fallback served while the same workspace's data refreshes: the previous workspace-scoped + // values merged over the CURRENT backend context, so a refreshed auth reaches the mounted + // app immediately instead of waiting for every workspace request to finish. + const refreshingFallback = useMemo | undefined>(() => { + if ( + backendContext.state !== "ready" || + lastReady === undefined || + lastReady.workspaceId !== workspaceId || + lastReady.result.state !== "ready" || + // A different principal or organization must never see the previous principal's + // permissions/settings, even for the duration of the refresh — fall through to + // the loader instead of merging across principals. + lastReady.result.ctx.user?.login !== backendContext.ctx.user?.login || + lastReady.result.ctx.organization?.id !== backendContext.ctx.organization?.id + ) { + return undefined; + } + const { + currentApplicationScope, + currentWorkspaceId, + workspacePermissions, + workspaceSettings, + colorPalette, + settings, + preferredLocale, + theme, + } = lastReady.result.ctx; + return { + state: "ready", + ctx: { + ...backendContext.ctx, + currentApplicationScope, + currentWorkspaceId, + workspacePermissions, + workspaceSettings, + colorPalette, + settings, + preferredLocale, + theme, + }, + }; + }, [backendContext, lastReady, workspaceId]); + + // Keep the previously rendered context while the same workspace's data refreshes (backend + // re-creation after a token refresh, reload-platform-context requests). Returning "loading" + // here would swap the whole HostUiContainer for a loader, unmounting the mounted app and + // cancelling its in-flight work; the refreshed context is pushed via handle.updateContext + // instead. A workspace change must NOT reuse the old context (permissions/theme belong to + // the previous workspace), so it still falls through to the loader. + if (result.state === "loading" && refreshingFallback !== undefined) { + return refreshingFallback; + } + + return result; } /** diff --git a/libs/sdk-ui-pluggable-host/src/translations/zh-HK.json b/libs/sdk-ui-pluggable-host/src/translations/zh-HK.json index a5acbc6f4aa..959ae705ee9 100644 --- a/libs/sdk-ui-pluggable-host/src/translations/zh-HK.json +++ b/libs/sdk-ui-pluggable-host/src/translations/zh-HK.json @@ -18,7 +18,7 @@ "gs.host.contractExpired.trial.description": "如果您想繼續使用GoodData Cloud,請聯繫我們。", "gs.host.contractExpired.nonTrial.title": "您的存取權限已被鎖定。", "gs.host.contractExpired.nonTrial.description": "請與帳戶管理員聯繫以重新獲得存取權限。", - "gs.host.contractExpired.buttonTitle": "聯繫我哋", + "gs.host.contractExpired.buttonTitle": "聯絡我們", "gs.host.error.failedToLoad": "無法載入", "gs.host.error.pageNotFound": "找不到頁面", "gs.host.error.pageNotFoundDescription": "您正在尋找的頁面不存在,或者您沒有權限存取該頁面。", @@ -27,7 +27,7 @@ "gs.host.error.appBuildExpired": "此應用程式已過期,必須由其作者重新建置後才能再次執行。", "gs.host.error.appSecurityMetadataMissing": "此應用程式缺少必要的安全性中繼資料,無法載入。", "gs.host.demoApp.validUntil": "示範應用程式有效期至 {date}", - "messages.genAi.visualisation.saved.success": "很好!我哋儲存咗您嘅可視化效果。", + "messages.genAi.visualisation.saved.success": "很好!我們已儲存您的可視化。", "messages.genAi.visualisation.saved.error": "糟糕,保存可視化時出現問題。", "messages.genAi.visualisation.saved.error.detail": "{errorType}: {errorMessage}", "messages.genAi.visualisation.link.copied": "可視化連結已複製到剪貼板。", diff --git a/libs/sdk-ui-pluggable-host/src/typings.d.ts b/libs/sdk-ui-pluggable-host/src/typings.d.ts index bd6ead6af8c..c6b2cb068d5 100644 --- a/libs/sdk-ui-pluggable-host/src/typings.d.ts +++ b/libs/sdk-ui-pluggable-host/src/typings.d.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports // eslint-disable-next-line @typescript-eslint/naming-convention interface Window { diff --git a/libs/sdk-ui-pluggable-host/src/ui/DefaultHostUi.tsx b/libs/sdk-ui-pluggable-host/src/ui/DefaultHostUi.tsx index 4b70bfc3c5b..a66d5aaf246 100644 --- a/libs/sdk-ui-pluggable-host/src/ui/DefaultHostUi.tsx +++ b/libs/sdk-ui-pluggable-host/src/ui/DefaultHostUi.tsx @@ -14,6 +14,9 @@ import { type IHostUiNotification, type IPlatformContext, } from "@gooddata/sdk-pluggable-application-model"; +import { resolveLocale } from "@gooddata/sdk-ui"; + +import { HostErrorBoundary } from "../components/HostErrorBoundary.js"; import { HostChrome } from "./HostChrome.js"; import { e } from "./hostChromeBem.js"; @@ -43,6 +46,7 @@ interface IHostUiBridgeProps { appendToChat?: boolean, replaceUserContext?: boolean, ) => void; + onError?: (error: string, context: string) => void; onAppContainerReady: (el: HTMLElement) => void; onReady: ( setCtx: (ctx: IPlatformContext) => void, @@ -63,6 +67,7 @@ function HostUiBridge({ replace, onChatToggleRequested, onAskAiAssistant, + onError, onAppContainerReady, onReady, }: IHostUiBridgeProps) { @@ -88,22 +93,27 @@ function HostUiBridge({ }, []); return ( - -
    - + // Inner boundary tracks the LIVE context locale (updateContext only reaches this + // component's state); the mount-level boundary keeps the mount-time locale and + // remains the last resort for a crash in this component itself. + + +
    + + ); } @@ -121,6 +131,7 @@ function mountDefaultHostUi(options: IHostUiMountOptions): IHostUiMountHandle { replace, onChatToggleRequested, onAskAiAssistant, + onError, } = options; let reactRoot: Root | null = createRoot(container); @@ -138,35 +149,40 @@ function mountDefaultHostUi(options: IHostUiMountOptions): IHostUiMountHandle { // making getAppContainer() safe to call immediately. flushSync(() => { reactRoot?.render( - { - appContainer = el; - }} - onReady={( - setCtx, - setApps, - setPathname, - setHeaderOptions, - setNotification, - setPageTitle, - setChatState, - ) => { - updateCtxFn = setCtx; - updateAppsFn = setApps; - updatePathnameFn = setPathname; - updateHeaderFn = setHeaderOptions; - updateNotificationFn = setNotification; - updateDocumentTitleFn = setPageTitle; - updateChatStateFn = setChatState; - }} - />, + // This root is a separate React tree from the host application's — an error here + // never reaches the application-level boundary, so it needs its own. + + { + appContainer = el; + }} + onReady={( + setCtx, + setApps, + setPathname, + setHeaderOptions, + setNotification, + setPageTitle, + setChatState, + ) => { + updateCtxFn = setCtx; + updateAppsFn = setApps; + updatePathnameFn = setPathname; + updateHeaderFn = setHeaderOptions; + updateNotificationFn = setNotification; + updateDocumentTitleFn = setPageTitle; + updateChatStateFn = setChatState; + }} + /> + , ); }); diff --git a/libs/sdk-ui-pluggable-host/vitest.config.ts b/libs/sdk-ui-pluggable-host/vitest.config.ts index ada2ede7316..1ba5d757bd6 100644 --- a/libs/sdk-ui-pluggable-host/vitest.config.ts +++ b/libs/sdk-ui-pluggable-host/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ define: { PRODUCTION: JSON.stringify(false), diff --git a/libs/sdk-ui-semantic-search/.dependency-cruiser.js b/libs/sdk-ui-semantic-search/.dependency-cruiser.js index 6ec81b6037f..bc1b3b01e98 100644 --- a/libs/sdk-ui-semantic-search/.dependency-cruiser.js +++ b/libs/sdk-ui-semantic-search/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-semantic-search/package.json b/libs/sdk-ui-semantic-search/package.json index c09cfc7a13c..14335f727a4 100644 --- a/libs/sdk-ui-semantic-search/package.json +++ b/libs/sdk-ui-semantic-search/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-semantic-search", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK TypeScript & React skeleton", "license": "MIT", "author": "GoodData Corporation", @@ -96,12 +96,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-semantic-search/src/index.ts b/libs/sdk-ui-semantic-search/src/index.ts index 5e62a2cfaff..0b8b93a9725 100644 --- a/libs/sdk-ui-semantic-search/src/index.ts +++ b/libs/sdk-ui-semantic-search/src/index.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides React hooks and components for semantic search. diff --git a/libs/sdk-ui-semantic-search/src/internal.ts b/libs/sdk-ui-semantic-search/src/internal.ts index 088995ce171..70154e61546 100644 --- a/libs/sdk-ui-semantic-search/src/internal.ts +++ b/libs/sdk-ui-semantic-search/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides React hooks and components for semantic search integration with GoodData apps, diff --git a/libs/sdk-ui-semantic-search/src/localization/bundles/zh-HK.json b/libs/sdk-ui-semantic-search/src/localization/bundles/zh-HK.json index 24c5bd242d2..f1e94785848 100644 --- a/libs/sdk-ui-semantic-search/src/localization/bundles/zh-HK.json +++ b/libs/sdk-ui-semantic-search/src/localization/bundles/zh-HK.json @@ -1,7 +1,7 @@ { - "semantic-search.error.title": "我哋嘅搜索服務暫時關閉。", - "semantic-search.error.text": "我哋正在努力解決此問題。", - "semantic-search.no-results": "未搵到與“{query}”相關嘅結果。", + "semantic-search.error.title": "我們的搜尋服務暫時中斷。", + "semantic-search.error.text": "我們正在努力解決此問題。", + "semantic-search.no-results": "找不到與“{query}”相關的結果。", "semantic-search.results.count": "{count, plural, one {#結果} other {#結果}}", "semantic-search.label": "搜索儀表板或可視化", "semantic-search.placeholder": "搜索儀表板或可視化…", diff --git a/libs/sdk-ui-semantic-search/vitest.config.ts b/libs/sdk-ui-semantic-search/vitest.config.ts index c230e98546e..a190ec64007 100644 --- a/libs/sdk-ui-semantic-search/vitest.config.ts +++ b/libs/sdk-ui-semantic-search/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-tests-app/.dependency-cruiser.js b/libs/sdk-ui-tests-app/.dependency-cruiser.js index 2c0ff17a4f7..80074e2cf8f 100644 --- a/libs/sdk-ui-tests-app/.dependency-cruiser.js +++ b/libs/sdk-ui-tests-app/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ // we need all dep in dev because this package is tooling and its dependencies ocurred in FOSSA scan diff --git a/libs/sdk-ui-tests-app/package.json b/libs/sdk-ui-tests-app/package.json index 4281e13094c..b99a1b9b6e8 100644 --- a/libs/sdk-ui-tests-app/package.json +++ b/libs/sdk-ui-tests-app/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-tests-app", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData SDK e2e tests", "license": "LicenseRef-LICENSE", @@ -64,12 +64,13 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "rolldown": "1.0.3", - "sass": "1.100.0", + "sass": "1.104.0", "typescript": "7.0.2", "vite": "8.0.16" }, diff --git a/libs/sdk-ui-tests-app/src/backend.ts b/libs/sdk-ui-tests-app/src/backend.ts index cb727ef80cb..47fc1a8100a 100644 --- a/libs/sdk-ui-tests-app/src/backend.ts +++ b/libs/sdk-ui-tests-app/src/backend.ts @@ -6,7 +6,7 @@ import tigerFactory, { TigerTokenAuthProvider } from "@gooddata/sdk-backend-tige export function createBackend(): IAnalyticalBackend { // TIGER_API_TOKEN is defined globally by Vite's define configuration - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("createBackend", TIGER_API_TOKEN); return withCaching( diff --git a/libs/sdk-ui-tests-app/vite.config.ts b/libs/sdk-ui-tests-app/vite.config.ts index 4b09cc2f9e4..1cb17d6ce71 100644 --- a/libs/sdk-ui-tests-app/vite.config.ts +++ b/libs/sdk-ui-tests-app/vite.config.ts @@ -100,7 +100,6 @@ function getProductionAliases() { } // https://vitejs.dev/config/ -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ mode }) => { const env = loadEnv(mode, path.resolve(__dirname), ""); // Fallback to process.env if not found in .env file @@ -121,7 +120,7 @@ export default defineConfig(({ mode }) => { } if (backendUrl || workspace) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("Backend URI:", backendUrl, ", ", "Workspace to use:", workspace); } diff --git a/libs/sdk-ui-tests-e2e/package.json b/libs/sdk-ui-tests-e2e/package.json index 9346dc3cf1f..52389029336 100644 --- a/libs/sdk-ui-tests-e2e/package.json +++ b/libs/sdk-ui-tests-e2e/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-tests-e2e", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData SDK e2e tests", "license": "LicenseRef-LICENSE", @@ -60,8 +60,9 @@ "lodash-es": "^4.17.23", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "rolldown": "1.0.3", "typescript": "7.0.2" }, diff --git a/libs/sdk-ui-tests-e2e/playwright/playwright.config.ts b/libs/sdk-ui-tests-e2e/playwright/playwright.config.ts index 71e703abd49..3e2cc896daf 100644 --- a/libs/sdk-ui-tests-e2e/playwright/playwright.config.ts +++ b/libs/sdk-ui-tests-e2e/playwright/playwright.config.ts @@ -8,7 +8,6 @@ import { GoodmockMode, goodmockMode } from "@gooddata/sdk-e2e-utils"; const isRecording = goodmockMode() === GoodmockMode.Record; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ testDir: "./tests", // TODO: remove --pass-with-no-tests flag and fix the workflow logic after integrated test migration to playwright is merged diff --git a/libs/sdk-ui-tests-e2e/rolldown.config.ts b/libs/sdk-ui-tests-e2e/rolldown.config.ts index 85e5d5b870c..76faf6bb98e 100644 --- a/libs/sdk-ui-tests-e2e/rolldown.config.ts +++ b/libs/sdk-ui-tests-e2e/rolldown.config.ts @@ -7,7 +7,6 @@ import { defineConfig } from "rolldown"; const testFiles = globSync("playwright/tests/**/*.spec.ts"); -// eslint-disable-next-line no-restricted-exports export default defineConfig({ input: { "playwright.config": "playwright/playwright.config.ts", diff --git a/libs/sdk-ui-tests-reference-workspace/package.json b/libs/sdk-ui-tests-reference-workspace/package.json index 3597f553dca..c875ce850b5 100644 --- a/libs/sdk-ui-tests-reference-workspace/package.json +++ b/libs/sdk-ui-tests-reference-workspace/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-tests-reference-workspace", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData SDK reference workspace", "license": "LicenseRef-LICENSE", @@ -68,8 +68,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2" }, "dependenciesInfo": "This is package is SDK tool and is not distributed all dependencies must be defined as devDependencies due to FOSSA automated report generation" diff --git a/libs/sdk-ui-tests-scenarios/.dependency-cruiser.js b/libs/sdk-ui-tests-scenarios/.dependency-cruiser.js index a5ae945e012..ed3fa8477a8 100644 --- a/libs/sdk-ui-tests-scenarios/.dependency-cruiser.js +++ b/libs/sdk-ui-tests-scenarios/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [ // not-to-dev-dep: all deps are dev because this package is tooling and its dependencies occurred in FOSSA scan diff --git a/libs/sdk-ui-tests-scenarios/package.json b/libs/sdk-ui-tests-scenarios/package.json index e84088e30a5..3464fc45a7d 100644 --- a/libs/sdk-ui-tests-scenarios/package.json +++ b/libs/sdk-ui-tests-scenarios/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-tests-scenarios", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData React components scenarios project", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -84,8 +84,9 @@ "moment": "^2.29.4", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "spark-md5": "^3.0.0", diff --git a/libs/sdk-ui-tests-scenarios/src/index.ts b/libs/sdk-ui-tests-scenarios/src/index.ts index 1efc9831a82..520c94327c0 100644 --- a/libs/sdk-ui-tests-scenarios/src/index.ts +++ b/libs/sdk-ui-tests-scenarios/src/index.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files import { chartScenarios } from "./scenarios/charts/index.js"; import { executeScenarios } from "./scenarios/execute/base.js"; diff --git a/libs/sdk-ui-tests-scenarios/vitest.config.ts b/libs/sdk-ui-tests-scenarios/vitest.config.ts index bd99974ff20..74317db764f 100644 --- a/libs/sdk-ui-tests-scenarios/vitest.config.ts +++ b/libs/sdk-ui-tests-scenarios/vitest.config.ts @@ -7,7 +7,6 @@ import { defineConfig } from "vitest/config"; // Note: all unit tests which iterate on scenarios are skipped, as neoBackstop is now voting on pre-merge. // The smoke-and-capture sweep is skipped too unless GDC_STORE_DEFS is set (populate-ref sets it). -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { alias: { diff --git a/libs/sdk-ui-tests-storybook/.oxlintrc.json b/libs/sdk-ui-tests-storybook/.oxlintrc.json index a9d874be3b6..59fee69fd7f 100644 --- a/libs/sdk-ui-tests-storybook/.oxlintrc.json +++ b/libs/sdk-ui-tests-storybook/.oxlintrc.json @@ -19,6 +19,12 @@ "@typescript-eslint/no-unsafe-call": "warn", "@typescript-eslint/no-explicit-any": "warn" } + }, + { + "files": ["**/*.stories.tsx"], + "rules": { + "eslint-js/no-restricted-exports": "off" + } } ] } diff --git a/libs/sdk-ui-tests-storybook/.storybook/main.js b/libs/sdk-ui-tests-storybook/.storybook/main.js index 4d2a4bc5f84..d9528a9e3ba 100644 --- a/libs/sdk-ui-tests-storybook/.storybook/main.js +++ b/libs/sdk-ui-tests-storybook/.storybook/main.js @@ -59,7 +59,7 @@ function makePackageStylesAlias(packageName) { }; } -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default { stories: ["../stories/**/*.stories.@(ts|tsx)"], addons: ["@storybook/addon-a11y"], diff --git a/libs/sdk-ui-tests-storybook/.storybook/preview.js b/libs/sdk-ui-tests-storybook/.storybook/preview.js index cbb2de5e03d..d0c4e40b5f2 100644 --- a/libs/sdk-ui-tests-storybook/.storybook/preview.js +++ b/libs/sdk-ui-tests-storybook/.storybook/preview.js @@ -9,5 +9,5 @@ export const preview = { }, }; -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default preview; diff --git a/libs/sdk-ui-tests-storybook/eslint.config.ts b/libs/sdk-ui-tests-storybook/eslint.config.ts index 9125c118f98..2d4d9945685 100644 --- a/libs/sdk-ui-tests-storybook/eslint.config.ts +++ b/libs/sdk-ui-tests-storybook/eslint.config.ts @@ -11,12 +11,6 @@ export default [ }, }, }, - { - files: ["**/*.stories.tsx"], - rules: { - "no-restricted-exports": "off", - }, - }, { files: ["**/*.generated.stories.tsx"], rules: { diff --git a/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_full-featured_icon_0_document_0_desktop.png b/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_full-featured_icon_0_document_0_desktop.png index 22c59ba267b..7984af19aa9 100644 Binary files a/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_full-featured_icon_0_document_0_desktop.png and b/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_full-featured_icon_0_document_0_desktop.png differ diff --git a/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_themed_0_document_0_desktop.png b/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_themed_0_document_0_desktop.png index bc8cb87c5ba..3b02dd93571 100644 Binary files a/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_themed_0_document_0_desktop.png and b/libs/sdk-ui-tests-storybook/neobackstop/output/reference/storybook_chromium_15_Ui_UiIcon_-_themed_0_document_0_desktop.png differ diff --git a/libs/sdk-ui-tests-storybook/package.json b/libs/sdk-ui-tests-storybook/package.json index 294fee2cccf..885b46c5056 100644 --- a/libs/sdk-ui-tests-storybook/package.json +++ b/libs/sdk-ui-tests-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-tests-storybook", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData React components storybook", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -94,8 +94,9 @@ "moment": "^2.29.4", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "react-intl": "10.1.25", diff --git a/libs/sdk-ui-tests-storybook/scripts/neobackstop-cleanup-artifacts.cjs b/libs/sdk-ui-tests-storybook/scripts/neobackstop-cleanup-artifacts.cjs index fc967247b61..4279dadd854 100644 --- a/libs/sdk-ui-tests-storybook/scripts/neobackstop-cleanup-artifacts.cjs +++ b/libs/sdk-ui-tests-storybook/scripts/neobackstop-cleanup-artifacts.cjs @@ -15,11 +15,10 @@ const fs = require("fs"); const path = require("path"); -const currentDir = __dirname; -const neoBackstopDir = path.join(currentDir, "../neobackstop"); +const neoBackstopDir = path.join(__dirname, "../neobackstop"); const references = "reference"; -// eslint-disable-next-line no-console +// oxlint-disable-next-line eslint-js/no-console console.log(`Cleaning artifacts from ${neoBackstopDir}/output`); const outputPath = path.join(neoBackstopDir, "output"); @@ -30,7 +29,7 @@ const outputConfig = path.join(htmlReportPath, "config.js"); const keepPassingScreenshots = process.env.KEEP_ALL_ARTIFACTS === "true"; if (!fs.existsSync(outputConfig)) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("No backstop output, skipping cleanup of test artifacts"); process.exit(1); } @@ -67,9 +66,9 @@ if (fs.existsSync(referenceDirectory)) { const allFiles = fs.readdirSync(referenceDirectory); const pngFiles = allFiles.filter((file) => file.endsWith(".png")); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Found ${pngFiles.length} PNG files in reference directory`); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Config contains ${data.tests.length} test results from this matrix run`); let removedCount = 0; @@ -95,7 +94,7 @@ if (fs.existsSync(referenceDirectory)) { // Failed test - keep the file keptCount++; } else if (status !== "pass" && status !== "fail") { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`File ${file} has unrecognized test status: "${status}"`); keptCount++; } @@ -105,16 +104,16 @@ if (fs.existsSync(referenceDirectory)) { removedCount++; } } catch (error) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Warning: Could not process ${file}: ${error.message}`); errorCount++; } }); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Cleanup summary: ${removedCount} removed, ${keptCount} kept, ${errorCount} errors`); } else { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("Reference directory does not exist, skipping reference cleanup"); } @@ -140,7 +139,7 @@ if (fs.existsSync(testDirectory)) { fs.unlinkSync(filePath); } } catch (error) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Warning: Could not process test file ${file}: ${error.message}`); } }); @@ -166,7 +165,7 @@ if (fs.existsSync(htmlReportPath)) { fs.unlinkSync(filePath); } } catch (error) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Warning: Could not process test file ${file}: ${error.message}`); } }); diff --git a/libs/sdk-ui-tests-storybook/stories/_infra/toBackstop.ts b/libs/sdk-ui-tests-storybook/stories/_infra/toBackstop.ts index 9451564f681..babcc12b852 100644 --- a/libs/sdk-ui-tests-storybook/stories/_infra/toBackstop.ts +++ b/libs/sdk-ui-tests-storybook/stories/_infra/toBackstop.ts @@ -79,7 +79,7 @@ async function processStoryFile(file: string): Promise { const defaultStory = storiesFromFile["default"]; if (!defaultStory) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`No default story, skipping file: ${file}`); return []; } @@ -137,7 +137,7 @@ export async function toBackstopJson(filterFiles?: string[]): Promise 0) { @@ -148,7 +148,7 @@ export async function toBackstopJson(filterFiles?: string[]): Promise normalizedFilter.has(path.normalize(path.join("stories/_infra", file))), ); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Filtered to ${files.length} story files from goodchanges detections`); } @@ -167,7 +167,7 @@ export async function toBackstopJson(filterFiles?: string[]): Promise { diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/axis/LabelSubsection.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/axis/LabelSubsection.stories.tsx index 04aca0eda2c..eac77058ef3 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/axis/LabelSubsection.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/axis/LabelSubsection.stories.tsx @@ -1,6 +1,6 @@ // (C) 2020-2026 GoodData Corporation -/* oxlint-disable sonarjs/no-identical-functions */ +// oxlint-disable sonarjs/no-identical-functions import { useState } from "react"; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/legend/LegendSection.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/legend/LegendSection.stories.tsx index 71f47c3e2c3..693fcb5bdb0 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/legend/LegendSection.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/configurationControls/legend/LegendSection.stories.tsx @@ -1,6 +1,6 @@ // (C) 2020-2026 GoodData Corporation -/* oxlint-disable sonarjs/no-identical-functions */ +// oxlint-disable sonarjs/no-identical-functions import { useState } from "react"; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/filters/AttributeFilter@next/TextFilter.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/filters/AttributeFilter@next/TextFilter.stories.tsx index 3cad95bf13c..5daa8ce9517 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/filters/AttributeFilter@next/TextFilter.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/filters/AttributeFilter@next/TextFilter.stories.tsx @@ -64,7 +64,7 @@ const compactParam = (p: unknown): unknown => { const logCallback = (name: string, ...params: unknown[]) => { const compact = params.map(compactParam); const paramsStr = compact.length > 0 ? ` ${JSON.stringify(compact)}` : ""; - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`${name}${paramsStr}`); action(name)(...params); }; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Dialog/Dialog.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Dialog/Dialog.stories.tsx index e02dc8a35a6..929dda91e8c 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Dialog/Dialog.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Dialog/Dialog.stories.tsx @@ -37,7 +37,7 @@ const DialogExamples = memo(function DialogExamples() { }; const onExportSubmit = (values: any) => { - console.log("values: ", values); // eslint-disable-line no-console + console.log("values: ", values); // oxlint-disable-line eslint-js/no-console setExportDialogOpen(false); }; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EditableLabel/EditableLabelExamples.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EditableLabel/EditableLabelExamples.stories.tsx index 32e64a8c952..db5155634e1 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EditableLabel/EditableLabelExamples.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EditableLabel/EditableLabelExamples.stories.tsx @@ -18,7 +18,7 @@ const EditableLabelExamples = memo(function EditableLabelExamples() {

    Simple example

    console.log(val)} // eslint-disable-line no-console + onSubmit={(val) => console.log(val)} // oxlint-disable-line eslint-js/no-console value={"Edit me!"} /> diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EmbedInsightDialog/NumericInput.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EmbedInsightDialog/NumericInput.stories.tsx index f9edc6e9dd4..d03a1e9cddf 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EmbedInsightDialog/NumericInput.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/EmbedInsightDialog/NumericInput.stories.tsx @@ -14,7 +14,7 @@ function NumericInputExamples() { const [value, setValue] = useState("400"); const onValueChanged = (e: string) => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(e); setValue(e); }; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Form/Form.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Form/Form.stories.tsx index 695094b8878..e4aba05ce1a 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Form/Form.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Form/Form.stories.tsx @@ -36,7 +36,7 @@ const FormExamples = memo(function FormExamples() {

    Search field with placeholder and autofocus

    console.log(val)} // eslint-disable-line no-console + onChange={(val) => console.log(val)} // oxlint-disable-line eslint-js/no-console placeholder="Search attributes..." clearOnEsc isSearch diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Header/Header.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Header/Header.stories.tsx index 1c9b431fade..3cba7ce20bc 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Header/Header.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Header/Header.stories.tsx @@ -109,11 +109,11 @@ const getPicker = (selectedWorkspace: any) => ( function HeaderExamples() { const onHeaderLogoClick = useCallback((...args: any[]) => { - console.log("onHeaderLogoClick", ...args); // eslint-disable-line no-console + console.log("onHeaderLogoClick", ...args); // oxlint-disable-line eslint-js/no-console }, []); const onMenuClick = useCallback((menuItem: any) => { - console.log("onMenuClick: ", menuItem); // eslint-disable-line no-console + console.log("onMenuClick: ", menuItem); // oxlint-disable-line eslint-js/no-console }, []); const getDarkBranding = useCallback((): Partial => { diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Input/Input.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Input/Input.stories.tsx index 6283e624f6e..673c6bd108a 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Input/Input.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/kit/Input/Input.stories.tsx @@ -16,7 +16,7 @@ const AutofocsExamples = memo(function AutofocsExamples() {

    Input without autofocus

    console.log(val)} // eslint-disable-line no-console + onChange={(val) => console.log(val)} // oxlint-disable-line eslint-js/no-console placeholder="Search attributes..." autofocus={autofocus1} /> @@ -26,7 +26,7 @@ const AutofocsExamples = memo(function AutofocsExamples() {

    Input with autofocus

    console.log(val)} // eslint-disable-line no-console + onChange={(val) => console.log(val)} // oxlint-disable-line eslint-js/no-console placeholder="Search attributes..." autofocus /> @@ -38,7 +38,7 @@ const AutofocsExamples = memo(function AutofocsExamples() {

    console.log(val)} // eslint-disable-line no-console + onChange={(val) => console.log(val)} // oxlint-disable-line eslint-js/no-console placeholder="Search attributes..." autofocus /> diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/ui/assets.d.ts b/libs/sdk-ui-tests-storybook/stories/visual-regression/ui/assets.d.ts index 353df13226e..1f7cabe290f 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/ui/assets.d.ts +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/ui/assets.d.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*.png" { const content: string; diff --git a/libs/sdk-ui-tests-storybook/stories/visual-regression/visualizations/pivotTable/index.stories.tsx b/libs/sdk-ui-tests-storybook/stories/visual-regression/visualizations/pivotTable/index.stories.tsx index 7630d1ff9eb..17fb891ef9d 100644 --- a/libs/sdk-ui-tests-storybook/stories/visual-regression/visualizations/pivotTable/index.stories.tsx +++ b/libs/sdk-ui-tests-storybook/stories/visual-regression/visualizations/pivotTable/index.stories.tsx @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable sonarjs/no-identical-functions */ +// oxlint-disable sonarjs/no-identical-functions import { action } from "storybook/actions"; diff --git a/libs/sdk-ui-tests-storybook/vite.config.ts b/libs/sdk-ui-tests-storybook/vite.config.ts index 8a4181e4464..674120d540e 100644 --- a/libs/sdk-ui-tests-storybook/vite.config.ts +++ b/libs/sdk-ui-tests-storybook/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vite"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ plugins: [ { diff --git a/libs/sdk-ui-theme-provider/.dependency-cruiser.js b/libs/sdk-ui-theme-provider/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-theme-provider/.dependency-cruiser.js +++ b/libs/sdk-ui-theme-provider/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-theme-provider/package.json b/libs/sdk-ui-theme-provider/package.json index b9bc62ce7d7..292d46e4bd5 100644 --- a/libs/sdk-ui-theme-provider/package.json +++ b/libs/sdk-ui-theme-provider/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-theme-provider", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - Theme provider", "license": "MIT", "author": "GoodData Corporation", @@ -85,8 +85,9 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", diff --git a/libs/sdk-ui-theme-provider/src/index.ts b/libs/sdk-ui-theme-provider/src/index.ts index 1fc5f2d6b21..4d1b2dbfde7 100644 --- a/libs/sdk-ui-theme-provider/src/index.ts +++ b/libs/sdk-ui-theme-provider/src/index.ts @@ -1,6 +1,6 @@ // (C) 2020-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides tools to make your application support themes. diff --git a/libs/sdk-ui-theme-provider/src/internal.ts b/libs/sdk-ui-theme-provider/src/internal.ts index 54e87dcd59f..1859c71e61c 100644 --- a/libs/sdk-ui-theme-provider/src/internal.ts +++ b/libs/sdk-ui-theme-provider/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2024-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { blueLightTheme } from "./defaultThemes/blueLight.js"; export { greenDarkTheme } from "./defaultThemes/greenDark.js"; diff --git a/libs/sdk-ui-theme-provider/src/variablesSpec/allThemeCssVariables.ts b/libs/sdk-ui-theme-provider/src/variablesSpec/allThemeCssVariables.ts index 83f3a2f43d9..e65a714ec09 100644 --- a/libs/sdk-ui-theme-provider/src/variablesSpec/allThemeCssVariables.ts +++ b/libs/sdk-ui-theme-provider/src/variablesSpec/allThemeCssVariables.ts @@ -13,6 +13,7 @@ import { internalModelerThemeVariables } from "./internal/modeler.js"; import { internalSpacingThemeVariables } from "./internal/spacing.js"; import { internalTableThemeVariables } from "./internal/table.js"; import { internalTabsThemeVariables } from "./internal/tabs.js"; +import { internalTooltipThemeVariables } from "./internal/tooltip.js"; import { internalTransitionThemeVariables } from "./internal/transition.js"; // Derived import { derivedPaletteBaseThemeVariables } from "./theme-derived/palette-base-derived.js"; @@ -85,6 +86,7 @@ export const themeInternalCssVariables: ThemeInternalCssVariable[] = [ ...internalIconThemeVariables, ...internalTabsThemeVariables, ...internalTableThemeVariables, + ...internalTooltipThemeVariables, // Modeler ...internalModelerThemeVariables, ]; diff --git a/libs/sdk-ui-theme-provider/src/variablesSpec/internal/tooltip.ts b/libs/sdk-ui-theme-provider/src/variablesSpec/internal/tooltip.ts new file mode 100644 index 00000000000..c05a462f073 --- /dev/null +++ b/libs/sdk-ui-theme-provider/src/variablesSpec/internal/tooltip.ts @@ -0,0 +1,10 @@ +// (C) 2026 GoodData Corporation +import { type ThemeInternalCssVariable } from "../types.js"; + +export const internalTooltipThemeVariables: ThemeInternalCssVariable[] = [ + { + type: "internal", + variableName: "--gd-tooltip-maxWidth", + defaultValue: "300px", + }, +]; diff --git a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/log.ts b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/log.ts index 904cc0ec601..6a92eb1735b 100644 --- a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/log.ts +++ b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/log.ts @@ -13,7 +13,7 @@ export function logError(message: unknown) { * @internal */ export function logSuccess(message: string) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(chalk.green(message)); } @@ -21,7 +21,7 @@ export function logSuccess(message: string) { * @internal */ export function logInfo(message: string) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(chalk.gray(message)); } diff --git a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.test.ts b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.test.ts index 6d92ca2c6d2..bf5f8abe750 100644 --- a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.test.ts +++ b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.test.ts @@ -19,6 +19,27 @@ describe("normalizeCssVariableValue", () => { expect(normalizeCssVariableValue("rgb(232, 247, 252)")).toBe("rgba(232,247,252,1)"); }); + it("does not precision-correct plain numeric channels near a half boundary", () => { + expect(normalizeCssVariableValue("rgb(1.4999999, 0, 0)")).toBe("rgba(1,0,0,1)"); + }); + + it("rounds half-value channels up through the percentage round-trip like sass itself does", () => { + // 216.5 in 0-255 space prints as 84.9019607843%, which converts back to 216.4999...; + // the specification value (#8ad9f1) was derived from sass rounding 216.5 up to 217. + expect(normalizeCssVariableValue("rgb(53.9215686275%, 84.9019607843%, 94.3137254902%)")).toBe( + "rgba(138,217,241,1)", + ); + }); + + it("normalizes the percentage-channel form emitted by sass 1.104+", () => { + expect(normalizeCssVariableValue("rgb(90.7843137255%, 96.9803921569%, 98.862745098%)")).toBe( + "rgba(232,247,252,1)", + ); + expect(normalizeCssVariableValue("rgba(0%, 87.6862745098%, 64.0609570253%, 0.5)")).toBe( + "rgba(0,224,163,0.5)", + ); + }); + it("rounds fractional color channels emitted by sass 1.79+", () => { expect(normalizeCssVariableValue("rgb(231.5, 247.3, 252.1)")).toBe("rgba(232,247,252,1)"); expect(normalizeCssVariableValue("rgba(217.8806818182, 242.8375, 250.4193181818, 0.5)")).toBe( diff --git a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.ts b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.ts index 937db9b4517..223762a277f 100644 --- a/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.ts +++ b/libs/sdk-ui-theme-provider/src/variablesSpec/validate/validate.ts @@ -322,16 +322,28 @@ function normalizeColorsInValue(value: string): string { ); // Normalize rgb()/rgba() values to consistent rgba() format. - // Sass 1.79+ no longer rounds color channels, so derived colors are emitted with - // fractional channels (e.g. rgb(231.5, 247.3, 252.1)) - round them to integers so - // they compare equal to the hex/rgba values in the specification. + // Sass 1.79+ no longer rounds color channels, and since 1.104 such fractional-channel colors + // are serialized in the CSS Color 4 percentage form (e.g. rgb(90.78%,96.98%,98.86%)) - parse + // both numeric and percentage channels and round them to integers so they compare equal to + // the hex/rgba values in the specification. + // The percentage form prints ~10 decimals, so a channel that is exactly N.5 in 0-255 space + // (e.g. 216.5 -> "84.9019607843%") converts back to fractionally BELOW the half (216.4999...) + // and would round down where Sass's own serializer rounded up. Snap to 6 decimals first so the + // conversion rounds the same way the specification values were derived. + const channelTo255 = (channel: string) => { + if (!channel.endsWith("%")) { + return Math.round(parseFloat(channel)); + } + const raw = (parseFloat(channel) * 255) / 100; + return Math.round(Math.round(raw * 1e6) / 1e6); + }; result = result.replace( - /rgba?\(([\d.]+),([\d.]+),([\d.]+)(?:,([\d.]+))?\)/g, + /rgba?\(([\d.]+%?),([\d.]+%?),([\d.]+%?)(?:,([\d.]+%?))?\)/g, (_match, r: string, g: string, b: string, a: string | undefined) => { - const red = Math.round(parseFloat(r)); - const green = Math.round(parseFloat(g)); - const blue = Math.round(parseFloat(b)); - const alpha = a === undefined ? 1 : parseFloat(a); + const red = channelTo255(r); + const green = channelTo255(g); + const blue = channelTo255(b); + const alpha = a === undefined ? 1 : a.endsWith("%") ? parseFloat(a) / 100 : parseFloat(a); // Round alpha to 2 decimal places for consistent comparison const alphaRounded = Math.round(alpha * 100) / 100; return `rgba(${red},${green},${blue},${alphaRounded})`; diff --git a/libs/sdk-ui-theme-provider/vitest.config.ts b/libs/sdk-ui-theme-provider/vitest.config.ts index aa740c79335..c725edaef1c 100644 --- a/libs/sdk-ui-theme-provider/vitest.config.ts +++ b/libs/sdk-ui-theme-provider/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-vis-commons/.dependency-cruiser.js b/libs/sdk-ui-vis-commons/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/sdk-ui-vis-commons/.dependency-cruiser.js +++ b/libs/sdk-ui-vis-commons/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/sdk-ui-vis-commons/package.json b/libs/sdk-ui-vis-commons/package.json index b130786828b..f597fb9adbe 100644 --- a/libs/sdk-ui-vis-commons/package.json +++ b/libs/sdk-ui-vis-commons/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-vis-commons", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - common functionality for different types of visualizations", "license": "MIT", "author": "GoodData Corporation", @@ -94,12 +94,13 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", - "sass": "1.100.0", + "sass": "1.104.0", "stylelint": "16.26.1", "typescript": "7.0.2", "vitest": "4.1.8", diff --git a/libs/sdk-ui-vis-commons/src/index.ts b/libs/sdk-ui-vis-commons/src/index.ts index f27425c0ba6..1fb290042d7 100644 --- a/libs/sdk-ui-vis-commons/src/index.ts +++ b/libs/sdk-ui-vis-commons/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides functions commonly used when building visualizations. diff --git a/libs/sdk-ui-vis-commons/vitest.config.ts b/libs/sdk-ui-vis-commons/vitest.config.ts index 7a2409a5cd6..cec43c95cfd 100644 --- a/libs/sdk-ui-vis-commons/vitest.config.ts +++ b/libs/sdk-ui-vis-commons/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui-web-components/.dependency-cruiser.js b/libs/sdk-ui-web-components/.dependency-cruiser.js index 4fac81d3ec7..46c6122af39 100644 --- a/libs/sdk-ui-web-components/.dependency-cruiser.js +++ b/libs/sdk-ui-web-components/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { // Excluding PublicLibraryRules, as we are importing CSS files to the build forbidden: [...DefaultRules, ...DefaultSdkRules /*, ...PublicLibraryRules*/], diff --git a/libs/sdk-ui-web-components/package.json b/libs/sdk-ui-web-components/package.json index 9c1440246f3..3cff83b3440 100644 --- a/libs/sdk-ui-web-components/package.json +++ b/libs/sdk-ui-web-components/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui-web-components", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Web Components wrapper for GoodData.UI", "license": "LicenseRef-LICENSE", "author": "GoodData Corporation", @@ -101,8 +101,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", diff --git a/libs/sdk-ui-web-components/scripts/copy-dts.js b/libs/sdk-ui-web-components/scripts/copy-dts.js index 86431e14855..55d9bc5f15e 100644 --- a/libs/sdk-ui-web-components/scripts/copy-dts.js +++ b/libs/sdk-ui-web-components/scripts/copy-dts.js @@ -1,4 +1,5 @@ -// (C) 2022-2025 GoodData Corporation +// (C) 2022-2026 GoodData Corporation + import fs from "fs"; import path from "path"; @@ -22,7 +23,7 @@ const copyDeclarationFiles = () => { filesToCopy.forEach((file) => { if (fs.existsSync(path.join(srcDir, file))) { fs.copyFileSync(path.join(srcDir, file), path.join(destDir, file)); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Copied ${file} to esm directory`); } else { console.error(`${file} not found in temp directory`); diff --git a/libs/sdk-ui-web-components/src/index.ts b/libs/sdk-ui-web-components/src/index.ts index d32d7c9ff74..29ccaab7786 100644 --- a/libs/sdk-ui-web-components/src/index.ts +++ b/libs/sdk-ui-web-components/src/index.ts @@ -1,6 +1,6 @@ // (C) 2022-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This module registers GoodData's Custom Elements for Dashboard and Insight embedding. diff --git a/libs/sdk-ui-web-components/src/tigerBackend.ts b/libs/sdk-ui-web-components/src/tigerBackend.ts index c620c4a491c..f600c35ee00 100644 --- a/libs/sdk-ui-web-components/src/tigerBackend.ts +++ b/libs/sdk-ui-web-components/src/tigerBackend.ts @@ -1,6 +1,6 @@ // (C) 2022-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files import { type IAnalyticalBackend, type IAnalyticalBackendConfig } from "@gooddata/sdk-backend-spi"; import { tigerFactory } from "@gooddata/sdk-backend-tiger"; @@ -20,9 +20,10 @@ export const tigerBackend = (config?: IAnalyticalBackendConfig, implConfig?: any /** * @deprecated This will be removed in the next major release, please use the named export "tigerFactory" instead */ -// eslint-disable-next-line no-restricted-exports +// oxlint-disable-next-line eslint-js/no-restricted-exports export default tigerBackend; // Re-export Tiger backend, so it can be imported by user as a separate entry point // This helps us with chunking and keeping the original entry point slim +// oxlint-disable-next-line eslint-js/no-restricted-syntax export * from "@gooddata/sdk-backend-tiger"; diff --git a/libs/sdk-ui-web-components/vite.config.ts b/libs/sdk-ui-web-components/vite.config.ts index 7fff3c2cf18..9fad78730dc 100644 --- a/libs/sdk-ui-web-components/vite.config.ts +++ b/libs/sdk-ui-web-components/vite.config.ts @@ -14,7 +14,6 @@ const sdkModelDependency = npmPackage.dependencies?.["@gooddata/sdk-model"] ?? " const sdkModelVersion = sdkModelDependency.replace(/[\^~]/, ""); const projectDir = dirname(fileURLToPath(import.meta.url)); -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ command, mode }) => { // Load env file based on `mode` in the current working directory. const env = loadEnv(mode, projectDir, ""); diff --git a/libs/sdk-ui-web-components/vitest.config.ts b/libs/sdk-ui-web-components/vitest.config.ts index e8188177383..f1f67daac91 100644 --- a/libs/sdk-ui-web-components/vitest.config.ts +++ b/libs/sdk-ui-web-components/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/libs/sdk-ui/package.json b/libs/sdk-ui/package.json index 5cfa94aa3d8..d22d1544365 100644 --- a/libs/sdk-ui/package.json +++ b/libs/sdk-ui/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/sdk-ui", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData.UI SDK - Core", "license": "MIT", "author": "GoodData Corporation", @@ -103,8 +103,9 @@ "happy-dom": "20.12.0", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "raf": "3.4.1", "react": "19.1.1", "react-dom": "19.1.1", diff --git a/libs/sdk-ui/src/base/errors/errorHandling.test.ts b/libs/sdk-ui/src/base/errors/errorHandling.test.ts index f53f8c0390b..de1644ebe31 100644 --- a/libs/sdk-ui/src/base/errors/errorHandling.test.ts +++ b/libs/sdk-ui/src/base/errors/errorHandling.test.ts @@ -5,6 +5,7 @@ import { describe, expect, it } from "vitest"; import { + AbortError, ContractExpired, DataTooLargeError, NoDataError, @@ -15,6 +16,7 @@ import { import { convertError } from "./errorHandling.js"; import { + CancelledSdkError, type ContractExpiredSdkError, ErrorCodes, UnexpectedSdkError, @@ -35,6 +37,7 @@ describe("convertErrors", () => { ["protected data", new ProtectedDataError("access denied"), ErrorCodes.PROTECTED_REPORT], ["unauthenticated", new NotAuthenticated("access denied"), ErrorCodes.UNAUTHORIZED], ["contract expired", new ContractExpired("TRIAL"), ErrorCodes.CONTRACT_EXPIRED], + ["abort", new AbortError("canceled"), ErrorCodes.CANCELLED], ["bogus object", {}, ErrorCodes.UNKNOWN_ERROR], ["bogus string", "fun times", ErrorCodes.UNKNOWN_ERROR], ]; @@ -50,6 +53,10 @@ describe("convertErrors", () => { expect((converted as ContractExpiredSdkError).tier).toBe("TRIAL"); }); + it("converts abort to CancelledSdkError", () => { + expect(convertError(new AbortError("canceled"))).toBeInstanceOf(CancelledSdkError); + }); + it("leaves GoodDataSdkError as is", () => { const error = new UnexpectedSdkError("test"); diff --git a/libs/sdk-ui/src/base/errors/errorHandling.ts b/libs/sdk-ui/src/base/errors/errorHandling.ts index 0930bda5dcc..7ba3bbe09c2 100644 --- a/libs/sdk-ui/src/base/errors/errorHandling.ts +++ b/libs/sdk-ui/src/base/errors/errorHandling.ts @@ -145,6 +145,8 @@ export function convertError(error: unknown): GoodDataSdkError { sdkError.authenticationFlow = (error as NotAuthenticated).authenticationFlow; return sdkError; } + case AnalyticalBackendErrorTypes.ABORT: + return new CancelledSdkError(ErrorCodes.CANCELLED, error); default: return new UnexpectedSdkError(ErrorCodes.UNKNOWN_ERROR, error); } diff --git a/libs/sdk-ui/src/base/localization/bundles/de-DE.json b/libs/sdk-ui/src/base/localization/bundles/de-DE.json index 8f741195dcf..cb14c53dd3b 100644 --- a/libs/sdk-ui/src/base/localization/bundles/de-DE.json +++ b/libs/sdk-ui/src/base/localization/bundles/de-DE.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Die Visualisierung mit dieser URL existiert nicht.", "visualization.ErrorMessageResultCacheMissing": "Visualisierung muss aktualisiert werden", "visualization.ErrorDescriptionResultCacheMissing": "Die zugrundeliegenden Daten haben sich geändert oder sind abgelaufen. Aktualisieren Sie die Seite.", + "visualization.ErrorMessageContractExpired": "Ihr Zugriff ist gesperrt.", + "visualization.ErrorDescriptionContractExpired": "Wenden Sie sich an den Kontoadministrator, um wieder Zugriff zu erhalten.", "visualization.ErrorDescriptionMissingMapboxToken": "Die Geo-Diagramm-Konfiguration benötigt einen gültigen API-Zugriffstoken.", "visualization.tooltip.resetZoom": "Vergrößern zurücksetzen", "visualization.tooltip.interaction": "Klicken Sie das Diagramm um zu drillen", @@ -1097,6 +1099,7 @@ "richText.no_data": "Keine Daten", "richText.multiple_data": "Mehrere Artikel", "richText.no_fetch": "Daten konnten nicht abgerufen werden", + "richText.restricted": "eingeschränkt", "recurrence.starts": "Beginnt am", "recurrence.repeats": "Wiederholt", "recurrence.types.hourly": "Stündlich", diff --git a/libs/sdk-ui/src/base/localization/bundles/en-AU.json b/libs/sdk-ui/src/base/localization/bundles/en-AU.json index de6dbe625e0..effb50e8d28 100644 --- a/libs/sdk-ui/src/base/localization/bundles/en-AU.json +++ b/libs/sdk-ui/src/base/localization/bundles/en-AU.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "The visualization with this URL does not exist.", "visualization.ErrorMessageResultCacheMissing": "Visualisation needs refresh", "visualization.ErrorDescriptionResultCacheMissing": "The underlying data has changed or expired. Please refresh the page.", + "visualization.ErrorMessageContractExpired": "Your access is locked.", + "visualization.ErrorDescriptionContractExpired": "Contact the account administrator to regain access.", "visualization.ErrorDescriptionMissingMapboxToken": "The Geochart configuration requires a valid API access token.", "visualization.tooltip.resetZoom": "Reset zoom", "visualization.tooltip.interaction": "Click chart to drill", @@ -1097,6 +1099,7 @@ "richText.no_data": "No data", "richText.multiple_data": "Multiple items", "richText.no_fetch": "Data could not be retrieved", + "richText.restricted": "restricted", "recurrence.starts": "Starts on", "recurrence.repeats": "Repeats", "recurrence.types.hourly": "Hourly", diff --git a/libs/sdk-ui/src/base/localization/bundles/en-GB.json b/libs/sdk-ui/src/base/localization/bundles/en-GB.json index 84f95d868ed..651696edb65 100644 --- a/libs/sdk-ui/src/base/localization/bundles/en-GB.json +++ b/libs/sdk-ui/src/base/localization/bundles/en-GB.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "The visualisation with this URL does not exist.", "visualization.ErrorMessageResultCacheMissing": "Visualisation needs refresh", "visualization.ErrorDescriptionResultCacheMissing": "The underlying data has changed or expired. Please refresh the page.", + "visualization.ErrorMessageContractExpired": "Your access is locked.", + "visualization.ErrorDescriptionContractExpired": "Contact the account administrator to regain access.", "visualization.ErrorDescriptionMissingMapboxToken": "The Geochart configuration requires a valid API access token.", "visualization.tooltip.resetZoom": "Reset zoom", "visualization.tooltip.interaction": "Click chart to drill", @@ -1097,6 +1099,7 @@ "richText.no_data": "No data", "richText.multiple_data": "Multiple items", "richText.no_fetch": "Data could not be retrieved", + "richText.restricted": "restricted", "recurrence.starts": "Starts on", "recurrence.repeats": "Repeats", "recurrence.types.hourly": "Hourly", diff --git a/libs/sdk-ui/src/base/localization/bundles/es-419.json b/libs/sdk-ui/src/base/localization/bundles/es-419.json index 9e13a228ce3..1b7978551db 100644 --- a/libs/sdk-ui/src/base/localization/bundles/es-419.json +++ b/libs/sdk-ui/src/base/localization/bundles/es-419.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "La visualización con esta URL no existe.", "visualization.ErrorMessageResultCacheMissing": "Hay que actualizar la visualización", "visualization.ErrorDescriptionResultCacheMissing": "Los datos subyacentes han cambiado o han caducado. Actualiza la página.", + "visualization.ErrorMessageContractExpired": "Su acceso está bloqueado.", + "visualization.ErrorDescriptionContractExpired": "Contacte con el administrador de la cuenta para volver a tener acceso.", "visualization.ErrorDescriptionMissingMapboxToken": "La configuración del gráfico geográfico requiere un token de acceso a la API válido.", "visualization.tooltip.resetZoom": "Restablecer acercar/alejar", "visualization.tooltip.interaction": "Haga clic en el gráfico para explorar", @@ -1097,6 +1099,7 @@ "richText.no_data": "No hay datos", "richText.multiple_data": "Varios elementos", "richText.no_fetch": "No se pudieron recuperar los datos", + "richText.restricted": "restringido", "recurrence.starts": "Comienza el", "recurrence.repeats": "Repite", "recurrence.types.hourly": "Por hora", diff --git a/libs/sdk-ui/src/base/localization/bundles/es-ES.json b/libs/sdk-ui/src/base/localization/bundles/es-ES.json index f28cae56704..82d32ebd189 100644 --- a/libs/sdk-ui/src/base/localization/bundles/es-ES.json +++ b/libs/sdk-ui/src/base/localization/bundles/es-ES.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "La visualización con esta URL no existe.", "visualization.ErrorMessageResultCacheMissing": "Hay que actualizar la visualización", "visualization.ErrorDescriptionResultCacheMissing": "Los datos subyacentes han cambiado o han caducado. Actualiza la página.", + "visualization.ErrorMessageContractExpired": "Su acceso se ha bloqueado.", + "visualization.ErrorDescriptionContractExpired": "Contacte con el administrador de la cuenta para volver a tener acceso.", "visualization.ErrorDescriptionMissingMapboxToken": "La configuración del gráfico geográfico requiere un token de acceso a la API válido.", "visualization.tooltip.resetZoom": "Restablecer acercar/alejar", "visualization.tooltip.interaction": "Haga clic en el gráfico para explorar", @@ -1097,6 +1099,7 @@ "richText.no_data": "No hay datos", "richText.multiple_data": "Varios artículos", "richText.no_fetch": "No se han podido recuperar los datos", + "richText.restricted": "restringido", "recurrence.starts": "Comienza el", "recurrence.repeats": "Repite", "recurrence.types.hourly": "Cada hora", diff --git a/libs/sdk-ui/src/base/localization/bundles/fi-FI.json b/libs/sdk-ui/src/base/localization/bundles/fi-FI.json index 4d9f4a7e4b6..f97962f69a5 100644 --- a/libs/sdk-ui/src/base/localization/bundles/fi-FI.json +++ b/libs/sdk-ui/src/base/localization/bundles/fi-FI.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Visualisointia tällä URL-osoitteella ei ole olemassa.", "visualization.ErrorMessageResultCacheMissing": "Visualisointi tarvitsee päivityksen", "visualization.ErrorDescriptionResultCacheMissing": "Taustatiedot muuttuivat tai vanhentuivat. Päivitä sivu.", + "visualization.ErrorMessageContractExpired": "Pääsysi on lukittu.", + "visualization.ErrorDescriptionContractExpired": "Ota yhteyttä tilin ylläpitäjään pääsyn palauttamiseksi.", "visualization.ErrorDescriptionMissingMapboxToken": "Geokaavion määritys vaatii voimassa olevan API-käyttöoikeustokenin.", "visualization.tooltip.resetZoom": "Nollaa zoomaus", "visualization.tooltip.interaction": "Klikkaa kaaviota porautuaksesi", @@ -1097,6 +1099,7 @@ "richText.no_data": "Ei tietoja", "richText.multiple_data": "Useita kohteita", "richText.no_fetch": "Tietoja ei voitu noutaa", + "richText.restricted": "rajoitettu", "recurrence.starts": "Alkaa", "recurrence.repeats": "Toistuu", "recurrence.types.hourly": "Tunneittain", diff --git a/libs/sdk-ui/src/base/localization/bundles/fr-CA.json b/libs/sdk-ui/src/base/localization/bundles/fr-CA.json index 1e83f81f4d1..8d33306716a 100644 --- a/libs/sdk-ui/src/base/localization/bundles/fr-CA.json +++ b/libs/sdk-ui/src/base/localization/bundles/fr-CA.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "La visualisation avec cette URL n’existe pas.", "visualization.ErrorMessageResultCacheMissing": "La visualisation a besoin d'être rafraîchie", "visualization.ErrorDescriptionResultCacheMissing": "Les données sous-jacentes ont changé ou ont expiré. Actualiser la page.", + "visualization.ErrorMessageContractExpired": "Votre accès est bloqué.", + "visualization.ErrorDescriptionContractExpired": "Contactez l’administrateur du compte pour récupérer l’accès.", "visualization.ErrorDescriptionMissingMapboxToken": "La configuration de graphique géographique nécessite un jeton d’accès à l’API valide.", "visualization.tooltip.resetZoom": "Réinitialiser le zoom", "visualization.tooltip.interaction": "Cliquez sur le graphique pour l’explorer", @@ -1097,6 +1099,7 @@ "richText.no_data": "Aucune donnée", "richText.multiple_data": "Plusieurs articles", "richText.no_fetch": "Les données n’ont pas pu être récupérées", + "richText.restricted": "restreint", "recurrence.starts": "Débute la", "recurrence.repeats": "Se répète", "recurrence.types.hourly": "Toutes les heures", diff --git a/libs/sdk-ui/src/base/localization/bundles/fr-FR.json b/libs/sdk-ui/src/base/localization/bundles/fr-FR.json index 71e5d21ace9..28ba3d49039 100644 --- a/libs/sdk-ui/src/base/localization/bundles/fr-FR.json +++ b/libs/sdk-ui/src/base/localization/bundles/fr-FR.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "La visualisation avec cette URL n'existe pas.", "visualization.ErrorMessageResultCacheMissing": "La visualisation a besoin d'être rafraîchie", "visualization.ErrorDescriptionResultCacheMissing": "Les données sous-jacentes ont changé ou ont expiré. Actualiser la page.", + "visualization.ErrorMessageContractExpired": "Votre accès est bloqué.", + "visualization.ErrorDescriptionContractExpired": "Contactez l'administrateur du compte pour récupérer l'accès.", "visualization.ErrorDescriptionMissingMapboxToken": "La configuration de graphique géographique nécessite un jeton d'accès à l'API valide.", "visualization.tooltip.resetZoom": "Réinitialiser le zoom", "visualization.tooltip.interaction": "Cliquez sur le graphique pour l'explorer", @@ -1097,6 +1099,7 @@ "richText.no_data": "Aucune donnée", "richText.multiple_data": "Plusieurs éléments", "richText.no_fetch": "Les données n'ont pas pu être récupérées", + "richText.restricted": "restreint", "recurrence.starts": "Commence le", "recurrence.repeats": "Répétitions", "recurrence.types.hourly": "Toutes les heures", diff --git a/libs/sdk-ui/src/base/localization/bundles/id-ID.json b/libs/sdk-ui/src/base/localization/bundles/id-ID.json index 3395bd8e839..6731f846a68 100644 --- a/libs/sdk-ui/src/base/localization/bundles/id-ID.json +++ b/libs/sdk-ui/src/base/localization/bundles/id-ID.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Visualisasi dengan URL ini tidak ada.", "visualization.ErrorMessageResultCacheMissing": "Visualisasi perlu disegarkan", "visualization.ErrorDescriptionResultCacheMissing": "Data yang mendasari berubah atau kedaluwarsa. Segarkan halaman.", + "visualization.ErrorMessageContractExpired": "Akses Anda dikunci.", + "visualization.ErrorDescriptionContractExpired": "Hubungi administrator akun untuk mendapatkan kembali akses.", "visualization.ErrorDescriptionMissingMapboxToken": "Konfigurasi Geochart memerlukan token akses API yang valid.", "visualization.tooltip.resetZoom": "Atur ulang zoom", "visualization.tooltip.interaction": "Klik diagram untuk drilling", @@ -1097,6 +1099,7 @@ "richText.no_data": "Tidak ada data", "richText.multiple_data": "Beberapa item", "richText.no_fetch": "Data tidak dapat diambil", + "richText.restricted": "dibatasi", "recurrence.starts": "Mulai pada", "recurrence.repeats": "Berulang", "recurrence.types.hourly": "Per jam", diff --git a/libs/sdk-ui/src/base/localization/bundles/it-IT.json b/libs/sdk-ui/src/base/localization/bundles/it-IT.json index 452e1da987e..ec7336d5526 100644 --- a/libs/sdk-ui/src/base/localization/bundles/it-IT.json +++ b/libs/sdk-ui/src/base/localization/bundles/it-IT.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "La visualizzazione con questo URL non esiste.", "visualization.ErrorMessageResultCacheMissing": "La visualizzazione ha bisogno di una rinfrescata", "visualization.ErrorDescriptionResultCacheMissing": "I dati sottostanti sono cambiati o scaduti. Aggiornare la pagina.", + "visualization.ErrorMessageContractExpired": "L'accesso è bloccato.", + "visualization.ErrorDescriptionContractExpired": "Contattare l'amministratore dell'account per recuperare l'accesso.", "visualization.ErrorDescriptionMissingMapboxToken": "La configurazione di Geochart richiede un token di accesso API valido.", "visualization.tooltip.resetZoom": "Azzeramento zoom", "visualization.tooltip.interaction": "Fai clic sul grafico per eseguire il drill", @@ -1097,6 +1099,7 @@ "richText.no_data": "Nessun dato", "richText.multiple_data": "Voci multiple", "richText.no_fetch": "Non è stato possibile recuperare i dati", + "richText.restricted": "riservato", "recurrence.starts": "Inizia il", "recurrence.repeats": "Ripetizioni", "recurrence.types.hourly": "Orario", diff --git a/libs/sdk-ui/src/base/localization/bundles/ja-JP.json b/libs/sdk-ui/src/base/localization/bundles/ja-JP.json index 7c6edd05b7a..34d1568d8c5 100644 --- a/libs/sdk-ui/src/base/localization/bundles/ja-JP.json +++ b/libs/sdk-ui/src/base/localization/bundles/ja-JP.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "この URL のビジュアライゼーションは存在しません。", "visualization.ErrorMessageResultCacheMissing": "ビジュアライゼーションの刷新が必要", "visualization.ErrorDescriptionResultCacheMissing": "基礎データが変更されたか、有効期限が切れた。ページを更新してください。", + "visualization.ErrorMessageContractExpired": "アクセスはロックされています。", + "visualization.ErrorDescriptionContractExpired": "アカウント管理者に連絡して、アクセス権を再取得してください。", "visualization.ErrorDescriptionMissingMapboxToken": "ジオチャート 設定には有効な API アクセストークンが必要です。", "visualization.tooltip.resetZoom": "ズームをリセット", "visualization.tooltip.interaction": "チャートをクリックしてドリル", @@ -1097,6 +1099,7 @@ "richText.no_data": "データはありません", "richText.multiple_data": "複数項目", "richText.no_fetch": "データを取得できませんでした", + "richText.restricted": "制限あり", "recurrence.starts": "開始日:", "recurrence.repeats": "反復", "recurrence.types.hourly": "/時", diff --git a/libs/sdk-ui/src/base/localization/bundles/ko-KR.json b/libs/sdk-ui/src/base/localization/bundles/ko-KR.json index 9779e799c56..48bb9b89d9b 100644 --- a/libs/sdk-ui/src/base/localization/bundles/ko-KR.json +++ b/libs/sdk-ui/src/base/localization/bundles/ko-KR.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "이 URL의 시각화는 존재하지 않습니다.", "visualization.ErrorMessageResultCacheMissing": "시각화 새로 고침 필요", "visualization.ErrorDescriptionResultCacheMissing": "기본 데이터가 변경되었거나 만료되었습니다. 페이지를 새로 고치십시오.", + "visualization.ErrorMessageContractExpired": "접근이 차단되었습니다.", + "visualization.ErrorDescriptionContractExpired": "계정 관리자에게 연락하여 접근 권한을 복구하세요.", "visualization.ErrorDescriptionMissingMapboxToken": "지리 차트 구성에는 유효한 API 액세스 토큰이 필요합니다.", "visualization.tooltip.resetZoom": "확대/축소 재설정", "visualization.tooltip.interaction": "차트를 클릭하여 드릴다운", @@ -1097,6 +1099,7 @@ "richText.no_data": "데이터 없음", "richText.multiple_data": "다중 항목", "richText.no_fetch": "데이터를 가져올 수 없습니다", + "richText.restricted": "제한됨", "recurrence.starts": "시작 날짜", "recurrence.repeats": "반복", "recurrence.types.hourly": "시간별", diff --git a/libs/sdk-ui/src/base/localization/bundles/nl-NL.json b/libs/sdk-ui/src/base/localization/bundles/nl-NL.json index fa4ac211bab..08685adcee3 100644 --- a/libs/sdk-ui/src/base/localization/bundles/nl-NL.json +++ b/libs/sdk-ui/src/base/localization/bundles/nl-NL.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "De visualisatie met deze URL bestaat niet.", "visualization.ErrorMessageResultCacheMissing": "Visualisatie heeft een opfrisbeurt nodig", "visualization.ErrorDescriptionResultCacheMissing": "De onderliggende gegevens zijn gewijzigd of verlopen. Vernieuw de pagina.", + "visualization.ErrorMessageContractExpired": "Uw toegang is vergrendeld.", + "visualization.ErrorDescriptionContractExpired": "Neem contact op met de accountbeheerder om weer toegang te krijgen.", "visualization.ErrorDescriptionMissingMapboxToken": "U hebt een API-toegangstoken nodig voor de configuratie met de geografische kaart.", "visualization.tooltip.resetZoom": "Zoomen resetten", "visualization.tooltip.interaction": "Klik op grafiek om in te zoomen", @@ -1097,6 +1099,7 @@ "richText.no_data": "Geen gegevens", "richText.multiple_data": "Meerdere items", "richText.no_fetch": "Gegevens konden niet worden opgehaald", + "richText.restricted": "beperkt", "recurrence.starts": "Begint op", "recurrence.repeats": "Herhaalt", "recurrence.types.hourly": "Uurlijks", diff --git a/libs/sdk-ui/src/base/localization/bundles/pl-PL.json b/libs/sdk-ui/src/base/localization/bundles/pl-PL.json index 899c3cf0160..608a187a3d5 100644 --- a/libs/sdk-ui/src/base/localization/bundles/pl-PL.json +++ b/libs/sdk-ui/src/base/localization/bundles/pl-PL.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Wizualizacja z tym adresem URL nie istnieje.", "visualization.ErrorMessageResultCacheMissing": "Wizualizacja wymaga odświeżenia", "visualization.ErrorDescriptionResultCacheMissing": "Dane źródłowe uległy zmianie lub wygasły. Odśwież stronę.", + "visualization.ErrorMessageContractExpired": "Twój dostęp jest zablokowany.", + "visualization.ErrorDescriptionContractExpired": "Skontaktuj się z administratorem konta, aby odzyskać dostęp.", "visualization.ErrorDescriptionMissingMapboxToken": "Konfiguracja Geochart wymaga ważnego tokenu dostępu API.", "visualization.tooltip.resetZoom": "Resetuj powiększenie", "visualization.tooltip.interaction": "Kliknij wykres, aby przewijać", @@ -1097,6 +1099,7 @@ "richText.no_data": "Brak danych", "richText.multiple_data": "Wiele elementów", "richText.no_fetch": "Nie można pobrać danych", + "richText.restricted": "ograniczone", "recurrence.starts": "Zaczyna się od", "recurrence.repeats": "Powtórzenia", "recurrence.types.hourly": "Godzinowy", diff --git a/libs/sdk-ui/src/base/localization/bundles/pt-BR.json b/libs/sdk-ui/src/base/localization/bundles/pt-BR.json index 7d025febd7f..71ee3f324d7 100644 --- a/libs/sdk-ui/src/base/localization/bundles/pt-BR.json +++ b/libs/sdk-ui/src/base/localization/bundles/pt-BR.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "A visualização com essa URL não existe.", "visualization.ErrorMessageResultCacheMissing": "A visualização precisa ser atualizada", "visualization.ErrorDescriptionResultCacheMissing": "Os dados subjacentes mudaram ou expiraram. Atualize a página.", + "visualization.ErrorMessageContractExpired": "Seu acesso está bloqueado.", + "visualization.ErrorDescriptionContractExpired": "Entre em contato com o administrador da conta para recuperar o acesso.", "visualization.ErrorDescriptionMissingMapboxToken": "A configuração do Mapa Geográfico requer um token de acesso válido à API.", "visualization.tooltip.resetZoom": "Redefinir zoom", "visualization.tooltip.interaction": "Clique no gráfico para detalhar", @@ -1097,6 +1099,7 @@ "richText.no_data": "Nenhum dado", "richText.multiple_data": "Vários itens", "richText.no_fetch": "Não foi possível recuperar os dados", + "richText.restricted": "restrito", "recurrence.starts": "Começa em", "recurrence.repeats": "Repete", "recurrence.types.hourly": "Horário", diff --git a/libs/sdk-ui/src/base/localization/bundles/pt-PT.json b/libs/sdk-ui/src/base/localization/bundles/pt-PT.json index 0227f03e837..7da0ce0bde0 100644 --- a/libs/sdk-ui/src/base/localization/bundles/pt-PT.json +++ b/libs/sdk-ui/src/base/localization/bundles/pt-PT.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "A visualização com este URL não existe.", "visualization.ErrorMessageResultCacheMissing": "A visualização precisa ser atualizada", "visualization.ErrorDescriptionResultCacheMissing": "Os dados subjacentes mudaram ou expiraram. Atualize a página.", + "visualization.ErrorMessageContractExpired": "O seu acesso encontra-se bloqueado.", + "visualization.ErrorDescriptionContractExpired": "Para recuperar o acesso, contacte o administrador de conta.", "visualization.ErrorDescriptionMissingMapboxToken": "A configuração do gráfico geográfico exige um token de acesso à API válido.", "visualization.tooltip.resetZoom": "Repor zoom", "visualization.tooltip.interaction": "Clique no gráfico para explorar", @@ -1097,6 +1099,7 @@ "richText.no_data": "Nenhum dado", "richText.multiple_data": "Vários itens", "richText.no_fetch": "Não foi possível recuperar os dados", + "richText.restricted": "restrito", "recurrence.starts": "Começa em", "recurrence.repeats": "Repete", "recurrence.types.hourly": "Horário", diff --git a/libs/sdk-ui/src/base/localization/bundles/ru-RU.json b/libs/sdk-ui/src/base/localization/bundles/ru-RU.json index a583581a289..35dbbe03cf1 100644 --- a/libs/sdk-ui/src/base/localization/bundles/ru-RU.json +++ b/libs/sdk-ui/src/base/localization/bundles/ru-RU.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Визуализации с этим URL не существует.", "visualization.ErrorMessageResultCacheMissing": "Визуализация требует обновления", "visualization.ErrorDescriptionResultCacheMissing": "Исходные данные изменились или устарели. Обновите страницу.", + "visualization.ErrorMessageContractExpired": "Ваш доступ заблокирован.", + "visualization.ErrorDescriptionContractExpired": "Обратитесь к администратору учетной записи, чтобы восстановить доступ.", "visualization.ErrorDescriptionMissingMapboxToken": "Для конфигурации гео-диаграммы требуется действующий токен доступа API.", "visualization.tooltip.resetZoom": "Сбросить масштаб", "visualization.tooltip.interaction": "Щелкните диаграмму для детализации", @@ -1097,6 +1099,7 @@ "richText.no_data": "Нет данных", "richText.multiple_data": "Несколько элементов", "richText.no_fetch": "Не удалось получить данные", + "richText.restricted": "ограничено", "recurrence.starts": "Начинается с", "recurrence.repeats": "Повторы", "recurrence.types.hourly": "Почасовой", diff --git a/libs/sdk-ui/src/base/localization/bundles/sl-SI.json b/libs/sdk-ui/src/base/localization/bundles/sl-SI.json index abe3024b132..e1cbd5eda0b 100644 --- a/libs/sdk-ui/src/base/localization/bundles/sl-SI.json +++ b/libs/sdk-ui/src/base/localization/bundles/sl-SI.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Vizualizacija s tem URL naslovom ne obstaja.", "visualization.ErrorMessageResultCacheMissing": "Vizualizacija potrebuje osvežitev", "visualization.ErrorDescriptionResultCacheMissing": "Osnovni podatki so se spremenili ali potekli. Osvežite stran.", + "visualization.ErrorMessageContractExpired": "Vaš dostop je zaklenjen.", + "visualization.ErrorDescriptionContractExpired": "Za ponovno pridobitev dostopa kontaktirajte skrbnika računa.", "visualization.ErrorDescriptionMissingMapboxToken": "Konfiguracija GeoCharts zahteva veljaven žeton za dostop do API-ja.", "visualization.tooltip.resetZoom": "Ponastavi povečavo", "visualization.tooltip.interaction": "Klikni grafikon za drilldown", @@ -1097,6 +1099,7 @@ "richText.no_data": "Ni podatkov", "richText.multiple_data": "Več predmetov", "richText.no_fetch": "Podatkov ni bilo mogoče pridobiti", + "richText.restricted": "omejeno", "recurrence.starts": "Začne se", "recurrence.repeats": "Ponavlja", "recurrence.types.hourly": "Na uro", diff --git a/libs/sdk-ui/src/base/localization/bundles/th-TH.json b/libs/sdk-ui/src/base/localization/bundles/th-TH.json index 7e86a945744..600e9afd6d3 100644 --- a/libs/sdk-ui/src/base/localization/bundles/th-TH.json +++ b/libs/sdk-ui/src/base/localization/bundles/th-TH.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "ไม่มีการแสดงภาพข้อมูลที่มี URL นี้", "visualization.ErrorMessageResultCacheMissing": "การแสดงภาพข้อมูลต้องรีเฟรช", "visualization.ErrorDescriptionResultCacheMissing": "ข้อมูลพื้นฐานมีการเปลี่ยนแปลงหรือหมดอายุ รีเฟรชหน้า", + "visualization.ErrorMessageContractExpired": "การเข้าถึงของคุณถูกล็อก.", + "visualization.ErrorDescriptionContractExpired": "ติดต่อผู้ดูแลระบบบัญชีเพื่อรับการเข้าถึงอีกครั้ง", "visualization.ErrorDescriptionMissingMapboxToken": "การกำหนดค่า Geochart ต้องใช้โทเคนการเข้าถึง API ที่ถูกต้อง", "visualization.tooltip.resetZoom": "รีเซ็ตการซูม", "visualization.tooltip.interaction": "คลิกแผนภูมิเพื่อเจาะลึก", @@ -1097,6 +1099,7 @@ "richText.no_data": "ไม่มีข้อมูล", "richText.multiple_data": "หลายรายการ", "richText.no_fetch": "ไม่สามารถดึงข้อมูลได้", + "richText.restricted": "ถูกจำกัด", "recurrence.starts": "เริ่มต้นเมื่อ", "recurrence.repeats": "ทำซ้ำ", "recurrence.types.hourly": "รายชั่วโมง", diff --git a/libs/sdk-ui/src/base/localization/bundles/tr-TR.json b/libs/sdk-ui/src/base/localization/bundles/tr-TR.json index 6175870989e..648b4008754 100644 --- a/libs/sdk-ui/src/base/localization/bundles/tr-TR.json +++ b/libs/sdk-ui/src/base/localization/bundles/tr-TR.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Bu URL ile görselleştirme mevcut değil.", "visualization.ErrorMessageResultCacheMissing": "Görselleştirmenin yenilenmesi gerekiyor", "visualization.ErrorDescriptionResultCacheMissing": "Altta yatan veriler değişti veya süresi doldu. Sayfayı yenileyin.", + "visualization.ErrorMessageContractExpired": "Erişiminiz kilitlendi.", + "visualization.ErrorDescriptionContractExpired": "Erişimi yeniden kazanmak için hesap yöneticisiyle iletişime geçin.", "visualization.ErrorDescriptionMissingMapboxToken": "Geochart yapılandırması geçerli bir API erişim belirteci gerektirir.", "visualization.tooltip.resetZoom": "Yakınlaştırmayı sıfırla", "visualization.tooltip.interaction": "Detay için grafiğe tıklayın", @@ -1097,6 +1099,7 @@ "richText.no_data": "Veri yok", "richText.multiple_data": "Birden fazla öğe", "richText.no_fetch": "Veriler alınamadı", + "richText.restricted": "kısıtlı", "recurrence.starts": "Başlangıç tarihi", "recurrence.repeats": "Tekrarlar", "recurrence.types.hourly": "Saatlik", diff --git a/libs/sdk-ui/src/base/localization/bundles/uk-UA.json b/libs/sdk-ui/src/base/localization/bundles/uk-UA.json index 57591de1c70..735abcb0997 100644 --- a/libs/sdk-ui/src/base/localization/bundles/uk-UA.json +++ b/libs/sdk-ui/src/base/localization/bundles/uk-UA.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Visualization із цією URL-адресою не існує.", "visualization.ErrorMessageResultCacheMissing": "Візуалізацію потрібно оновити", "visualization.ErrorDescriptionResultCacheMissing": "Базові дані змінилися або строк їх дії минув. Оновіть сторінку.", + "visualization.ErrorMessageContractExpired": "Ваш доступ заблоковано.", + "visualization.ErrorDescriptionContractExpired": "Зверніться до адміністратора облікового запису, щоб відновити доступ.", "visualization.ErrorDescriptionMissingMapboxToken": "Конфігурація Geochart вимагає дійсний маркер доступу API.", "visualization.tooltip.resetZoom": "Скинути масштаб", "visualization.tooltip.interaction": "Натисніть діаграму, щоб деталізувати", @@ -1097,6 +1099,7 @@ "richText.no_data": "Немає даних", "richText.multiple_data": "Кілька елементів", "richText.no_fetch": "Не вдалося отримати дані", + "richText.restricted": "обмежено", "recurrence.starts": "Починається", "recurrence.repeats": "Повторюється", "recurrence.types.hourly": "Щогодини", diff --git a/libs/sdk-ui/src/base/localization/bundles/vi-VN.json b/libs/sdk-ui/src/base/localization/bundles/vi-VN.json index 3203cd4537b..9e1f0832f8f 100644 --- a/libs/sdk-ui/src/base/localization/bundles/vi-VN.json +++ b/libs/sdk-ui/src/base/localization/bundles/vi-VN.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "Trực quan hóa với URL này không tồn tại.", "visualization.ErrorMessageResultCacheMissing": "Trực quan hóa cần làm mới", "visualization.ErrorDescriptionResultCacheMissing": "Dữ liệu nền tảng đã thay đổi hoặc hết hạn. Làm mới trang.", + "visualization.ErrorMessageContractExpired": "Quyền truy cập của bạn đã bị khóa.", + "visualization.ErrorDescriptionContractExpired": "Liên hệ với quản trị viên tài khoản để lấy lại quyền truy cập.", "visualization.ErrorDescriptionMissingMapboxToken": "Cấu hình GeoChart yêu cầu mã thông báo truy cập API hợp lệ.", "visualization.tooltip.resetZoom": "Đặt lại thu phóng", "visualization.tooltip.interaction": "Nhấp vào biểu đồ để đi sâu", @@ -1097,6 +1099,7 @@ "richText.no_data": "Không có dữ liệu", "richText.multiple_data": "Nhiều mục", "richText.no_fetch": "Không thể truy xuất dữ liệu", + "richText.restricted": "bị hạn chế", "recurrence.starts": "Bắt đầu vào", "recurrence.repeats": "Lặp lại", "recurrence.types.hourly": "Theo giờ", diff --git a/libs/sdk-ui/src/base/localization/bundles/zh-HK.json b/libs/sdk-ui/src/base/localization/bundles/zh-HK.json index aa5063789c7..75d7c83e11c 100644 --- a/libs/sdk-ui/src/base/localization/bundles/zh-HK.json +++ b/libs/sdk-ui/src/base/localization/bundles/zh-HK.json @@ -32,19 +32,19 @@ "gs.header.workspaceSettings": "工作區設置", "gs.header.account.title": "帳戶", "gs.header.projectPicker.home": "起始", - "gs.header.projectPicker.noMatchingWorkspaces": "冇匹配嘅工作區。", + "gs.header.projectPicker.noMatchingWorkspaces": "沒有相符的工作區。", "gs.header.projectPicker.searchPlaceholder.aria": "搜索工作區", "gs.header.projectPicker.pickerName.aria": "工作區選擇器", "gs.header.projectPicker.workspaces": "工作區", "gs.list.all": "所有", "gs.list.allAndCount": "全部 ({count})", "gs.list.allSearchResultsAndCount": "所有搜尋結果 ({count})", - "gs.list.except": "除咗", - "gs.list.is": "係", + "gs.list.except": "除了", + "gs.list.is": "是", "gs.list.isNot": "不等於", - "gs.list.selectionStatus.all": "所有", - "gs.list.selectionStatus.none": "沒有", - "gs.list.selectionStatus.is": "{selection}", + "gs.list.selectionStatus.all": "全部", + "gs.list.selectionStatus.none": "", + "gs.list.selectionStatus.is": "{selection}", "gs.list.selectionStatus.isNot": "{selection}", "gs.list.subtitle.isNot": "不等於 {selection}", "gs.list.search.placeholder": "搜尋...", @@ -58,7 +58,7 @@ "gs.color-picker.currentColor": "當前", "gs.color-picker.newColor": "新增功能", "gs.color-picker.cancelButton": "取消", - "gs.color-picker.okButton": "仲可以", + "gs.color-picker.okButton": "確定", "gs.color-picker.hex": "十六進制", "gs.ui-color-picker.wheel": "色相及飽和度", "gs.ui-color-picker.lightness": "明度", @@ -66,9 +66,9 @@ "gs.ui-color-picker.value": "顏色值", "gs.ui-color-picker.notation": "顏色表示法", "gs.ui-color-picker.cancelButton": "取消", - "gs.ui-color-picker.okButton": "仲可以", - "gs.noData.noMatchingData": "冇匹配嘅數據", - "gs.noData.noDataAvailable": "冇匹配嘅數據", + "gs.ui-color-picker.okButton": "確定", + "gs.noData.noMatchingData": "沒有相符的數據", + "gs.noData.noDataAvailable": "沒有相符的數據", "geochart.scroll.zoom.blocker": "使用 {button} +轆縮放地圖", "geochart.touch.pan.blocker": "用兩根手指移動地圖", "geochart.map.canvas.label": "GeoChart:{title} 地圖畫布", @@ -93,11 +93,11 @@ "visualizations.totals.dropdown.tooltip.nat.disabled.mvf": "按度量值篩選可視化效果時,匯總(總計)聚合不可用。 要使用Rollup (Total),請從篩選器欄中刪除所有度量值篩選器。", "visualizations.totals.dropdown.tooltip.nat.disabled.ranking": "透過排名篩選器篩選可視化效果時,匯總(總計)聚合不可用。 要使用Rollup (Total),請從過濾器欄中刪除排名過濾器。", "visualizations.totals.dropdown.title.med": "中位數", - "visualizations.headline.tertiary.title": "啱", + "visualizations.headline.tertiary.title": "對比", "visualizations.headline.comparison.title.change": "改變", "visualizations.headline.comparison.title.change.positive": "增加", "visualizations.headline.comparison.title.change.negative": "減少", - "visualizations.headline.comparison.title.change.equals": "冇變化", + "visualizations.headline.comparison.title.change.equals": "無變化", "visualizations.headline.comparison.title.ratio": "之", "visualizations.headline.comparison.title.difference": "差異", "visualizations.headline.comparison.title.difference.positive": "增加", @@ -108,8 +108,8 @@ "visualizations.headline.pagination.drill.hint": "可鑽孔。 按Enter或Space鍵繼續。", "visualizations.headline.comparison.ariaLabel.singleValuePositiveChange": "向上 {mainValue}", "visualizations.headline.comparison.ariaLabel.singleValueNegativeChange": "下 {mainValue}", - "visualizations.headline.comparison.ariaLabel.twoValuesPositiveChange": "向上 {mainValue} (區別係{secondaryValue})", - "visualizations.headline.comparison.ariaLabel.twoValuesNegativeChange": "向下 {mainValue} (區別係{secondaryValue})", + "visualizations.headline.comparison.ariaLabel.twoValuesPositiveChange": "上升 {mainValue} (差異為 {secondaryValue})", + "visualizations.headline.comparison.ariaLabel.twoValuesNegativeChange": "下降 {mainValue} (差異為 {secondaryValue})", "visualizations.headline.comparison.ariaLabel.twoValues": "{mainValue} (區別是{secondaryValue})", "visualization.numericValues.k": "k", "visualization.numericValues.m": "M", @@ -121,19 +121,21 @@ "visualization.waterfall.total": "總", "visualization.cluster": "集群", "visualization.anomalies": "異常", - "visualization.ErrorMessageGeneric": "抱歉,我哋無法顯示此可視化效果", + "visualization.ErrorMessageGeneric": "抱歉,我們無法顯示此可視化", "visualization.ErrorDescriptionGeneric": "請與管理員聯繫。", - "visualization.ErrorMessageDataTooLarge": "顯示嘅數據啲過多", + "visualization.ErrorMessageDataTooLarge": "要顯示的數據點過多", "visualization.ErrorDescriptionDataTooLarge": "嘗試應用篩選器。", "visualization.ErrorMessageNoData": "暫無內容", - "visualization.ErrorDescriptionNoData": "冇啱您的篩選器選擇嘅數據。", + "visualization.ErrorDescriptionNoData": "沒有符合您篩選器選擇的數據。", "visualization.ErrorMessageKpi": "錯誤", "visualization.ErrorMessageUnauthorized": "未經授權", "visualization.ErrorDescriptionUnauthorized": "很抱歉,您無權存取此可視化效果。 登錄或請求管理員授予您權限。", - "visualization.ErrorMessageNotFound": "抱歉,我哋搵唔到呢個可視化效果", - "visualization.ErrorDescriptionNotFound": "此URL嘅可視化效果唔存在。", + "visualization.ErrorMessageNotFound": "抱歉,我們找不到此可視化", + "visualization.ErrorDescriptionNotFound": "此URL的可視化不存在。", "visualization.ErrorMessageResultCacheMissing": "可視化需要更新", "visualization.ErrorDescriptionResultCacheMissing": "基礎數據已更改或過期。 重新整理頁面。", + "visualization.ErrorMessageContractExpired": "您的存取權限已被鎖定。", + "visualization.ErrorDescriptionContractExpired": "請與帳戶管理員聯繫以重新獲得存取權限。", "visualization.ErrorDescriptionMissingMapboxToken": "Geochart配置需要有效的API存取令牌。", "visualization.tooltip.resetZoom": "重置縮放", "visualization.tooltip.interaction": "點擊圖表進行切入", @@ -152,16 +154,16 @@ "gs.list.selectAll": "全選", "gs.list.limitExceeded": "抱歉,您已超出限制({limit} )。", "gs.list.limitReached": "警告:已達上限 ({limit}) — 請移除一個值以新增另一個。", - "gs.list.cannotSelectMoreValues": "錯誤:值過多 — 你最多可以新增 {limit} 個值。", - "gs.list.noItemsFound": "未搵到任何項目。", - "gs.list.noResultsMatch": "冇匹配嘅結果", + "gs.list.cannotSelectMoreValues": "錯誤:值過多 — 您最多可以新增 {limit} 個值。", + "gs.list.noItemsFound": "找不到任何項目。", + "gs.list.noResultsMatch": "沒有相符的結果", "gs.list.error": "載入篩選器值時出錯。", "gs.list.back": "返回", "gs.header.upsellButtonText": "升級", "gs.header.countdown.numberOfDaysLeft": "{number} 剩餘天數", "gs.header.countdown.numberOfMonthsLeft": "{number} 剩餘月數", - "gs.header.countdown.oneMonthLeft": "仲剩1個月", - "gs.header.countdown.lastDay": "這是你的最後一天", + "gs.header.countdown.oneMonthLeft": "剩餘1個月", + "gs.header.countdown.lastDay": "這是您的最後一天", "measure.title.suffix.same_period_year_ago": "SP年前", "measure.title.suffix.previous_period": "時期前", "visualizations.measure.arithmetic.sum.title": "{firstMeasureTitle} 和 {secondMeasureTitle} 的總和", @@ -186,7 +188,7 @@ "visualizations.menu.aggregations.columns": "列", "visualizations.menu.aggregations.all-rows": "所有行", "visualizations.menu.aggregations.all-columns": "在所有列中", - "visualizations.menu.aggregations.within-attribute": "係 {attributeName}", + "visualizations.menu.aggregations.within-attribute": "在 {attributeName} 之內", "visualizations.menu.textWrapping": "換行", "visualizations.menu.textWrapping.header": "鏟頭", "visualizations.menu.textWrapping.cell": "儲存格", @@ -379,7 +381,7 @@ "filters.relative.to.invalid.value": "錯誤:無效的期間結束-輸入有效期間或由列表中選擇它。", "filters.relative.to.empty.value": "錯誤:Period end is empty -輸入有效期間或由列表中選擇它。", "filters.floatingRange": "相對周期", - "filters.floatingRange.noMatch": "未搵到匹配項", + "filters.floatingRange.noMatch": "找不到相符項目", "filters.floatingRange.tooBig": "最大值: {limit}", "filters.floatingRange.option.second.offset.history": "{n, plural, other {# 秒前}}", "filters.floatingRange.option.second.offset.today": "此秒", @@ -389,7 +391,7 @@ "filters.floatingRange.option.minute.offset.future": "{n, plural, one {下一分鐘} other {#提前幾分鐘}}", "filters.floatingRange.option.hour.offset.history": "{n, plural, one {上一個鐘頭} other {#鐘頭前}}", "filters.floatingRange.option.hour.offset.today": "呢個鐘頭,", - "filters.floatingRange.option.hour.offset.future": "{n, plural, one {下一个小时} other {#提前幾個鐘頭}}", + "filters.floatingRange.option.hour.offset.future": "{n, plural, one {下一個小時} other {#提前幾個鐘頭}}", "filters.floatingRange.option.day.offset.history": "{n, plural, one {昨天} other {#天前}}", "filters.floatingRange.option.day.offset.today": "今日", "filters.floatingRange.option.day.offset.future": "{n, plural, one {明天} other {#未來幾日}}", @@ -410,7 +412,7 @@ "filters.floatingRange.option.year.offset.future": "{n, plural, one {} other {明年#未來幾年}}", "filters.allTime.title": "所有時間", "filters.allTime.exceptEmptyValues.title": "所有時間(不包括空值)", - "filters.allTime.excludeCurrentPeriod": "唔包括開放期", + "filters.allTime.excludeCurrentPeriod": "排除未結束期間", "filters.emptyValues.label": "{title},空值", "filters.other.heading": "其他", "filters.emptyValues.title": "空值", @@ -429,13 +431,13 @@ "filters.month.title": "月", "filters.month.excludeCurrentPeriod": "本月不予排除", "filters.quarter.title": "季度", - "filters.quarter.excludeCurrentPeriod": "唔包括本季度", + "filters.quarter.excludeCurrentPeriod": "排除本季度", "filters.year.title": "年", - "filters.year.excludeCurrentPeriod": "今年唔包括在內", + "filters.year.excludeCurrentPeriod": "排除今年", "filters.fiscalYear.title": "財政年度", "filters.fiscalQuarter.title": "財政季度", "filters.period.title": "期數", - "filters.period.excludeCurrentPeriod": "唔包括目前期", + "filters.period.excludeCurrentPeriod": "排除目前期間", "filters.excludeCurrentPeriod.unavailable": "不適用於所選日期範圍", "filters.staticPeriod": "靜態周期", "filters.staticPeriod.errors.emptyStartDate": "錯誤:開始日期為空-輸入有效日期以繼續。", @@ -446,9 +448,9 @@ "filters.staticPeriod.errors.invalidEndDate": "錯誤:結束日期無效-使用{format}格式。", "filters.staticPeriod.errors.endDateBeforeStartDate": "錯誤:結束日期無效-設置開始日期之後的日期。", "filters.staticPeriod.errors.endTimeBeforeStartTime": "錯誤:結束時間無效-設置開始時間之後的時間。", - "filters.locked.filter.tooltip": "呢個過濾器上鎖咗", + "filters.locked.filter.tooltip": "此篩選器已鎖定", "attributeFilter.operator.all": "所有", - "attributeFilter.operator.is": "係", + "attributeFilter.operator.is": "是", "attributeFilter.operator.isNot": "不等於", "attributeFilter.operator.contains": "包含", "attributeFilter.operator.doesNotContain": "不包含", @@ -475,7 +477,7 @@ "attributeFilter.text.applyButton.tooltip.emptyValue": "條件中的值為空白。", "attributeFilter.text.applyButton.tooltip.noChanges": "無改應", "attributeFilter.text.validation.valuesLimitReached": "警告:已達上限 ({maxValues}) — 請移除一個值以新增另一個。", - "attributeFilter.text.validation.valuesLimitExceeded": "錯誤:值過多 — 你最多可以新增 {maxValues} 個值。", + "attributeFilter.text.validation.valuesLimitExceeded": "錯誤:值過多 — 您最多可以新增 {maxValues} 個值。", "attributeFilter.text.validation.valueCannotBeEmpty": "錯誤:值不可為空白.", "attributeFilter.text.isNone": "沒有", "attributeFilter.text.summary.plain": "{operator} {value}", @@ -531,7 +533,7 @@ "mvf.operator.between.tooltip.bubble": "包括起始值和結束值。", "mvf.operator.notBetween.tooltip.bubble": "不包括起始值和結束值。", "mvf.treatNullValuesAsZeroLabel": "把空白值視為0", - "mvf.treatNullValuesAsZeroTooltip": "如果篩選條件包括0,則空白值將被視為0並包含喺計算中。", + "mvf.treatNullValuesAsZeroTooltip": "如果篩選條件包括0,則空白值將被視為0並包含在計算中。", "mvf.preview.filterWithoutDimensionality": "{metric} {condition}", "mvf.preview.filterWithoutDimensionalityNoMetric": "{condition}", "mvf.preview.filterSimplified": "為 {condition}", @@ -550,7 +552,7 @@ "mvf.preview.filterWithoutDimensionalityNoMetric.tooltip": "{condition}", "mvf.preview.filterWithDimensionality.tooltip": "{metric} 為 {condition},按每個 {dimensionality}", "mvf.preview.filterWithDimensionalityNoMetric.tooltip": "每個 {dimensionality} 的 {condition}", - "mvf.dimensionality.backwardCompatibilityNote": "「按每個」目前會跟隨可視化的屬性。編輯篩選器將會停止此行為,並讓你選擇要使用的屬性。", + "mvf.dimensionality.backwardCompatibilityNote": "「按每個」目前會跟隨可視化的屬性。編輯篩選器將會停止此行為,並讓您選擇要使用的屬性。", "mvf.dimensionality.forEach": "每個", "mvf.dimensionality.reset": "重置", "mvf.dimensionality.reset.tooltip": "重設為可視化中的所有屬性。", @@ -627,7 +629,7 @@ "measureNumberFormat.numberFormat.template.conditionalColors": "條件顏色", "measureNumberFormat.numberFormat.template.trendSymbols": "趨勢符號", "measureNumberFormat.numberFormat.template.timeFromSeconds": "時間(以秒為單位)", - "measureNumberFormat.numberFormat.template.zeroInsteadOfNull": "零值而唔係空白值", + "measureNumberFormat.numberFormat.template.zeroInsteadOfNull": "以零代替空白值", "measureNumberCustomFormatDialog.title": "自定義格式", "measureNumberCustomFormatDialog.template.preview.title": "格式預覽", "measureNumberCustomFormatDialog.template.title": "糢闆", @@ -693,8 +695,8 @@ "attributesDropdown.relevantValues.tooltip.limits": "的值 {child} 由其他篩選器設置進行篩選。 ", "attributesDropdown.relevantValues.tooltip.parentFiltersLimits": "的值 {child} 按所選 {parents} 設置和附加篩選器設置進行篩選。", "attributesDropdown.noRelevantValues": "無相關值", - "attributesDropdown.noRelevantValues.tooltip": "你喺相關篩選器中嘅選擇導致此篩選器冇匹配嘅值。{break}單擊“顯示全部”以查看所有可用選項。", - "attributesDropdown.irrelevantValues": "選擇唔相關嘅值。{nbsp}", + "attributesDropdown.noRelevantValues.tooltip": "您在相依篩選器中的選擇導致此篩選器沒有相符的值。{break}請點擊“顯示全部”以查看所有可用選項。", + "attributesDropdown.irrelevantValues": "已選擇不相關的值。{nbsp}", "attributesDropdown.irrelevantValues.tooltip": "由於中的 {parents} 更改,所選值不再相關。", "attributesDropdown.irrelevantValues.clear": "清除", "attributesDropdown.showAll": "顯示全部", @@ -702,7 +704,7 @@ "shareDialog.share.dashboard.title": "共享儀表板", "shareDialog.share.dashboard.list.title": "用戶和群組", "shareDialog.share.link.helperText": "Link反映當前的過濾器。 只有具有控制面板訪問權限的用戶才能訪問。", - "shareDialog.share.link.helperTextWithTabs": "連結會反映目前嘅過濾器喺活動標籤上。 只有擁有儀表板存取權限嘅用戶才能存取。", + "shareDialog.share.link.helperTextWithTabs": "連結會反映使用中分頁的目前篩選器。 只有擁有儀表板存取權限的使用者才能存取。", "shareDialog.share.link.title": "連結", "shareDialog.share.link.buttonText": "複製控制面板連結", "shareDialog.share.grantee.share": "共享", @@ -715,8 +717,8 @@ "shareDialog.share.grantee.add.search.error.message": "無法載入使用者和組。 請稍後再試。", "shareDialog.share.grantee.add.label.user": "使用者", "shareDialog.share.grantee.add.label.group": "組", - "shareDialog.share.grantee.add.search.no.matching.items": "冇匹配嘅使用者或組。", - "shareDialog.share.grantee.item.you": "{userName} (你)", + "shareDialog.share.grantee.add.search.no.matching.items": "沒有相符的使用者或群組。", + "shareDialog.share.grantee.item.you": "{userName} (您)", "shareDialog.share.grantee.item.users.count": "{granteeCount} 用戶", "shareDialog.share.grantee.item.creator": "造物主", "shareDialog.share.grantee.item.user.all": "所有使用者", @@ -727,16 +729,16 @@ "shareDialog.share.granular.grantee.permission.share": "可以查看同分享", "shareDialog.share.granular.grantee.permission.view": "可以查看", "shareDialog.share.granular.grantee.permission.remove": "刪除", - "shareDialog.share.granular.granularUser.tooltip.cannotChangeHigher": "你無法刪除或更改具有比您擁有更高權限的使用者的權限。", + "shareDialog.share.granular.granularUser.tooltip.cannotChangeHigher": "您無法刪除或更改具有比您擁有更高權限的使用者的權限。", "shareDialog.share.granular.granularGroup.tooltip.cannotChangeHigher": "您無法刪除或更改具有比您擁有的權限更高的權限的組的權限。", - "shareDialog.share.granular.grantee.tooltip.cannotGrantHigher": "你不能設置高於您擁有的權限。", - "shareDialog.share.granular.granularUser.tooltip.cannotGrantLower": "你設置的權限不能低於具有此用戶的使用者組的權限。", - "shareDialog.share.granular.granularGroup.tooltip.cannotGrantLower": "你設置的權限不能低於包含此組的使用者組的權限。", - "shareDialog.share.granular.granularUser.tooltip.cannotRemoveFromParent": "你無法在此工作區中刪除此使用者的權限。 若要刪除權限,請從父工作區執行此操作。", - "shareDialog.share.granular.granularGroup.tooltip.cannotRemoveFromParent": "你無法在此工作區中刪除此使用者組的權限。 若要刪除權限,請從父工作區執行此操作。", - "shareDialog.share.granular.granularUser.tooltip.noChangeAvailable": "你無法在此工作區中刪除或更改此使用者的權限。 若要編輯權限,請從父工作區執行此操作。", - "shareDialog.share.granular.granularGroup.tooltip.noChangeAvailable": "你無法在此工作區中刪除或更改此使用者組的權限。 若要編輯權限,請從父工作區執行此操作。", - "shareDialog.share.granular.administrator.info": "注意。 你同其他管理員對所有儀表板具有完全存取權限。", + "shareDialog.share.granular.grantee.tooltip.cannotGrantHigher": "您不能設置高於您擁有的權限。", + "shareDialog.share.granular.granularUser.tooltip.cannotGrantLower": "您設置的權限不能低於具有此用戶的使用者組的權限。", + "shareDialog.share.granular.granularGroup.tooltip.cannotGrantLower": "您設置的權限不能低於包含此組的使用者組的權限。", + "shareDialog.share.granular.granularUser.tooltip.cannotRemoveFromParent": "您無法在此工作區中刪除此使用者的權限。 若要刪除權限,請從父工作區執行此操作。", + "shareDialog.share.granular.granularGroup.tooltip.cannotRemoveFromParent": "您無法在此工作區中刪除此使用者組的權限。 若要刪除權限,請從父工作區執行此操作。", + "shareDialog.share.granular.granularUser.tooltip.noChangeAvailable": "您無法在此工作區中刪除或更改此使用者的權限。 若要編輯權限,請從父工作區執行此操作。", + "shareDialog.share.granular.granularGroup.tooltip.noChangeAvailable": "您無法在此工作區中刪除或更改此使用者組的權限。 若要編輯權限,請從父工作區執行此操作。", + "shareDialog.share.granular.administrator.info": "注意。 您同其他管理員對所有儀表板具有完全存取權限。", "shareDialog.share.granular.grantee.permission.label": "許可", "shareDialog.olp.label.suffix.primary": "(主要)", "shareDialog.olp.label.suffix.default": "(預設)", @@ -810,12 +812,12 @@ "embedInsightDialog.code.options.display.configuration": "包含配置", "embedInsightDialog.headLine.embedInsight": "嵌入可視化", "embedInsightDialog.componentType.byReference.tooltip": "嵌入為對通用GD中儲存的可視化效果的引用。 UI react組件。 未來對可視化效果的任何更改(例如可視化類型、量度、屬性)都將反映在嵌入式版本中。", - "embedInsightDialog.componentType.byDefinition.tooltip": "作為可視化定義嵌入到特定GD中。 基於當前所選可視化類型的UI react組件。 在此應用程序中對可視化效果所做的任何未來更改(例如可視化類型、計量、屬性)都不會反映在嵌入式版本中。", - "embedInsightDialog.webComponents.customTitle.tooltip": "直頭喺代碼片段中定義標題嘅文本。", + "embedInsightDialog.componentType.byDefinition.tooltip": "作為可視化定義嵌入到特定GD中。 基於當前所選可視化類型的UI react組件。 在此應用程序中對可視化效果所做的任何未來更改(例如可視化類型、指標、屬性)都不會反映在嵌入式版本中。", + "embedInsightDialog.webComponents.customTitle.tooltip": "直接在代碼片段中定義標題的文本。", "embedInsightDialog.webComponents.customTitle.disabled.tooltip": "啟用“首先顯示標題”。", "embedInsightDialog.prepareEnvironmentMessage": "注意。 要使此代碼段正常工作,請準備您的環境。", "embedInsightDialog.prepareEnvironmentMessage.link": "瞭解更多信息", - "embed.dialog.numeric.input.validation": "該值必須係大於0的數字", + "embed.dialog.numeric.input.validation": "該值必須是大於0的數字", "embed.dialog.numeric.input.warning": "此輸入僅接受大於0的數字", "embedInsightDialog.complete.list.props.message": "嵌入屬性的完整列表", "embedInsightDialog.emptyInsight.byReference": "儲存可視化效果之後,截取的代碼將在此處顯示。", @@ -823,7 +825,7 @@ "embedDialog.tab.react": "反應", "embedDialog.tab.webComponents": "Web組件", "error.overlay.login": "登錄", - "error.overlay.title": "你已被註銷", + "error.overlay.title": "您已被註銷", "error.overlay.text": "請重新登錄以繼續您的工作。", "stylingPicker.title.basic": "預設", "stylingPicker.title.custom": "自定義", @@ -832,7 +834,7 @@ "stylingEditor.dialog.name.required": "無法儲存。 指定名稱。", "stylingEditor.dialog.definition": "定義", "stylingEditor.dialog.definition.required": "無法保存。 指定定義。", - "stylingEditor.dialog.definition.invalid": "無法儲存。 該定義唔係有效嘅JSON對象。", + "stylingEditor.dialog.definition.invalid": "無法儲存。 該定義不是有效的JSON對象。", "stylingEditor.dialog.examples": "糢闆", "stylingEditor.dialog.example.paste": "糊", "stylingPicker.item.edit": "編輯", @@ -841,13 +843,13 @@ "descriptionPanel.button.more": "顯示更多", "descriptionPanel.button.less": "顯示簡要信息", "configurationPanel.drillConfig.learn.more.link": "瞭解更多信息", - "configurationPanel.drillConfig.drillToDashboard.tooltip": "打開相關儀錶板,查看所選數據啲嘅擴展視圖,並保留相關嘅過濾器同上下文。 瞭解更多信息", - "configurationPanel.drillConfig.drillToInsight.tooltip": "打開相關的可視化效果,同時保留原始视圖中應用的篩選器。 瞭解更多信息", - "configurationPanel.drillConfig.drillToUrl.tooltip": "打開一個自定義URL,其中包含基於所選數據啲嘅動態參數。 瞭解更多信息", + "configurationPanel.drillConfig.drillToDashboard.tooltip": "開啟相關儀表板,以查看所選數據點的擴展檢視,並保留相關的篩選器和上下文。 了解更多", + "configurationPanel.drillConfig.drillToInsight.tooltip": "打開相關的可視化效果,同時保留原始視圖中應用的篩選器。 瞭解更多信息", + "configurationPanel.drillConfig.drillToUrl.tooltip": "開啟包含動態參數的自訂URL,參數取決於所選的數據點。 了解更多", "configurationPanel.drillConfig.drilldown": "向下切入", - "configurationPanel.drillConfig.drilldown.tooltip": "在當前可視化效果中向下切入,遵循預定義嘅屬性層次結構,以越嚟越精細嘅級別查看數據。 瞭解更多信息", + "configurationPanel.drillConfig.drilldown.tooltip": "在目前可視化中向下切入,依照預先定義的屬性階層,以越來越精細的層級查看數據。 了解更多", "configurationPanel.drillConfig.drilldown.tooltip.disabled.insight": "在此可視化的配置中,“向下切入”處於關閉狀態。", - "configurationPanel.drillConfig.drilldown.tooltip.disabled.metric": "只能為屬性設置向下切入,而不能為計量設置向下切入。", + "configurationPanel.drillConfig.drilldown.tooltip.disabled.metric": "只能為屬性設置向下切入,而不能為指標設置向下切入。", "configurationPanel.drillConfig.drilldown.emtpy.hierarchy.info": "屬性必須位於層次結構中才能啟用向下切入。", "configurationPanel.drillConfig.selectHierarchy": "選擇層次結構...", "configurationPanel.drillConfig.drilldown.create.hierarchy": "創建屬性層次結構", @@ -861,37 +863,37 @@ "attributeHierarchyEditor.delete.confirm.message": "將刪除使用此層次結構的所有現有向下切入交互。", "attributeHierarchyEditor.remove.attribute": "刪除", "attributeHierarchyEditor.add.attribute": "添加屬性", - "attributeHierarchyEditor.add.attribute.above": "喺上面添加屬性", + "attributeHierarchyEditor.add.attribute.above": "在上方新增屬性", "attributeHierarchyEditor.add.attribute.bellow": "添加屬性在之下", "attributeHierarchyEditor.howToWork.link": "如何使用層次結構", "attributeHierarchyEditor.attribute.dropdown.title": "選擇屬性...", "attributeHierarchyEditor.attribute.dropdown.search": "搜尋...", "attributeHierarchyEditor.note.title": "注意:", - "attributeHierarchyEditor.note.message": "注意: 您定義的屬性層次結構將應用於全域所有可視化效果。 這樣不僅可以針對當前可視化效果進行 {br}向下切入交互,仲可以針對包含層次結構中任何屬性的所有可視化效果進行向下切入交互。", + "attributeHierarchyEditor.note.message": "注意: 您定義的屬性階層將全域套用至所有可視化。 這不僅可讓{br}目前可視化進行向下切入互動,也適用於包含該階層中任何屬性的所有可視化。", "attributeHierarchyEditor.howItWorks.title": "運作方式", "attributeHierarchyEditor.howItWorks.message.heading": "層次結構將應用於所有可視化效果。", - "attributeHierarchyEditor.message.create.success": "很好!我哋創建了屬性階層! ", - "attributeHierarchyEditor.message.update.success": "很好!我哋儲存了屬性階層! ", - "attributeHierarchyEditor.message.delete.success": "很好! 我哋刪除了屬性階層!", + "attributeHierarchyEditor.message.create.success": "很好!我們已建立屬性階層!", + "attributeHierarchyEditor.message.update.success": "很好!我們已儲存屬性階層!", + "attributeHierarchyEditor.message.delete.success": "很好!我們已刪除屬性階層!", "attributeHierarchyEditor.message.create.failed": "創建失敗。 請稍後再試。", "attributeHierarchyEditor.message.update.failed": "儲存失敗。 請稍後再試。", "attributeHierarchyEditor.message.delete.failed": "刪除失敗。 請稍後再試。", "attributeHierarchyEditor.howItWorks.message.content": "向下切入交互不僅將啟用到此可視化效果,而且將啟用包含層次結構中其中一個屬性的所有可視化效果。", "attributeHierarchyList.tooltip.generic.date.title": "通用日期", - "attributeHierarchyList.tooltip.generic.date.info": "此層次結構係自動生成的,適用於任何日期維度。", + "attributeHierarchyList.tooltip.generic.date.info": "此階層是自動產生的,適用於任何日期維度。", "attributeHierarchyList.tooltip.hierarchy.levels": "層次結構級別", "attributeHierarchyList.tooltip.button.edit": "編輯", "catalog.attribute.tooltip.hierarchy.title": "等級制度", "visualizationConfig.interactions.section.title": "相互作用", "visualizationConfig.interactions.section.drilldown": "向下切入", "workspaceHierarchy.lockedInsight": "只有管理員才能儲存對此可視化效果的更改。", - "workspaceHierarchy.inheritedInsight": "你無法編輯或刪除此可視化效果。{br}它經過集中管理和認證。", - "userManagement.workspace.noMatchingItems": "冇匹配嘅工作區。", + "workspaceHierarchy.inheritedInsight": "您無法編輯或刪除此可視化效果。{br}它經過集中管理和認證。", + "userManagement.workspace.noMatchingItems": "沒有相符的工作區。", "userManagement.workspace.searchPlaceholder": "搜索工作區", "userManagement.workspace.searchError": "無法載入工作區。 請稍後再試。", "userManagement.dataSource.searchError": "無法加載數據源。 請稍後再試。", - "userManagement.workspace.emptySelection.user.view": "此使用者冇訪問任何工作區嘅權限。", - "userManagement.workspace.emptySelection.userGroup.view": "此組冇對任何工作區嘅權限。", + "userManagement.workspace.emptySelection.user.view": "此使用者沒有任何工作區的權限。", + "userManagement.workspace.emptySelection.userGroup.view": "此群組沒有任何工作區的權限。", "userManagement.workspace.emptySelection.edit": "未選擇任何工作區。", "userManagement.workspace.title": "添加工作區權限", "userManagement.workspace.edit.title": "編輯工作區權限", @@ -910,61 +912,61 @@ "userManagement.dataSource.title": "添加數據源權限", "userManagement.tab.dataSources": "數據源({count})", "userManagement.buttons.addDataSourcePermission": "添加數據源權限", - "userManagement.dataSources.emptySelection.view": "此使用者對任何數據源都冇權限。", - "userManagement.dataSources.emptySelection.userGroup.view": "此組對任何數據源都冇權限。", + "userManagement.dataSources.emptySelection.view": "此使用者沒有任何數據源的權限。", + "userManagement.dataSources.emptySelection.userGroup.view": "此群組沒有任何數據源的權限。", "userManagement.dataSources.emptySelection.edit": "未選擇數據源。", "userManagement.dataSources.permission.manage": "管理", "userManagement.dataSources.permission.use": "用", "userManagement.dataSources.permission.remove": "刪除", - "userManagement.dataSources.noMatchingItems": "冇匹配嘅數據源。", - "userManagement.users.multipleDataSources.addSuccess": "很好!你已成功向使用者添加權限。", - "userManagement.userGroups.multipleDataSources.addSuccess": "很好! 你已成功向組添加權限。", + "userManagement.dataSources.noMatchingItems": "沒有相符的數據源。", + "userManagement.users.multipleDataSources.addSuccess": "很好!您已成功向使用者添加權限。", + "userManagement.userGroups.multipleDataSources.addSuccess": "很好! 您已成功向組添加權限。", "userManagement.users.multipleDataSources.addError": "向使用者添加權限失敗。", "userManagement.userGroups.multipleDataSources.addError": "向組添加權限失敗。", - "userManagement.dataSources.removeSuccess": "很好!你已成功刪除數據源權限。", + "userManagement.dataSources.removeSuccess": "很好!您已成功刪除數據源權限。", "userManagement.dataSources.removeError": "刪除數據源權限失敗。", - "userManagement.dataSources.changeSuccess": "很好!你已成功更改數據源權限。", + "userManagement.dataSources.changeSuccess": "很好!您已成功更改數據源權限。", "userManagement.dataSources.changeError": "更改數據源權限失敗。", "userManagement.users.deleteLink": "刪除使用者", "userManagement.userGroups.deleteLink": "刪除組", "userManagement.closeButton": "關閉", "userManagement.workspace.removeSuccess": "很好!您已成功刪除權限。", "userManagement.workspace.removeError": "刪除權限失敗。", - "userManagement.workspace.addSuccess": "很好!你已成功添加工作區權限。", + "userManagement.workspace.addSuccess": "很好!您已成功添加工作區權限。", "userManagement.workspace.addError": "添加工作區權限失敗。", - "userManagement.dataSource.addSuccess": "很好!你已成功添加數據源權限。", + "userManagement.dataSource.addSuccess": "很好!您已成功添加數據源權限。", "userManagement.dataSource.addError": "添加數據源權限失敗。", - "userManagement.users.multipleWorkspaces.addSuccess": "很好!你已成功向使用者添加工作區權限。", + "userManagement.users.multipleWorkspaces.addSuccess": "很好!您已成功向使用者添加工作區權限。", "userManagement.users.multipleWorkspaces.addError": "向用戶添加工作區權限失敗。", - "userManagement.userGroups.multipleWorkspaces.addSuccess": "很好!你已成功把工作區權限添加到群組中。", + "userManagement.userGroups.multipleWorkspaces.addSuccess": "很好!您已成功把工作區權限添加到群組中。", "userManagement.userGroups.multipleWorkspaces.addError": "向組添加工作區權限失敗。", - "userManagement.workspace.changeSuccess": "很好!你已成功更改工作區權限。", + "userManagement.workspace.changeSuccess": "很好!您已成功更改工作區權限。", "userManagement.workspace.changeError": "更改工作區權限失敗。", "userManagement.permissions.loadError": "無法重新整理權限。清單可能已過時。", "userManagement.workspace.hierarchicalPermission.yes": "包括子層次結構", "userManagement.workspace.hierarchicalPermission.yes.tooltip": "把權限應用于此工作區及其所有子工作區。", "userManagement.workspace.hierarchicalPermission.no": "此工作區", "userManagement.workspace.hierarchicalPermission.no.tooltip": "僅將權限應用于此工作區。", - "userManagement.workspace.permission.view": "视圖", - "userManagement.workspace.permissionUser.view.tooltip": "使用者可以查看已與佢哋共享嘅儀錶板。", + "userManagement.workspace.permission.view": "檢視", + "userManagement.workspace.permissionUser.view.tooltip": "使用者可以查看已與他們共享的儀表板。", "userManagement.workspace.permissionGroup.view.tooltip": "群組成員可以查看已與群組共享的儀表板。", "userManagement.workspace.permission.viewSaveViews": "查看同儲存視圖", - "userManagement.workspace.permissionUser.viewSaveViews.tooltip": "使用者可以查看已與佢哋共享嘅控制面板,並將控制面板過濾器嘅狀態儲存為個人視圖以備將來使用。", - "userManagement.workspace.permissionGroup.viewSaveViews.tooltip": "群組成員可以查看已與佢哋共享嘅控制面板,並將控制面板篩選器嘅狀態儲存為個人視圖以供將來使用。", + "userManagement.workspace.permissionUser.viewSaveViews.tooltip": "使用者可以查看已與他們共享的儀表板,並將儀表板篩選器的狀態儲存為個人視圖以供日後使用。", + "userManagement.workspace.permissionGroup.viewSaveViews.tooltip": "群組成員可以查看已與他們共享的儀表板,並將儀表板篩選器的狀態儲存為個人視圖以供日後使用。", "userManagement.dataSources.permissionUser.use.tooltip": "使用者可以使用已與其共享的數據源。", "userManagement.dataSources.permissionGroup.use.tooltip": "群組成員可以使用已與群組共享的數據源。", "userManagement.workspace.permission.viewExport": "查看和匯出", "userManagement.workspace.permissionUser.viewExport.tooltip": "涵蓋“查看”權限。 此外,使用者可以把儀表板導出為PDF文件,並將可視化中的表格數據導出為XLSX同CSV文件。", "userManagement.workspace.permissionGroup.viewExport.tooltip": "涵蓋“查看”權限。 此外,小組成員可以把儀表板導出為PDF文件,並將可視化中的表格數據導出為XLSX同CSV文件。", "userManagement.workspace.permission.viewExportSaveViews": "查看、匯出同儲存視圖", - "userManagement.workspace.permissionUser.viewExportSaveViews.tooltip": "涵蓋檢視權限。 此外,使用者可以創建、編輯或刪除儀表板和可視化內容,並查看LDM和計量。 佢哋仲可以將儀表板導出為PDF文件,將可視化中的表格數據匯出為XLSX和CSV文件,並將儀表板篩選器嘅狀態儲存為個人檢視以備將來使用。", - "userManagement.workspace.permissionGroup.viewExportSaveViews.tooltip": "涵蓋檢視權限。 此外,組成員可以創建、編輯或刪除儀錶板同可視化,以及查看邏輯磁碟管理員同計量。 佢哋仲可以把儀錶板匯出為PDF文件,把表格數據由可視化導出到XLSX同CSV文件,並將儀錶板篩選器嘅狀態儲存為個人視圖以備將來使用。", + "userManagement.workspace.permissionUser.viewExportSaveViews.tooltip": "涵蓋檢視權限。 此外,使用者可以建立、編輯或刪除儀表板和可視化,並查看LDM和指標。 他們也可以將儀表板匯出為PDF檔案,將可視化中的表格數據匯出為XLSX和CSV檔案,並將儀表板篩選器的狀態儲存為個人視圖以供日後使用。", + "userManagement.workspace.permissionGroup.viewExportSaveViews.tooltip": "涵蓋檢視權限。 此外,群組成員可以建立、編輯或刪除儀表板和可視化,並查看LDM和指標。 他們也可以將儀表板匯出為PDF檔案,將可視化中的表格數據匯出為XLSX和CSV檔案,並將儀表板篩選器的狀態儲存為個人視圖以供日後使用。", "userManagement.workspace.permission.analyze": "分析", "userManagement.workspace.permissionUser.analyze.tooltip": "涵蓋View同Save Views權限。 此外,用戶可以創建、編輯或刪除控制面板和可視化,以及查看LDM同指標。", - "userManagement.workspace.permissionGroup.analyze.tooltip": "涵蓋View同Save Views權限。 此外,組成員仲可以創建、編輯或刪除功能板同可視化,以及查看LDM同量度。", + "userManagement.workspace.permissionGroup.analyze.tooltip": "涵蓋檢視和儲存檢視權限。 此外,群組成員可以建立、編輯或刪除儀表板和可視化,並查看LDM和指標。", "userManagement.workspace.permission.analyzeExport": "分析與匯出", - "userManagement.workspace.permissionUser.analyzeExport.tooltip": "涵蓋View同Save Views權限。 此外,用戶可以創建、編輯或刪除控制面板和可視化,以及查看LDM同指標。 佢哋仲可以把控制面板導出為PDF文件,並將表格數據由可視化導出為XLSX同CSV文件。", - "userManagement.workspace.permissionGroup.analyzeExport.tooltip": "涵蓋View同Save Views權限。 此外,組成員仲可以創建、編輯或刪除功能板同可視化,以及查看LDM同量度。 佢哋仲可以把控制面板導出為PDF文件,並將表格數據由可視化導出為XLSX同CSV文件。", + "userManagement.workspace.permissionUser.analyzeExport.tooltip": "涵蓋檢視和儲存檢視權限。 此外,使用者可以建立、編輯或刪除儀表板和可視化,並查看LDM和指標。 他們也可以將儀表板匯出為PDF檔案,並將可視化中的表格數據匯出為XLSX和CSV檔案。", + "userManagement.workspace.permissionGroup.analyzeExport.tooltip": "涵蓋檢視和儲存檢視權限。 此外,群組成員可以建立、編輯或刪除儀表板和可視化,並查看LDM和指標。 他們也可以將儀表板匯出為PDF檔案,並將可視化中的表格數據匯出為XLSX和CSV檔案。", "userManagement.workspace.permission.manage": "管理", "userManagement.workspace.permissionUser.manage.tooltip": "涵蓋View (查看)、Analyze (分析)、Export (導出)同Save Views權限。 此外,用戶可以創建、編輯或刪除邏輯數據模型同指標,並且可以不受限制地訪問所有控制面板及其權限。", "userManagement.workspace.permissionGroup.manage.tooltip": "涵蓋View (查看)、Analyze (分析)、Export (導出)同Save Views權限。 此外,組成員可以創建、編輯或刪除邏輯數據模型同指標,並且可以不受限制地訪問所有控制面板及其權限。", @@ -976,11 +978,11 @@ "userManagement.workspace.permission.exportTabular.tooltip": "添加EXPORT_TABULAR權限,以CSV同XLSX格式匯出控制面板同可視化。", "userManagement.workspace.permission.exportPdf": "匯出PDF", "userManagement.workspace.permission.exportPdf.tooltip": "添加EXPORT_PDF權限,以把功能板同可視化導出為PDF文件。", - "userManagement.workspace.permission.createFilterView": "儲存的篩選器视圖", + "userManagement.workspace.permission.createFilterView": "儲存的篩選器視圖", "userManagement.workspace.permission.createFilterView.tooltip": "添加CREATE_FILTER_VIEW權限,以創建和儲存儀表板篩選器組合,以便快速重用。", "userManagement.workspace.permission.createAutomation": "警示和排程匯出", "userManagement.workspace.permission.createAutomation.tooltip": "添加CREATE_AUTOMATIONS權限以創建自動警示和排程匯出。", - "userManagement.workspace.permission.createMetric": "計量", + "userManagement.workspace.permission.createMetric": "指標", "userManagement.workspace.permission.createMetric.tooltip": "新增 CREATE_METRIC 權限,以建立及管理新指標。若要管理任何其他指標,使用者需要擁有其存取權。", "userManagement.workspace.permission.useAiAssistant": "AI助手", "userManagement.workspace.permission.useAiAssistant.tooltip": "添加USE_AI_ASSISTANT權限,以訪問AI聊天機械人並與之交互以處理分析問題。", @@ -990,8 +992,8 @@ "userManagement.workspace.granularPermission.workspaceAccess.title": "工作區存取", "userManagement.workspace.granularPermission.additionalAccess.title": "其他權限", "userManagement.workspace.granularPermission.help": "了解工作區權限", - "userManagement.workspace.granularPermission.view.description": "允許使用者查看已與佢哋或其用戶組共享嘅儀錶板。", - "userManagement.workspace.granularPermission.analyze.description": "允許使用者創建、編輯或刪除儀表板同可視化,以及查看邏輯數據模型( LDM )同計量。", + "userManagement.workspace.granularPermission.view.description": "允許使用者查看已與他們或其使用者群組共享的儀表板。", + "userManagement.workspace.granularPermission.analyze.description": "允許使用者創建、編輯或刪除儀表板同可視化,以及查看邏輯數據模型( LDM )同指標。", "userManagement.workspace.granularPermission.manage.description": "允許使用者建立、編輯或刪除邏輯數據模型和指標,存取所有儀表板,並可不受限制地編輯儀表板權限。", "userManagement.workspace.granularPermission.warning": "謹慎: 此操作將刪除所有多餘的組合。", "userManagement.workspace.permission.remove": "刪除", @@ -1008,81 +1010,81 @@ "userManagement.detail.orgPermission.admin": "管理", "userManagement.detail.orgPermission.admin.tooltip": "組織管理員。 此使用者有權存取任何受保護的操作或資源。", "userManagement.detail.orgPermission.member": "成員", - "userManagement.detail.orgPermission.member.tooltip": "組織成員。 此使用者有權存取佢有權訪問嘅操作或資源。", + "userManagement.detail.orgPermission.member.tooltip": "組織成員。 此使用者可存取其有權限的操作或資源。", "userManagement.userDetail.systemAccount.label": "系統使用者", "userManagement.userDetail.systemAccount.tooltip": "在共享或選擇收件人時,系統使用者不會在任何清單或搜尋結果中向終端使用者顯示。", "userManagement.userDetail.id.label": "識別碼", "userManagement.userDetail.emptyValue": "未設置", - "userManagement.userGroups.emptySelection.view": "此使用者唔係任何組嘅成員。", + "userManagement.userGroups.emptySelection.view": "此使用者不是任何群組的成員。", "userManagement.userGroups.emptySelection.edit": "未選擇任何組。", "userManagement.userDetail.saveButton": "儲存", "userManagement.userDetail.cancelButton": "取消", "userManagement.groupDetail.saveButton": "儲存", "userManagement.groupDetail.cancelButton": "取消", - "userManagement.userDetails.updatedSuccess": "很好!你已成功更改使用者詳細信息。", + "userManagement.userDetails.updatedSuccess": "很好!您已成功更改使用者詳細信息。", "userManagement.userDetails.updatedFailure": "更改使用者詳細信息失敗。", - "userManagement.groupDetails.updatedSuccess": "很好!你已成功更改群組詳細信息。", + "userManagement.groupDetails.updatedSuccess": "很好!您已成功更改群組詳細信息。", "userManagement.groupDetails.updatedFailure": "更改使用者詳細信息組失敗。", - "userManagement.userGroup.addSuccess": "很好!你已成功添加群組。", + "userManagement.userGroup.addSuccess": "很好!您已成功添加群組。", "userManagement.userGroup.addError": "添加組失敗。", - "userManagement.multipleUserGroup.addSuccess": "很好!你已成功把使用者添加到群組中。", + "userManagement.multipleUserGroup.addSuccess": "很好!您已成功把使用者添加到群組中。", "userManagement.multipleUserGroup.addError": "把使用者添加到組失敗。", "userManagement.userGroups.title": "添加到組", - "userManagement.userGroups.noMatchingItems": "冇匹配組。", + "userManagement.userGroups.noMatchingItems": "沒有相符的群組。", "userManagement.userGroups.searchPlaceholder": "搜索組", "userManagement.userGroups.searchError": "無法載入組。 請稍後再試。", - "userManagement.userGroup.removeSuccess": "很好!你已成功刪除該群組。", + "userManagement.userGroup.removeSuccess": "很好!您已成功刪除該群組。", "userManagement.userGroup.removeFailure": "刪除組失敗。", - "userManagement.userGroups.removeSuccess": "很好!你已成功刪除該群組。", + "userManagement.userGroups.removeSuccess": "很好!您已成功刪除該群組。", "userManagement.userGroups.removeFailure": "刪除組失敗。", "userManagement.userGroups.removeTooltip": "從使用者中刪除組。", "userManagement.userGroups.removeDisabledTooltip": "無法由使用者中刪除該組。", "userManagement.deleteDialog.user.title": "刪除使用者?", - "userManagement.deleteDialog.user.body": "使用者將被不可逆地刪除,但其可視化效果同儀錶板將唔受影響。", + "userManagement.deleteDialog.user.body": "使用者將被永久刪除,但其可視化和儀表板不會受到影響。", "userManagement.deleteDialog.user.disabledTooltip": "無法刪除使用者。", "userManagement.deleteDialog.userGroup.adminDisabledTooltip": "無法刪除此組。", "userManagement.deleteDialog.userGroup.nonEmptyDisabledTooltip": "無法刪除此組,因為它包含成員。 ", "userManagement.deleteDialog.multipleUsers.title": "刪除使用者?", - "userManagement.deleteDialog.multipleUsers.body": "{count} 使用者 將被不可逆地刪除,但其可視化效果同儀錶板將唔受影響。", + "userManagement.deleteDialog.multipleUsers.body": "{count} 位使用者將被永久刪除,但其可視化和儀表板不會受到影響。", "userManagement.deleteDialog.group.title": "刪除組?", "userManagement.deleteDialog.group.body": "該組將被不可逆地刪除。 其所有成員都將失去由組權限定義的權利。", "userManagement.deleteDialog.multipleGroups.title": "刪除組?", "userManagement.deleteDialog.multipleGroups.body": "{count} 組 將被不可逆地刪除。 其所有成員都將失去由特定於組的權限定義的權利。", "userManagement.deleteDialog.cancelButton": "取消", "userManagement.deleteDialog.confirmButton": "刪除", - "userManagement.userGroups.deleteSuccess": "很好!你已成功刪除該群組。", + "userManagement.userGroups.deleteSuccess": "很好!您已成功刪除該群組。", "userManagement.userGroups.deleteFailure": "刪除組失敗。", - "userManagement.multipleUserGroups.deleteSuccess": "很好!你已成功刪除該群組。", + "userManagement.multipleUserGroups.deleteSuccess": "很好!您已成功刪除該群組。", "userManagement.multipleUserGroups.deleteFailure": "刪除組失敗。", - "userManagement.createUserGroup.createSuccess": "很好!你已成功創建群組。", + "userManagement.createUserGroup.createSuccess": "很好!您已成功創建群組。", "userManagement.createUserGroup.createFailure": "組的創建失敗。", "userManagement.users.loadingError.title": "無法打開使用者", - "userManagement.users.loadingError.notExist": "你嘗試查看嘅使用者唔存在。", - "userManagement.users.loadingError.unknown": "你嘗試查看的使用者無法載入。", + "userManagement.users.loadingError.notExist": "您嘗試查看的使用者不存在。", + "userManagement.users.loadingError.unknown": "您嘗試查看的使用者無法載入。", "userManagement.userGroups.loadingError.title": "無法打開群組", - "userManagement.userGroups.loadingError.notExist": "你嘗試查看嘅組唔存在。", - "userManagement.userGroups.loadingError.unknown": "你嘗試查看的組無法載入。", - "userManagement.users.deleteSuccess": "很好!你已成功刪除使用者。", + "userManagement.userGroups.loadingError.notExist": "您嘗試查看的群組不存在。", + "userManagement.userGroups.loadingError.unknown": "您嘗試查看的組無法載入。", + "userManagement.users.deleteSuccess": "很好!您已成功刪除使用者。", "userManagement.users.deleteFailure": "刪除使用者失敗。", - "userManagement.multipleUsers.deleteSuccess": "很好!你已成功刪除使用者。", + "userManagement.multipleUsers.deleteSuccess": "很好!您已成功刪除使用者。", "userManagement.multipleUsers.deleteFailure": "刪除使用者失敗。", "userManagement.users.searchError": "無法載入使用者。 請稍後再試。", - "userManagement.users.emptySelection.view": "此組冇任何成員。", + "userManagement.users.emptySelection.view": "此群組沒有任何成員。", "userManagement.users.emptySelection.edit": "未選擇任何使用者。", "userManagement.users.removeUserFromGroup": "從組中刪除使用者。", "userManagement.users.removeUserFromGroupDisabledTooltip": "無法將此使用者由組中刪除。", "userManagement.users.removeUserFromSelection": "移除.", "userManagement.userGroups.removeUserGroupFromSelection": "移除.", - "userManagement.users.noMatchingItems": "冇匹配嘅使用者。", + "userManagement.users.noMatchingItems": "沒有相符的使用者。", "userManagement.users.searchPlaceholder": "搜索使用者", "userManagement.users.title": "添加群組成員", "userManagement.users.cancelButton": "取消", "userManagement.users.addButton": "增加", - "userManagement.users.addSuccess": "很好!你已成功把使用者添加到群組中。", + "userManagement.users.addSuccess": "很好!您已成功把使用者添加到群組中。", "userManagement.users.addFailure": "把用戶添加到組失敗。", - "userManagement.users.addToUserGroupsSuccess": "很好!你已成功把使用者添加到群組中。", + "userManagement.users.addToUserGroupsSuccess": "很好!您已成功把使用者添加到群組中。", "userManagement.users.addToUserGroupsFailure": "把使用者添加到組失敗。", - "userManagement.users.removeSuccess": "很好!你已成功刪除使用者。", + "userManagement.users.removeSuccess": "很好!您已成功刪除使用者。", "userManagement.users.removeFailure": "刪除使用者失敗。", "userManagement.buttons.close": "關閉", "userManagement.admin.alert": "管理員對所有工作區、資源和操作具有完全存取權限。", @@ -1093,10 +1095,11 @@ "userManagement.createUserGroup.dialogTitle": "創建組", "userManagement.createUserGroup.inputPlaceholder": "名字", "richText.placeholder": "使用Markdown設置文本同參考數據的款式:", - "richText.tooltip": "你可以使用Markdown語法嚟設置描述嘅款式:", + "richText.tooltip": "您可以使用Markdown語法來設定描述的樣式:", "richText.no_data": "暫無內容", "richText.multiple_data": "多個項目", "richText.no_fetch": "無法檢索數據", + "richText.restricted": "受限制", "recurrence.starts": "開始于", "recurrence.repeats": "重複", "recurrence.types.hourly": "小時", @@ -1147,7 +1150,7 @@ "table.bulkActions.selectedCountShort": "{selectedCount} 選擇", "table.bulkActions.selectAll": "全選", "table.bulkActions.selectedFiltersCount": "{selectedCount} 選擇", - "table.emptyState.noMatch": "未搵到匹配項", + "table.emptyState.noMatch": "找不到相符項目", "table.emptyState.tryAdjustingFilters": "檢查拼寫或調整過濾器。", "tabs.all": "所有", "tabs.actions": "目前分頁的動作", @@ -1180,7 +1183,7 @@ "gs.header.data": "數據", "gs.header.documentation": "文檔", "gs.header.kpis.new": "儀表板", - "gs.header.metrics": "計量", + "gs.header.metrics": "指標", "gs.header.slack": "鬆弛", "gs.header.submitTicket": "提交票證", "gs.header.university": "大學", @@ -1228,6 +1231,6 @@ "mvf.details.id": "識別碼", "mvf.details.infoButtonLabel": "顯示指標詳細資料", "mvf.details.type": "類型", - "mvf.details.typeValue": "計量", + "mvf.details.typeValue": "指標", "visualization.stackLabel.aria.total": "總計" } diff --git a/libs/sdk-ui/src/base/localization/bundles/zh-Hans.json b/libs/sdk-ui/src/base/localization/bundles/zh-Hans.json index a4fade71319..07c0fbd41f6 100644 --- a/libs/sdk-ui/src/base/localization/bundles/zh-Hans.json +++ b/libs/sdk-ui/src/base/localization/bundles/zh-Hans.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "带此 URL 的可视化不存在。", "visualization.ErrorMessageResultCacheMissing": "可视化效果需要更新", "visualization.ErrorDescriptionResultCacheMissing": "基础数据已更改或过期。请刷新页面。", + "visualization.ErrorMessageContractExpired": "您的访问权限已被锁定。", + "visualization.ErrorDescriptionContractExpired": "请联系账户管理员以重新获得访问权限。", "visualization.ErrorDescriptionMissingMapboxToken": "地理图表配置需要有效的 API 访问令牌", "visualization.tooltip.resetZoom": "重置缩放", "visualization.tooltip.interaction": "点击图表以深入查看", @@ -1097,6 +1099,7 @@ "richText.no_data": "无数据", "richText.multiple_data": "多个项目", "richText.no_fetch": "无法检索数据", + "richText.restricted": "受限", "recurrence.starts": "开始于", "recurrence.repeats": "重复", "recurrence.types.hourly": "每小时", diff --git a/libs/sdk-ui/src/base/localization/bundles/zh-Hant.json b/libs/sdk-ui/src/base/localization/bundles/zh-Hant.json index cfed6715f26..0963fe9be73 100644 --- a/libs/sdk-ui/src/base/localization/bundles/zh-Hant.json +++ b/libs/sdk-ui/src/base/localization/bundles/zh-Hant.json @@ -134,6 +134,8 @@ "visualization.ErrorDescriptionNotFound": "具有此 URL 的視覺化不存在。", "visualization.ErrorMessageResultCacheMissing": "可視化效果需要更新", "visualization.ErrorDescriptionResultCacheMissing": "基礎數據已更改或過期。請刷新頁面。", + "visualization.ErrorMessageContractExpired": "您的存取權限已被鎖定。", + "visualization.ErrorDescriptionContractExpired": "請聯絡帳戶管理員以重新獲得存取權限。", "visualization.ErrorDescriptionMissingMapboxToken": "Geochart 配置需要有效的 API 存取權杖。", "visualization.tooltip.resetZoom": "重設變焦", "visualization.tooltip.interaction": "點擊圖表進行鑽取", @@ -945,7 +947,7 @@ "userManagement.workspace.hierarchicalPermission.yes.tooltip": "將此權限套用至此工作區及其所有子工作區。", "userManagement.workspace.hierarchicalPermission.no": "這個工作區", "userManagement.workspace.hierarchicalPermission.no.tooltip": "僅將權限套用至此工作區。", - "userManagement.workspace.permission.view": "看法", + "userManagement.workspace.permission.view": "檢視", "userManagement.workspace.permissionUser.view.tooltip": "使用者可以查看與其共享的儀表板。", "userManagement.workspace.permissionGroup.view.tooltip": "群組成員可以查看已與群組共用的儀表板。", "userManagement.workspace.permission.viewSaveViews": "查看並儲存視圖", @@ -1097,6 +1099,7 @@ "richText.no_data": "沒有數據", "richText.multiple_data": "多個專案", "richText.no_fetch": "無法檢索數據", + "richText.restricted": "受限制", "recurrence.starts": "開始於", "recurrence.repeats": "重複", "recurrence.types.hourly": "每小時", diff --git a/libs/sdk-ui/src/base/react/useDebugEquality.ts b/libs/sdk-ui/src/base/react/useDebugEquality.ts index bc81d47ffd2..471ae4650e1 100644 --- a/libs/sdk-ui/src/base/react/useDebugEquality.ts +++ b/libs/sdk-ui/src/base/react/useDebugEquality.ts @@ -38,11 +38,11 @@ export const useDebugEquality = ( const shouldPrint = render === "eachRender" || !previousResults || !isEqual(results, previousResults); if (shouldPrint) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.table(results); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("Previous props:", previousProps); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("Current props:", props); } }; diff --git a/libs/sdk-ui/src/base/react/validation/types.test.ts b/libs/sdk-ui/src/base/react/validation/types.test.ts index f472c2bd768..4015dfb319b 100644 --- a/libs/sdk-ui/src/base/react/validation/types.test.ts +++ b/libs/sdk-ui/src/base/react/validation/types.test.ts @@ -2,7 +2,7 @@ // @vitest-environment node -/* oxlint-disable @typescript-eslint/no-empty-object-type */ +// oxlint-disable @typescript-eslint/no-empty-object-type import { describe, expectTypeOf, it } from "vitest"; diff --git a/libs/sdk-ui/src/index.ts b/libs/sdk-ui/src/index.ts index 877d35f1e36..bade738d1cd 100644 --- a/libs/sdk-ui/src/index.ts +++ b/libs/sdk-ui/src/index.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package provides base functionality useful for building React visualizations on top of GoodData. diff --git a/libs/sdk-ui/src/internal.ts b/libs/sdk-ui/src/internal.ts index 68519777334..78a1c969140 100644 --- a/libs/sdk-ui/src/internal.ts +++ b/libs/sdk-ui/src/internal.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { useDebugEquality } from "./base/react/useDebugEquality.js"; export { useDeepMemo } from "./base/react/useDeepMemo.js"; diff --git a/libs/sdk-ui/src/typings.d.ts b/libs/sdk-ui/src/typings.d.ts index b61e7399321..6f9aef68e99 100644 --- a/libs/sdk-ui/src/typings.d.ts +++ b/libs/sdk-ui/src/typings.d.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/libs/util/.dependency-cruiser.js b/libs/util/.dependency-cruiser.js index 7b575751809..593fba33716 100644 --- a/libs/util/.dependency-cruiser.js +++ b/libs/util/.dependency-cruiser.js @@ -7,7 +7,6 @@ import { PublicLibraryRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules, ...PublicLibraryRules], options: DefaultOptions, diff --git a/libs/util/package.json b/libs/util/package.json index af89e25f2bd..876bf7276d1 100644 --- a/libs/util/package.json +++ b/libs/util/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/util", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Utility Functions", "license": "MIT", "author": "GoodData", @@ -61,8 +61,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/libs/util/src/index.ts b/libs/util/src/index.ts index 353ccb32505..8063ff46a2d 100644 --- a/libs/util/src/index.ts +++ b/libs/util/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files /** * This package contains utility functions used in production or test code of multiple different GoodData.UI packages. diff --git a/libs/util/src/testUtils.ts b/libs/util/src/testUtils.ts index c3aa9bf2bb2..89fcdc86544 100644 --- a/libs/util/src/testUtils.ts +++ b/libs/util/src/testUtils.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console /** * Returns a promise which will resolve after the provided number of milliseconds. diff --git a/libs/util/vitest.config.ts b/libs/util/vitest.config.ts index 04ad74ff664..09371ab4118 100644 --- a/libs/util/vitest.config.ts +++ b/libs/util/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/templates/pluggable-module/harness/package.json b/templates/pluggable-module/harness/package.json index 6336b6aeee6..96e6654cc11 100644 --- a/templates/pluggable-module/harness/package.json +++ b/templates/pluggable-module/harness/package.json @@ -1,6 +1,6 @@ { "name": "gdc-app-template-name-harness", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "Standalone harness for gdc-app-template-name module", "license": "UNLICENSED", @@ -60,10 +60,11 @@ "jiti": "2.6.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "rolldown": "1.0.3", - "sass": "1.100.0", + "sass": "1.104.0", "typescript": "7.0.2", "vite": "8.0.16" } diff --git a/templates/pluggable-module/harness/vite.config.ts b/templates/pluggable-module/harness/vite.config.ts index d4f99ed9a39..aeea350bbf6 100644 --- a/templates/pluggable-module/harness/vite.config.ts +++ b/templates/pluggable-module/harness/vite.config.ts @@ -25,12 +25,11 @@ try { }; // oxlint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.info(`No certificates found in ${certDir}, skipping HTTPS configuration`); httpsConfig = undefined; } -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ mode }): UserConfig => { const env = loadEnv(mode, process.cwd(), ""); const isProduction = mode === "production"; diff --git a/templates/pluggable-module/module/package.json b/templates/pluggable-module/module/package.json index f9422fe9b52..17a03ba4259 100644 --- a/templates/pluggable-module/module/package.json +++ b/templates/pluggable-module/module/package.json @@ -1,6 +1,6 @@ { "name": "gdc-app-template-name-module", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "{applicationTemplateTitle} pluggable application module", "license": "UNLICENSED", @@ -76,12 +76,13 @@ "jiti": "2.6.1", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "rolldown": "1.0.3", - "sass": "1.100.0", + "sass": "1.104.0", "typescript": "7.0.2", "vite": "8.0.16", "vitest": "4.1.8" diff --git a/templates/pluggable-module/module/src/index.ts b/templates/pluggable-module/module/src/index.ts index 887a099841d..0ad68c716b0 100644 --- a/templates/pluggable-module/module/src/index.ts +++ b/templates/pluggable-module/module/src/index.ts @@ -1,5 +1,5 @@ // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { pluggableApp } from "./pluggableApp.js"; diff --git a/templates/pluggable-module/module/vite.config.ts b/templates/pluggable-module/module/vite.config.ts index 91da4a522a3..65e5985375d 100644 --- a/templates/pluggable-module/module/vite.config.ts +++ b/templates/pluggable-module/module/vite.config.ts @@ -60,12 +60,11 @@ try { }; // oxlint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.info(`No certificates found in ${certDir}, skipping HTTPS configuration`); httpsConfig = undefined; } -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ mode, command }): UserConfig => { const env = loadEnv(mode, process.cwd(), ""); const port = parseInt(env["PORT"] || "{modulePort}"); diff --git a/templates/pluggable-module/module/vitest.config.ts b/templates/pluggable-module/module/vitest.config.ts index 54fe8b8837b..20748fc55d2 100644 --- a/templates/pluggable-module/module/vitest.config.ts +++ b/templates/pluggable-module/module/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ test: { environment: "happy-dom", diff --git a/tools/app-toolkit/.dependency-cruiser.js b/tools/app-toolkit/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/app-toolkit/.dependency-cruiser.js +++ b/tools/app-toolkit/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/app-toolkit/.oxlintrc.json b/tools/app-toolkit/.oxlintrc.json index b0365de4687..b1fdec484a5 100644 --- a/tools/app-toolkit/.oxlintrc.json +++ b/tools/app-toolkit/.oxlintrc.json @@ -4,6 +4,9 @@ "options": { "typeAware": true }, + "rules": { + "eslint-js/no-console": "off" + }, "overrides": [ { "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], diff --git a/tools/app-toolkit/package.json b/tools/app-toolkit/package.json index fad715d0b24..1f9c0083cb8 100644 --- a/tools/app-toolkit/package.json +++ b/tools/app-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/app-toolkit", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "CLI with useful tools for creating and maintaining GoodData web applications.", "license": "LicenseRef-LICENSE", "author": "GoodData", @@ -71,8 +71,9 @@ "eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-sonarjs": "3.0.6", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/app-toolkit/scripts/preparePackageJson.mjs b/tools/app-toolkit/scripts/preparePackageJson.mjs index 941ad003c3a..8905ec4362c 100644 --- a/tools/app-toolkit/scripts/preparePackageJson.mjs +++ b/tools/app-toolkit/scripts/preparePackageJson.mjs @@ -1,6 +1,6 @@ // (C) 2021-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console import fs from "fs"; import * as path from "path"; import * as process from "process"; @@ -133,9 +133,7 @@ function removeTs(packageJson) { } } -if (process.argv.length !== 4) { - process.exit(1); -} else { +if (process.argv.length === 4) { const action = process.argv[2]; const dir = process.argv[3]; const packageJsonFile = path.resolve(dir, "package.json"); @@ -156,4 +154,6 @@ if (process.argv.length !== 4) { } fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 4), { encoding: "utf-8" }); +} else { + process.exit(1); } diff --git a/tools/app-toolkit/src/_base/terminal/loggers.ts b/tools/app-toolkit/src/_base/terminal/loggers.ts index cca68d891a9..3c7e995d4d1 100644 --- a/tools/app-toolkit/src/_base/terminal/loggers.ts +++ b/tools/app-toolkit/src/_base/terminal/loggers.ts @@ -1,5 +1,7 @@ -// (C) 2007-2022 GoodData Corporation -/* eslint-disable no-console */ +// (C) 2007-2026 GoodData Corporation + +// oxlint-disable eslint-js/no-console + import chalk from "chalk"; /** diff --git a/tools/app-toolkit/src/typings.d.ts b/tools/app-toolkit/src/typings.d.ts index ab08ddd8e0e..c83502856b1 100644 --- a/tools/app-toolkit/src/typings.d.ts +++ b/tools/app-toolkit/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/tools/catalog-export/.dependency-cruiser.js b/tools/catalog-export/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/catalog-export/.dependency-cruiser.js +++ b/tools/catalog-export/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/catalog-export/package.json b/tools/catalog-export/package.json index 3adfc2ce104..2f674350d99 100644 --- a/tools/catalog-export/package.json +++ b/tools/catalog-export/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/catalog-export", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK Catalog Export tooling", "license": "MIT", "author": "GoodData", @@ -72,8 +72,9 @@ "eslint-plugin-sonarjs": "3.0.6", "happy-dom": "20.12.0", "npm-run-all": "4.1.5", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/catalog-export/src/cli/loggers.ts b/tools/catalog-export/src/cli/loggers.ts index 166ca359929..6d676901d79 100644 --- a/tools/catalog-export/src/cli/loggers.ts +++ b/tools/catalog-export/src/cli/loggers.ts @@ -1,6 +1,6 @@ // (C) 2007-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console import chalk from "chalk"; import stripAnsi from "strip-ansi"; diff --git a/tools/catalog-export/src/typings.d.ts b/tools/catalog-export/src/typings.d.ts index 502f883e344..054ee8474fd 100644 --- a/tools/catalog-export/src/typings.d.ts +++ b/tools/catalog-export/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/tools/catalog-export/vitest.config.ts b/tools/catalog-export/vitest.config.ts index 5e09b3a8e9e..b3cccb2df73 100644 --- a/tools/catalog-export/vitest.config.ts +++ b/tools/catalog-export/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ resolve: { preserveSymlinks: true, diff --git a/tools/create-pluggable-module/.oxlintrc.json b/tools/create-pluggable-module/.oxlintrc.json index 2d9a8e1498b..f44db3532d6 100644 --- a/tools/create-pluggable-module/.oxlintrc.json +++ b/tools/create-pluggable-module/.oxlintrc.json @@ -4,5 +4,8 @@ "options": { "typeAware": true }, + "rules": { + "eslint-js/no-console": "off" + }, "ignorePatterns": ["src/templates/**", "esm/**", "esm/templates/**"] } diff --git a/tools/create-pluggable-module/eslint.config.ts b/tools/create-pluggable-module/eslint.config.ts index 127c30184ee..f3cd0c116cd 100644 --- a/tools/create-pluggable-module/eslint.config.ts +++ b/tools/create-pluggable-module/eslint.config.ts @@ -4,12 +4,6 @@ import config from "@gooddata/eslint-config/oxlint-esm"; export default [ ...config, - { - // CLI tool: console output is the primary user interface. - rules: { - "no-console": "off", - }, - }, { // Template files are raw fixtures — don't lint them. ignores: ["src/templates/**", "esm/templates/**"], diff --git a/tools/create-pluggable-module/package.json b/tools/create-pluggable-module/package.json index 87f3ea7ee8e..22adfff89be 100644 --- a/tools/create-pluggable-module/package.json +++ b/tools/create-pluggable-module/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/create-pluggable-module", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "Scaffolder for GoodData pluggable applications. Invoked via `npm init @gooddata/pluggable-module`.", "license": "MIT", "author": "GoodData Corporation", @@ -64,8 +64,9 @@ "gdc-app-template-name-module": "workspace:*", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "tslib": "2.8.1", "typescript": "7.0.2", "vite": "8.0.16", diff --git a/tools/create-pluggable-module/src/engine/__snapshots__/snapshot.test.ts.snap b/tools/create-pluggable-module/src/engine/__snapshots__/snapshot.test.ts.snap index e80e6c77990..846b31738cb 100644 --- a/tools/create-pluggable-module/src/engine/__snapshots__/snapshot.test.ts.snap +++ b/tools/create-pluggable-module/src/engine/__snapshots__/snapshot.test.ts.snap @@ -210,6 +210,7 @@ export { default } from "@gooddata/eslint-config/oxlint-react"; "npm-run-all": "", "oxfmt": "", "oxlint": "", + "oxlint-plugin-eslint": "", "oxlint-tsgolint": "", "rolldown": "", "sass": "", @@ -380,12 +381,11 @@ try { }; // oxlint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.info(\`No certificates found in \${certDir}, skipping HTTPS configuration\`); httpsConfig = undefined; } -// eslint-disable-next-line no-restricted-exports export default defineConfig(({ mode }): UserConfig => { const env = loadEnv(mode, process.cwd(), ""); const isProduction = mode === "production"; @@ -670,6 +670,7 @@ export { default } from "@gooddata/eslint-config/oxlint-react-vitest"; "npm-run-all": "", "oxfmt": "", "oxlint": "", + "oxlint-plugin-eslint": "", "oxlint-tsgolint": "", "react": "", "react-dom": "", @@ -858,7 +859,7 @@ export function AppProviders({ ctx, children }: PropsWithChildren { const env = loadEnv(mode, process.cwd(), ""); const port = parseInt(env["PORT"] || "8550"); @@ -1404,7 +1404,6 @@ export default defineConfig(({ mode, command }): UserConfig => { import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ test: { environment: "happy-dom", diff --git a/tools/create-pluggable-module/src/index.ts b/tools/create-pluggable-module/src/index.ts index 16449d37c59..d942844cb49 100644 --- a/tools/create-pluggable-module/src/index.ts +++ b/tools/create-pluggable-module/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node // (C) 2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files -- this file IS the package's curated public surface */ +// oxlint-disable no-barrel-files/no-barrel-files -- this file IS the package's curated public surface import { realpathSync } from "fs"; import { fileURLToPath } from "url"; diff --git a/tools/create-pluggable-module/vitest.config.ts b/tools/create-pluggable-module/vitest.config.ts index 9488b272fe8..d233f4fd3e3 100644 --- a/tools/create-pluggable-module/vitest.config.ts +++ b/tools/create-pluggable-module/vitest.config.ts @@ -2,7 +2,6 @@ import { defineConfig } from "vitest/config"; -// eslint-disable-next-line no-restricted-exports export default defineConfig({ test: { reporters: ["default"], diff --git a/tools/dashboard-plugin-template/.dependency-cruiser.js b/tools/dashboard-plugin-template/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/dashboard-plugin-template/.dependency-cruiser.js +++ b/tools/dashboard-plugin-template/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/dashboard-plugin-template/.oxlintrc.json b/tools/dashboard-plugin-template/.oxlintrc.json index d25d2790a72..48e27363c7f 100644 --- a/tools/dashboard-plugin-template/.oxlintrc.json +++ b/tools/dashboard-plugin-template/.oxlintrc.json @@ -7,5 +7,14 @@ "ignorePatterns": ["typings.d.ts"], "rules": { "import/no-unassigned-import": "warn" - } + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + "rules": { + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-explicit-any": "warn" + } + } + ] } diff --git a/tools/dashboard-plugin-template/eslint.config.ts b/tools/dashboard-plugin-template/eslint.config.ts index 3c21c9c40a6..cfd16de4339 100644 --- a/tools/dashboard-plugin-template/eslint.config.ts +++ b/tools/dashboard-plugin-template/eslint.config.ts @@ -12,11 +12,4 @@ export default [ "configTemplates/js/vite.config.js", ], }, - { - files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], - rules: { - "@typescript-eslint/no-unsafe-member-access": "warn", - "@typescript-eslint/no-explicit-any": "warn", - }, - }, ]; diff --git a/tools/dashboard-plugin-template/package.json b/tools/dashboard-plugin-template/package.json index bae94e7d7b6..a823c8f3725 100644 --- a/tools/dashboard-plugin-template/package.json +++ b/tools/dashboard-plugin-template/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/dashboard-plugin-template", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData Template for Dashboard Plugins", "license": "LicenseRef-LICENSE", @@ -90,8 +90,9 @@ "eslint-plugin-sonarjs": "3.0.6", "html-webpack-plugin": "5.6.4", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "react-intl": "10.1.25", diff --git a/tools/dashboard-plugin-template/src/plugin/Plugin.tsx b/tools/dashboard-plugin-template/src/plugin/Plugin.tsx index fbffcd39480..af954ab44a2 100644 --- a/tools/dashboard-plugin-template/src/plugin/Plugin.tsx +++ b/tools/dashboard-plugin-template/src/plugin/Plugin.tsx @@ -70,7 +70,7 @@ export class Plugin extends DashboardPluginV1 { ); }); handlers.addEventHandler("GDC.DASH/EVT.INITIALIZED", (evt) => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log("### Dashboard initialized", evt); }); } diff --git a/tools/dashboard-plugin-template/src/plugin_engine/index.ts b/tools/dashboard-plugin-template/src/plugin_engine/index.ts index a27de80b18b..445cd49ceb7 100644 --- a/tools/dashboard-plugin-template/src/plugin_engine/index.ts +++ b/tools/dashboard-plugin-template/src/plugin_engine/index.ts @@ -1,5 +1,5 @@ // (C) 2021-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files export { newDashboardEngine } from "@gooddata/sdk-ui-dashboard"; diff --git a/tools/dashboard-plugin-template/webpack.config.cjs b/tools/dashboard-plugin-template/webpack.config.cjs index ff6cea55fe4..59cbf4edd04 100644 --- a/tools/dashboard-plugin-template/webpack.config.cjs +++ b/tools/dashboard-plugin-template/webpack.config.cjs @@ -105,7 +105,7 @@ function createGeoStyleProxy({ backend, origin, cookieDomain, hostHeader }) { res.setHeader("content-type", "application/json"); res.end(rewrittenBody); } catch (err) { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.error("[devServer] Failed to rewrite geo style response", err); res.writeHead(500, { "content-type": "application/json" }); res.end(JSON.stringify({ error: "Failed to rewrite geo style response." })); diff --git a/tools/eslint-config/PACKAGES_V9.md b/tools/eslint-config/PACKAGES_V9.md index 78eb372322d..40e892f9e9b 100644 --- a/tools/eslint-config/PACKAGES_V9.md +++ b/tools/eslint-config/PACKAGES_V9.md @@ -4,7 +4,7 @@ This table shows which packages are required for each configuration. | Package | base | browser-esm | esm | esm-playwright | esm-react | esm-react-playwright | esm-react-storybook | esm-react-vitest | esm-react-vitest-rtl | esm-vitest | esm-vitest-rtl | react | react-playwright | react-vitest | react-vitest-rtl | | --------------------------------- | ------ | ----------- | ------ | -------------- | --------- | -------------------- | ------------------- | ---------------- | -------------------- | ---------- | -------------- | ------ | ---------------- | ------------ | ---------------- | -| @eslint/js | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | +| @eslint/js | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | 9.39.5 | | @testing-library/dom | | | | | | | 10.4.1 | | | | | | | | | | @typescript-eslint/eslint-plugin | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | | @typescript-eslint/parser | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | @@ -24,6 +24,7 @@ This table shows which packages are required for each configuration. | eslint-plugin-storybook | | | | | | | 10.5.6 | | | | | | | | | | eslint-plugin-testing-library | | | | | | | | | 7.16.2 | | 7.16.2 | | | | 7.16.2 | | globals | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | +| jiti | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | | react | | | | | | | 19.1.1 | | | | | | | | | | react-dom | | | | | | | 19.1.1 | | | | | | | | | | storybook | | | | | | | ^10.5.6 | | | | | | | | | diff --git a/tools/eslint-config/PACKAGES_V9_OXLINT.md b/tools/eslint-config/PACKAGES_V9_OXLINT.md index eaa0c77d066..a9d9afc78e3 100644 --- a/tools/eslint-config/PACKAGES_V9_OXLINT.md +++ b/tools/eslint-config/PACKAGES_V9_OXLINT.md @@ -4,10 +4,10 @@ This table shows which packages are required for each configuration. | Package | oxlint-base | oxlint-browser-esm | oxlint-esm | oxlint-esm-playwright | oxlint-esm-react | oxlint-esm-react-playwright | oxlint-esm-react-storybook | oxlint-esm-react-vitest | oxlint-esm-react-vitest-rtl | oxlint-esm-vitest | oxlint-esm-vitest-rtl | oxlint-react | oxlint-react-playwright | oxlint-react-vitest | oxlint-react-vitest-rtl | | -------------------------------- | ----------- | ------------------ | ---------- | --------------------- | ---------------- | --------------------------- | -------------------------- | ----------------------- | --------------------------- | ----------------- | --------------------- | ------------ | ----------------------- | ------------------- | ----------------------- | -| @eslint/js | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | 9.28.0 | | @typescript-eslint/eslint-plugin | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | | @typescript-eslint/parser | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | 8.58.0 | | eslint-plugin-jsdoc | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | 62.1.0 | | eslint-plugin-react | | | | | 7.37.5 | 7.37.5 | 7.37.5 | 7.37.5 | 7.37.5 | | | 7.37.5 | 7.37.5 | 7.37.5 | 7.37.5 | | eslint-plugin-react-hooks | | | | | 5.2.0 | 5.2.0 | 5.2.0 | 5.2.0 | 5.2.0 | | | 5.2.0 | 5.2.0 | 5.2.0 | 5.2.0 | | globals | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | 17.2.0 | +| jiti | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | 2.6.1 | diff --git a/tools/eslint-config/package.json b/tools/eslint-config/package.json index 925fde70a4a..81cc0582656 100644 --- a/tools/eslint-config/package.json +++ b/tools/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/eslint-config", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "license": "MIT", "author": "GoodData", "repository": { @@ -135,7 +135,7 @@ "validate": "npm run lint && npm run format-check" }, "devDependencies": { - "@eslint/js": "9.28.0", + "@eslint/js": "9.39.5", "@gooddata/lint-config": "workspace:*", "@testing-library/dom": "10.4.1", "@types/node": "24.12.0", diff --git a/tools/eslint-config/scripts/updatePackage.ts b/tools/eslint-config/scripts/updatePackage.ts index c92801ec9db..cf747ab8241 100644 --- a/tools/eslint-config/scripts/updatePackage.ts +++ b/tools/eslint-config/scripts/updatePackage.ts @@ -79,7 +79,6 @@ const devDeps: Record = { "@types/node": packageJson.devDependencies["@types/node"], jiti: "2.6.1", // Required for ESLint to load TypeScript config files typescript: packageJson.devDependencies["typescript"], - "@typescript/native-preview": "7.0.0-dev.20260202.1", "vite-node": packageJson.devDependencies["vite-node"], }; diff --git a/tools/eslint-config/src/configurations/eslint.ts b/tools/eslint-config/src/configurations/eslint.ts index 201d39b5faa..93f2d0f53b1 100644 --- a/tools/eslint-config/src/configurations/eslint.ts +++ b/tools/eslint-config/src/configurations/eslint.ts @@ -1,24 +1,34 @@ // (C) 2025-2026 GoodData Corporation -import { eslintOverrides, eslintRules, eslintRulesNativeNotSupported } from "@gooddata/lint-config"; +import { eslintOverrides, eslintRules } from "@gooddata/lint-config"; import type { IDualConfiguration } from "../types.js"; -const v9Common = { - packages: [ - { - name: "@eslint/js", - version: "9.28.0", - }, - ], - overrides: eslintOverrides, -}; - export const eslint: IDualConfiguration = { v8: { rules: eslintRules, overrides: eslintOverrides, }, - v9: { ...v9Common, rules: eslintRules }, - ox: { ...v9Common, rules: eslintRulesNativeNotSupported }, + v9: { + packages: [ + { + name: "@eslint/js", + version: "9.39.5", + }, + { + name: "jiti", + version: "2.6.1", + }, + ], + overrides: eslintOverrides, + rules: eslintRules, + }, + ox: { + packages: [ + { + name: "jiti", + version: "2.6.1", + }, + ], + }, }; diff --git a/tools/eslint-config/src/configurations/playwright.ts b/tools/eslint-config/src/configurations/playwright.ts index a4135afb069..089da60c1fc 100644 --- a/tools/eslint-config/src/configurations/playwright.ts +++ b/tools/eslint-config/src/configurations/playwright.ts @@ -1,6 +1,6 @@ // (C) 2025-2026 GoodData Corporation -import { playwrightPlugin, playwrightRules } from "@gooddata/lint-config"; +import { playwrightConflicts, playwrightPlugin, playwrightRules } from "@gooddata/lint-config"; import type { IDualConfiguration } from "../types.js"; @@ -8,7 +8,10 @@ export const playwright: IDualConfiguration<"playwright"> = { v8: { packages: [playwrightPlugin], plugins: ["playwright"], - rules: playwrightRules, + rules: { + ...playwrightConflicts, + ...playwrightRules, + }, }, v9: { packages: [playwrightPlugin], @@ -21,7 +24,10 @@ export const playwright: IDualConfiguration<"playwright"> = { chai: "readonly" as const, }, }, - rules: playwrightRules, + rules: { + ...playwrightConflicts, + ...playwrightRules, + }, }, ox: {}, }; diff --git a/tools/eslint-config/src/configurations/vitest.ts b/tools/eslint-config/src/configurations/vitest.ts index 1c45ec1c1a2..3b3951b3f25 100644 --- a/tools/eslint-config/src/configurations/vitest.ts +++ b/tools/eslint-config/src/configurations/vitest.ts @@ -1,9 +1,14 @@ // (C) 2025-2026 GoodData Corporation -import { scopeRules, vitestPlugin, vitestRules } from "@gooddata/lint-config"; +import { type IPackage, scopeRules, vitestRules } from "@gooddata/lint-config"; import type { IDualConfiguration } from "../types.js"; +export const vitestPlugin: IPackage = { + name: "@vitest/eslint-plugin", + version: "1.6.19", +}; + const packages = [vitestPlugin]; const v9 = { diff --git a/tools/i18n-toolkit/.dependency-cruiser.js b/tools/i18n-toolkit/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/i18n-toolkit/.dependency-cruiser.js +++ b/tools/i18n-toolkit/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/i18n-toolkit/package.json b/tools/i18n-toolkit/package.json index 3f0644f5a7f..d3c75217fdf 100644 --- a/tools/i18n-toolkit/package.json +++ b/tools/i18n-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/i18n-toolkit", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "Localization validator to validate localization complexity and intl and html format.", "license": "MIT", "author": "GoodData", @@ -72,8 +72,9 @@ "mkdirp": "1.0.4", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/i18n-toolkit/src/utils/console.ts b/tools/i18n-toolkit/src/utils/console.ts index 9d9efdc9b39..0ae4bb4f8a1 100644 --- a/tools/i18n-toolkit/src/utils/console.ts +++ b/tools/i18n-toolkit/src/utils/console.ts @@ -1,6 +1,6 @@ // (C) 2021-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console import * as path from "path"; diff --git a/tools/i18n-toolkit/validate-esm.cjs b/tools/i18n-toolkit/validate-esm.cjs index acf73fc2fed..eb78a186bad 100644 --- a/tools/i18n-toolkit/validate-esm.cjs +++ b/tools/i18n-toolkit/validate-esm.cjs @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console try { require.resolve("@gooddata/i18n-toolkit"); diff --git a/tools/lint-config/package.json b/tools/lint-config/package.json index 30a5408b82a..7c2383390c0 100644 --- a/tools/lint-config/package.json +++ b/tools/lint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/lint-config", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "Linter-agnostic shared rules for @gooddata/eslint-config and @gooddata/oxlint-config", "license": "MIT", "author": "GoodData", diff --git a/tools/lint-config/src/configurations/chai-friendly.ts b/tools/lint-config/src/configurations/chai-friendly.ts index 6571732fe84..f49b91cc912 100644 --- a/tools/lint-config/src/configurations/chai-friendly.ts +++ b/tools/lint-config/src/configurations/chai-friendly.ts @@ -7,7 +7,6 @@ export const chaiFriendlyPlugin: IPackage = { version: "1.1.0", }; -export const chaiFriendlyRules: Rules = { - "no-unused-expressions": "off", +export const chaiFriendlyRules: Rules<"chai-friendly"> = { "chai-friendly/no-unused-expressions": "error", }; diff --git a/tools/lint-config/src/configurations/eslint.ts b/tools/lint-config/src/configurations/eslint.ts index ddb2fe72237..9ae3f838325 100644 --- a/tools/lint-config/src/configurations/eslint.ts +++ b/tools/lint-config/src/configurations/eslint.ts @@ -2,7 +2,9 @@ // Lodash-es imports that should be replaced with native alternatives // Maps import name(s) to the recommended alternative -import { Rules } from "../types.js"; +import { IOverride, Rules } from "../types.js"; + +import { typescriptOverrideFiles } from "./typescript.js"; const lodashEsBans: Record = { "get,getOr": "the ?. and ?? operators", @@ -187,6 +189,7 @@ export const eslintRulesNativeNotSupported: Rules = { "no-dupe-args": "error", + // https://github.com/oxc-project/oxc/releases/tag/apps_v1.77.0 "no-restricted-exports": [ "error", { @@ -212,8 +215,8 @@ export const eslintRulesNativeNotSupported: Rules = { }, ], - // https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-negated-condition - "no-negated-condition": "error", // oxlint state: pending fix + // https://github.com/oxc-project/oxc/releases/tag/apps_v1.72.0 + "no-negated-condition": "error", // todo: maybe leave this here, both linters seem to turn it on by default and conflict "prefer-const": "off", @@ -227,7 +230,7 @@ export const eslintRules: Rules = { ...eslintRulesNativeNotSupported, }; -export const eslintOverridesNativeSupported = [ +export const eslintOverridesNativeSupported: IOverride[] = [ { files: ["**/*.ts", "**/*.tsx"], rules: { @@ -244,7 +247,7 @@ export const eslintOverridesNativeSupported = [ ], }, ], - } as Rules, + }, }, { files: [ @@ -267,11 +270,11 @@ export const eslintOverridesNativeSupported = [ patterns: lodashEsPatterns, }, ], - } as Rules, + }, }, ]; -const eslintOverridesNativeNotSupported = [ +export const eslintOverridesNativeNotSupported: IOverride[] = [ { // ESLint flat config files & Vite config files require a default export files: [ @@ -281,6 +284,10 @@ const eslintOverridesNativeNotSupported = [ "**/vite.config.js", "**/vitest.config.ts", "**/vitest.config.js", + "**/rolldown.config.ts", + "**/rolldown.config.js", + "**/playwright.config.ts", + "**/playwright.config.js", "**/.dependency-cruiser.ts", "**/.dependency-cruiser.js", "**/.i18nrc.ts", @@ -288,8 +295,40 @@ const eslintOverridesNativeNotSupported = [ ], rules: { "no-restricted-exports": "off", - } as Rules, + }, + }, + { + files: typescriptOverrideFiles, + rules: { + "no-restricted-syntax": [ + "error", + { + selector: "MemberExpression[object.name='React']", + message: "Do not use `React.*`. Use named imports instead.", + }, + { + selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='React']", + message: "Do not use `React.*` types. Use named imports instead.", + }, + { + selector: "ExportNamespaceSpecifier", + message: "Usage of 'export * as …' is forbidden.", + }, + { + selector: "ExportAllDeclaration", + message: "Usage of `export * from` is forbidden.", + }, + { + selector: + "ImportDeclaration[source.value=/^(?!.*reference_workspace)\\./] ImportNamespaceSpecifier", + message: "Do not use `import * as ...` from relative paths.", + }, + ], + }, }, ]; -export const eslintOverrides = [...eslintOverridesNativeSupported, ...eslintOverridesNativeNotSupported]; +export const eslintOverrides: IOverride[] = [ + ...eslintOverridesNativeSupported, + ...eslintOverridesNativeNotSupported, +]; diff --git a/tools/lint-config/src/configurations/no-barrel-files.ts b/tools/lint-config/src/configurations/no-barrel-files.ts index c8d28ade681..a92996cb609 100644 --- a/tools/lint-config/src/configurations/no-barrel-files.ts +++ b/tools/lint-config/src/configurations/no-barrel-files.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -import { IPackage, Rules } from "../types.js"; +import { IOverride, IPackage, Rules } from "../types.js"; export const noBarrelFilesPlugin: IPackage = { name: "eslint-plugin-no-barrel-files", @@ -11,11 +11,11 @@ export const noBarrelFilesRules: Rules<"no-barrel-files"> = { "no-barrel-files/no-barrel-files": "error", }; -export const noBarrelFilesOverrides = [ +export const noBarrelFilesOverrides: IOverride<"no-barrel-files">[] = [ { files: ["**/eslint.config.ts"], rules: { "no-barrel-files/no-barrel-files": "off", - } as Rules<"no-barrel-files">, + }, }, ]; diff --git a/tools/lint-config/src/configurations/playwright.ts b/tools/lint-config/src/configurations/playwright.ts index 02cce189829..ee94134fc36 100644 --- a/tools/lint-config/src/configurations/playwright.ts +++ b/tools/lint-config/src/configurations/playwright.ts @@ -2,13 +2,16 @@ import { IPackage, Rules } from "../types.js"; +export const playwrightConflicts: Rules = { + "no-empty-pattern": "off", +}; + export const playwrightPlugin: IPackage = { name: "eslint-plugin-playwright", version: "2.10.1", }; -export const playwrightRules: Rules = { - "no-empty-pattern": "off", +export const playwrightRules: Rules<"playwright"> = { "playwright/consistent-spacing-between-blocks": "warn", "playwright/expect-expect": "warn", "playwright/max-nested-describe": "warn", diff --git a/tools/lint-config/src/configurations/sonarjs.ts b/tools/lint-config/src/configurations/sonarjs.ts index 54467fea157..774d174fa20 100644 --- a/tools/lint-config/src/configurations/sonarjs.ts +++ b/tools/lint-config/src/configurations/sonarjs.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -import type { IPackage, Rules } from "../types.js"; +import type { IOverride, IPackage, Rules } from "../types.js"; export const sonarjsPlugin: IPackage = { name: "eslint-plugin-sonarjs", @@ -285,12 +285,12 @@ export const sonarjsRules: Rules<"sonarjs"> = { "sonarjs/no-control-regex": "off", }; -export const sonarjsOverrides = [ +export const sonarjsOverrides: IOverride<"sonarjs">[] = [ { files: ["*.test.ts", "*.test.tsx", "*.spec.ts"], rules: { // we do not care about duplicate functions in test files, they often make sense (e.g. in different describe blocks) "sonarjs/no-identical-functions": "off", - } as Rules<"sonarjs">, + }, }, ]; diff --git a/tools/lint-config/src/configurations/storybook.ts b/tools/lint-config/src/configurations/storybook.ts index 02c6041ff09..22c35cedacf 100644 --- a/tools/lint-config/src/configurations/storybook.ts +++ b/tools/lint-config/src/configurations/storybook.ts @@ -1,6 +1,6 @@ // (C) 2026 GoodData Corporation -import { IPackage, Rules } from "../types.js"; +import { IOverride, IPackage, Rules } from "../types.js"; export const storybookPlugin: IPackage = { name: "eslint-plugin-storybook", @@ -31,26 +31,27 @@ export const storybookPackages = [ }, ]; -export const storybookOverrides = [ - { - files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)", "**/*.story.@(ts|tsx|js|jsx|mjs|cjs)"], - rules: { - "react-hooks/rules-of-hooks": "off", - "import/no-anonymous-default-export": "off", - "storybook/await-interactions": "error", - "storybook/context-in-play-function": "error", - "storybook/default-exports": "error", - "storybook/hierarchy-separator": "warn", - "storybook/no-redundant-story-name": "warn", - "storybook/no-renderer-packages": "error", - "storybook/prefer-pascal-case": "error", - "storybook/story-exports": "error", - "storybook/use-storybook-expect": "error", - "storybook/use-storybook-testing-library": "error", - } as Rules<"react-hooks" | "import" | "storybook">, - }, - { - files: [".storybook/main.@(js|cjs|mjs|ts)"], - rules: { "storybook/no-uninstalled-addons": "error" } as Rules<"storybook">, - }, -]; +export const storybookOverrides: [IOverride<"react-hooks" | "import" | "storybook">, IOverride<"storybook">] = + [ + { + files: ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)", "**/*.story.@(ts|tsx|js|jsx|mjs|cjs)"], + rules: { + "react-hooks/rules-of-hooks": "off", + "import/no-anonymous-default-export": "off", + "storybook/await-interactions": "error", + "storybook/context-in-play-function": "error", + "storybook/default-exports": "error", + "storybook/hierarchy-separator": "warn", + "storybook/no-redundant-story-name": "warn", + "storybook/no-renderer-packages": "error", + "storybook/prefer-pascal-case": "error", + "storybook/story-exports": "error", + "storybook/use-storybook-expect": "error", + "storybook/use-storybook-testing-library": "error", + }, + }, + { + files: [".storybook/main.@(js|cjs|mjs|ts)"], + rules: { "storybook/no-uninstalled-addons": "error" }, + }, + ]; diff --git a/tools/lint-config/src/configurations/typescript.ts b/tools/lint-config/src/configurations/typescript.ts index 91d472318c7..49ef9792d7a 100644 --- a/tools/lint-config/src/configurations/typescript.ts +++ b/tools/lint-config/src/configurations/typescript.ts @@ -1,19 +1,17 @@ // (C) 2026 GoodData Corporation -import { Rules } from "../types.js"; +import { OverrideFiles, Rules } from "../types.js"; -export const typescriptConflicts: Rules = { +export const typescriptConflictsNativeSupported: Rules = { "constructor-super": "off", "getter-return": "off", "no-class-assign": "off", "no-const-assign": "off", - "no-dupe-args": "off", "no-dupe-class-members": "off", "no-dupe-keys": "off", "no-func-assign": "off", "no-import-assign": "off", "no-new-native-nonconstructor": "off", - "no-new-symbol": "off", "no-obj-calls": "off", "no-redeclare": "off", "no-setter-return": "off", @@ -27,11 +25,20 @@ export const typescriptConflicts: Rules = { "prefer-rest-params": "error", "prefer-spread": "error", "no-array-constructor": "off", - "no-unused-expressions": "off", "no-unused-vars": "off", }; -const typescriptRulesCommon: Rules = { +const typescriptConflictsNativeNotSupported: Rules = { + "no-dupe-args": "off", + "no-new-symbol": "off", +}; + +export const typescriptConflicts: Rules = { + ...typescriptConflictsNativeSupported, + ...typescriptConflictsNativeNotSupported, +}; + +const typescriptRulesCommon: Rules<"@typescript-eslint"> = { "@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-duplicate-enum-values": "error", "@typescript-eslint/no-empty-object-type": "error", @@ -43,17 +50,17 @@ const typescriptRulesCommon: Rules = { "@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-unnecessary-type-constraint": "error", "@typescript-eslint/no-unsafe-declaration-merging": "error", - // Covered by chai-friendly/no-unused-expressions, not needed here. - // Also causes issues in oxlint, which maps it back to the base eslint rule. - // "@typescript-eslint/no-unused-expressions": "error", "@typescript-eslint/prefer-as-const": "error", "@typescript-eslint/prefer-namespace-keyword": "error", "@typescript-eslint/triple-slash-reference": "error", - "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/no-use-before-define": 0, - "@typescript-eslint/no-empty-function": 0, - "@typescript-eslint/no-unused-vars": [2, { varsIgnorePattern: "^_.*$", argsIgnorePattern: "^_.*$" }], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { varsIgnorePattern: "^_.*$", argsIgnorePattern: "^_.*$" }, + ], "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/array-type": "off", @@ -83,36 +90,9 @@ const typescriptRulesCommon: Rules = { }, }, ], - "@typescript-eslint/explicit-member-accessibility": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/member-ordering": "off", "@typescript-eslint/no-inferrable-types": "off", "@typescript-eslint/no-non-null-assertion": "off", - "no-restricted-syntax": [ - "error", - { - selector: "MemberExpression[object.name='React']", - message: "Do not use `React.*`. Use named imports instead.", - }, - { - selector: "TSTypeReference[typeName.type='TSQualifiedName'][typeName.left.name='React']", - message: "Do not use `React.*` types. Use named imports instead.", - }, - { - selector: "ExportNamespaceSpecifier", - message: "Usage of 'export * as …' is forbidden.", - }, - { - selector: "ExportAllDeclaration", - message: "Usage of `export * from` is forbidden.", - }, - { - selector: - "ImportDeclaration[source.value=/^(?!.*reference_workspace)\\./] ImportNamespaceSpecifier", - message: "Do not use `import * as ...` from relative paths.", - }, - ], "@typescript-eslint/consistent-type-imports": [ "error", { @@ -146,9 +126,9 @@ export const typescriptRulesNativeNotSupported: Rules<"@typescript-eslint"> = { ], }; -export const typescriptRules: Rules = { +export const typescriptRules: Rules<"@typescript-eslint"> = { ...typescriptRulesNativeSupported, ...typescriptRulesNativeNotSupported, }; -export const typescriptOverrideFiles = ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]; +export const typescriptOverrideFiles: OverrideFiles = ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]; diff --git a/tools/lint-config/src/configurations/vitest.ts b/tools/lint-config/src/configurations/vitest.ts index 6d24139f76a..630172d6aab 100644 --- a/tools/lint-config/src/configurations/vitest.ts +++ b/tools/lint-config/src/configurations/vitest.ts @@ -1,11 +1,6 @@ // (C) 2025-2026 GoodData Corporation -import { IPackage, Rules } from "../types.js"; - -export const vitestPlugin: IPackage = { - name: "@vitest/eslint-plugin", - version: "1.6.19", -}; +import { Rules } from "../types.js"; export const vitestRules: Rules = { "no-conditional-expect": "error", diff --git a/tools/lint-config/src/index.ts b/tools/lint-config/src/index.ts index f6b0894512f..83093d1fbee 100644 --- a/tools/lint-config/src/index.ts +++ b/tools/lint-config/src/index.ts @@ -6,6 +6,7 @@ export { chaiFriendlyPlugin, chaiFriendlyRules } from "./configurations/chai-fri export { eslintOverrides, eslintOverridesNativeSupported, + eslintOverridesNativeNotSupported, eslintRules, eslintRulesNativeSupported, eslintRulesNativeNotSupported, @@ -19,7 +20,7 @@ export { noBarrelFilesRules, } from "./configurations/no-barrel-files.js"; export { noOnlyTestsPlugin, noOnlyTestsRules } from "./configurations/no-only-tests.js"; -export { playwrightPlugin, playwrightRules } from "./configurations/playwright.js"; +export { playwrightConflicts, playwrightPlugin, playwrightRules } from "./configurations/playwright.js"; export { sonarjsOverrides, sonarjsPlugin, sonarjsRules } from "./configurations/sonarjs.js"; export { storybookOverrides, storybookPackages, storybookPlugin } from "./configurations/storybook.js"; export { @@ -30,11 +31,12 @@ export { } from "./configurations/testing-library.js"; export { typescriptConflicts, + typescriptConflictsNativeSupported, typescriptOverrideFiles, typescriptRules, typescriptRulesNativeNotSupported, typescriptRulesNativeSupported, } from "./configurations/typescript.js"; -export { vitestPlugin, vitestRules } from "./configurations/vitest.js"; +export { vitestRules } from "./configurations/vitest.js"; export { scopeRules } from "./utils/scopeRules.js"; diff --git a/tools/lint-config/src/types.ts b/tools/lint-config/src/types.ts index 3831f5218ce..8eaeff3add2 100644 --- a/tools/lint-config/src/types.ts +++ b/tools/lint-config/src/types.ts @@ -19,3 +19,10 @@ export type RuleValue = string | number | object | object[]; export type Rules = { [key in RuleKey]: RuleValue; }; + +export type OverrideFiles = string[]; + +export interface IOverride { + files: OverrideFiles; + rules: Rules; +} diff --git a/tools/lint-config/src/utils/scopeRules.ts b/tools/lint-config/src/utils/scopeRules.ts index 97301dcdc30..1a2bc6dc7cc 100644 --- a/tools/lint-config/src/utils/scopeRules.ts +++ b/tools/lint-config/src/utils/scopeRules.ts @@ -2,7 +2,7 @@ import { Rules } from "../types.js"; -export function scopeRules(rules: Rules, scope: Scope): Rules { +export function scopeRules(rules: Rules, scope: Scope) { return Object.fromEntries( Object.entries(rules).map(([key, value]) => [`${scope}/${key}`, value]), ) as Rules; diff --git a/tools/mock-handling/.dependency-cruiser.js b/tools/mock-handling/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/mock-handling/.dependency-cruiser.js +++ b/tools/mock-handling/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/mock-handling/.oxlintrc.json b/tools/mock-handling/.oxlintrc.json index 92c9bcedd9c..fe763b0d0b0 100644 --- a/tools/mock-handling/.oxlintrc.json +++ b/tools/mock-handling/.oxlintrc.json @@ -4,6 +4,9 @@ "options": { "typeAware": true }, + "rules": { + "eslint-js/no-console": "off" + }, "overrides": [ { "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], diff --git a/tools/mock-handling/eslint.config.ts b/tools/mock-handling/eslint.config.ts index 9910a225b86..a731690dc12 100644 --- a/tools/mock-handling/eslint.config.ts +++ b/tools/mock-handling/eslint.config.ts @@ -1,12 +1,3 @@ // (C) 2020-2026 GoodData Corporation -import config from "@gooddata/eslint-config/oxlint-esm"; - -export default [ - ...config, - { - rules: { - "no-console": "off", - }, - }, -]; +export { default } from "@gooddata/eslint-config/oxlint-esm"; diff --git a/tools/mock-handling/package.json b/tools/mock-handling/package.json index 218652c92d3..037e25cae58 100644 --- a/tools/mock-handling/package.json +++ b/tools/mock-handling/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/mock-handling", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK Mock data capture and management tool", "license": "MIT", "author": "GoodData", @@ -71,8 +71,9 @@ "eslint-plugin-sonarjs": "3.0.6", "npm-run-all": "4.1.5", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/mock-handling/src/index.ts b/tools/mock-handling/src/index.ts index 2d9e896145d..8a1a86c0f58 100644 --- a/tools/mock-handling/src/index.ts +++ b/tools/mock-handling/src/index.ts @@ -1,7 +1,7 @@ #!/usr/bin/env node // (C) 2007-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files import * as path from "path"; import * as process from "process"; diff --git a/tools/mock-handling/src/typings.d.ts b/tools/mock-handling/src/typings.d.ts index 0409f8214f0..7bb60f4ebb2 100644 --- a/tools/mock-handling/src/typings.d.ts +++ b/tools/mock-handling/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2019 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/tools/oxlint-config/PACKAGES.md b/tools/oxlint-config/PACKAGES.md index bb743d25b6c..ed4e5b823aa 100644 --- a/tools/oxlint-config/PACKAGES.md +++ b/tools/oxlint-config/PACKAGES.md @@ -5,7 +5,6 @@ This table shows which packages are required for each configuration. | Package | base | browser-esm | esm | esm-playwright | esm-react | esm-react-playwright | esm-react-storybook | esm-react-vitest | esm-react-vitest-rtl | esm-vitest | esm-vitest-rtl | react | react-playwright | react-vitest | react-vitest-rtl | | ----------------------------- | ------- | ----------- | ------- | -------------- | --------- | -------------------- | ------------------- | ---------------- | -------------------- | ---------- | -------------- | ------- | ---------------- | ------------ | ---------------- | | @testing-library/dom | | | | | | | 10.4.1 | | | | | | | | | -| @vitest/eslint-plugin | | | | | | | | 1.6.19 | 1.6.19 | 1.6.19 | 1.6.19 | | | 1.6.19 | 1.6.19 | | eslint-plugin-chai-friendly | | | | 1.1.0 | | 1.1.0 | | | | | | | 1.1.0 | | | | eslint-plugin-headers | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | 1.3.3 | | eslint-plugin-import-esm | | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | 1.2.1 | | | | | @@ -16,7 +15,8 @@ This table shows which packages are required for each configuration. | eslint-plugin-storybook | | | | | | | 10.5.6 | | | | | | | | | | eslint-plugin-testing-library | | | | | | | | | 7.16.2 | | 7.16.2 | | | | 7.16.2 | | oxlint | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | ^1.51.0 | -| oxlint-tsgolint | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | 0.15.0 | +| oxlint-plugin-eslint | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | 1.82.0 | +| oxlint-tsgolint | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | 0.22.1 | | react | | | | | | | 19.1.1 | | | | | | | | | | react-dom | | | | | | | 19.1.1 | | | | | | | | | | storybook | | | | | | | ^10.5.6 | | | | | | | | | diff --git a/tools/oxlint-config/package.json b/tools/oxlint-config/package.json index def6f3427d4..190585d5306 100644 --- a/tools/oxlint-config/package.json +++ b/tools/oxlint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/oxlint-config", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "license": "MIT", "author": "GoodData", "repository": { @@ -48,14 +48,12 @@ "validate": "npm run lint && npm run format-check" }, "devDependencies": { - "@eslint/js": "9.28.0", "@gooddata/eslint-config": "workspace:*", "@gooddata/lint-config": "workspace:*", "@testing-library/dom": "10.4.1", "@types/node": "24.12.0", "@typescript-eslint/eslint-plugin": "8.58.0", "@typescript-eslint/parser": "8.58.0", - "@vitest/eslint-plugin": "1.6.19", "eslint": "^9.39.2", "eslint-plugin-chai-friendly": "1.1.0", "eslint-plugin-headers": "1.3.3", @@ -69,8 +67,9 @@ "globals": "17.2.0", "jiti": "2.6.1", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "react": "19.1.1", "react-dom": "19.1.1", "storybook": "^10.5.6", @@ -82,6 +81,7 @@ "eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-sonarjs": "3.0.6", "oxlint": "^1.57.0", - "oxlint-tsgolint": "0.15.0" + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1" } } diff --git a/tools/oxlint-config/scripts/updatePackage.ts b/tools/oxlint-config/scripts/updatePackage.ts index 04f239e391e..a491e8ba6d1 100644 --- a/tools/oxlint-config/scripts/updatePackage.ts +++ b/tools/oxlint-config/scripts/updatePackage.ts @@ -18,10 +18,8 @@ const devDeps: Record = { "@types/node": packageJson.devDependencies["@types/node"], jiti: "2.6.1", // Required for ESLint to load TypeScript config files typescript: packageJson.devDependencies["typescript"], - "@typescript/native-preview": "7.0.0-dev.20260202.1", "vite-node": packageJson.devDependencies["vite-node"], "@gooddata/eslint-config": packageJson.devDependencies["@gooddata/eslint-config"], - "@eslint/js": packageJson.devDependencies["@eslint/js"], "@typescript-eslint/eslint-plugin": packageJson.devDependencies["@typescript-eslint/eslint-plugin"], "@typescript-eslint/parser": packageJson.devDependencies["@typescript-eslint/parser"], eslint: packageJson.devDependencies["eslint"], diff --git a/tools/oxlint-config/src/configurations/eslint.ts b/tools/oxlint-config/src/configurations/eslint.ts index 57f3f34f994..12526c4a7f6 100644 --- a/tools/oxlint-config/src/configurations/eslint.ts +++ b/tools/oxlint-config/src/configurations/eslint.ts @@ -1,11 +1,38 @@ // (C) 2026 GoodData Corporation -import { eslintOverridesNativeSupported, eslintRulesNativeSupported } from "@gooddata/lint-config"; +import { + type IPackage, + eslintOverridesNativeNotSupported, + eslintOverridesNativeSupported, + eslintRulesNativeNotSupported, + eslintRulesNativeSupported, + scopeRules, +} from "@gooddata/lint-config"; import { type IConfiguration } from "../types.js"; +const eslintPlugin: IPackage = { + name: "oxlint-plugin-eslint", + version: "1.82.0", +}; + export const eslint: IConfiguration = { + // native plugin plugins: ["eslint"], - rules: eslintRulesNativeSupported, - overrides: eslintOverridesNativeSupported, + + // js plugin + packages: [eslintPlugin], + jsPlugins: [{ name: "eslint-js", specifier: "oxlint-plugin-eslint" }], + + rules: { + ...eslintRulesNativeSupported, + ...scopeRules(eslintRulesNativeNotSupported, "eslint-js"), + }, + overrides: [ + ...eslintOverridesNativeSupported, + ...eslintOverridesNativeNotSupported.map((override) => ({ + ...override, + rules: scopeRules(override.rules, "eslint-js"), + })), + ], }; diff --git a/tools/oxlint-config/src/configurations/playwright.ts b/tools/oxlint-config/src/configurations/playwright.ts index 8d43cf3278e..c0d70323387 100644 --- a/tools/oxlint-config/src/configurations/playwright.ts +++ b/tools/oxlint-config/src/configurations/playwright.ts @@ -1,13 +1,16 @@ // (C) 2025-2026 GoodData Corporation -import { playwrightPlugin, playwrightRules } from "@gooddata/lint-config"; +import { playwrightConflicts, playwrightPlugin, playwrightRules } from "@gooddata/lint-config"; import type { IConfiguration } from "../types.js"; export const playwright: IConfiguration<"playwright"> = { packages: [playwrightPlugin], jsPlugins: [{ name: "playwright", specifier: playwrightPlugin.name }], - rules: playwrightRules, + rules: { + ...playwrightConflicts, + ...playwrightRules, + }, overrides: [ { files: ["*"], diff --git a/tools/oxlint-config/src/configurations/typescript.ts b/tools/oxlint-config/src/configurations/typescript.ts index 7a823c4a1cf..d4e1d51e97e 100644 --- a/tools/oxlint-config/src/configurations/typescript.ts +++ b/tools/oxlint-config/src/configurations/typescript.ts @@ -1,7 +1,7 @@ // (C) 2026 GoodData Corporation import { - typescriptConflicts, + typescriptConflictsNativeSupported, typescriptOverrideFiles, typescriptRulesNativeSupported, } from "@gooddata/lint-config"; @@ -12,7 +12,7 @@ export const typescript: IConfiguration = { packages: [ { name: "oxlint-tsgolint", - version: "0.15.0", + version: "0.22.1", }, ], plugins: ["typescript"], @@ -20,7 +20,7 @@ export const typescript: IConfiguration = { { files: typescriptOverrideFiles, rules: { - ...typescriptConflicts, + ...typescriptConflictsNativeSupported, ...typescriptRulesNativeSupported, }, }, diff --git a/tools/oxlint-config/src/configurations/vitest.ts b/tools/oxlint-config/src/configurations/vitest.ts index 99b00d61443..4544ce5b021 100644 --- a/tools/oxlint-config/src/configurations/vitest.ts +++ b/tools/oxlint-config/src/configurations/vitest.ts @@ -1,11 +1,10 @@ // (C) 2026 GoodData Corporation -import { scopeRules, vitestPlugin, vitestRules } from "@gooddata/lint-config"; +import { scopeRules, vitestRules } from "@gooddata/lint-config"; import { type IConfiguration } from "../types.js"; export const vitest: IConfiguration<"vitest"> = { - packages: [vitestPlugin], plugins: ["vitest"], rules: scopeRules(vitestRules, "vitest"), }; diff --git a/tools/plugin-toolkit/.dependency-cruiser.js b/tools/plugin-toolkit/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/plugin-toolkit/.dependency-cruiser.js +++ b/tools/plugin-toolkit/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/plugin-toolkit/package.json b/tools/plugin-toolkit/package.json index 681a53dc1a9..5b9d7c8a131 100644 --- a/tools/plugin-toolkit/package.json +++ b/tools/plugin-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/plugin-toolkit", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData Set of Tools for working with Plugins", "license": "LicenseRef-LICENSE", "author": "GoodData", @@ -80,8 +80,9 @@ "eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-sonarjs": "3.0.6", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/plugin-toolkit/scripts/preparePackageJson.mjs b/tools/plugin-toolkit/scripts/preparePackageJson.mjs index a67810d19b4..f3a34976379 100644 --- a/tools/plugin-toolkit/scripts/preparePackageJson.mjs +++ b/tools/plugin-toolkit/scripts/preparePackageJson.mjs @@ -1,6 +1,7 @@ // (C) 2021-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console + import fs from "fs"; import * as path from "path"; import * as process from "process"; @@ -95,9 +96,7 @@ function removeTs(packageJson) { delete packageJson.typings; } -if (process.argv.length !== 4) { - process.exit(1); -} else { +if (process.argv.length === 4) { const action = process.argv[2]; const dir = process.argv[3]; const packageJsonFile = path.resolve(dir, "package.json"); @@ -118,4 +117,6 @@ if (process.argv.length !== 4) { } fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 4), { encoding: "utf-8" }); +} else { + process.exit(1); } diff --git a/tools/plugin-toolkit/src/_base/terminal/loggers.ts b/tools/plugin-toolkit/src/_base/terminal/loggers.ts index c241e68fed3..3c7e995d4d1 100644 --- a/tools/plugin-toolkit/src/_base/terminal/loggers.ts +++ b/tools/plugin-toolkit/src/_base/terminal/loggers.ts @@ -1,5 +1,7 @@ -// (C) 2007-2021 GoodData Corporation -/* eslint-disable no-console */ +// (C) 2007-2026 GoodData Corporation + +// oxlint-disable eslint-js/no-console + import chalk from "chalk"; /** diff --git a/tools/plugin-toolkit/src/inspectCmds/inspectDashboard.ts b/tools/plugin-toolkit/src/inspectCmds/inspectDashboard.ts index 6f1f33f52ae..9416aad00a2 100644 --- a/tools/plugin-toolkit/src/inspectCmds/inspectDashboard.ts +++ b/tools/plugin-toolkit/src/inspectCmds/inspectDashboard.ts @@ -1,6 +1,7 @@ // (C) 2021-2026 GoodData Corporation -/* eslint-disable no-console */ +// oxlint-disable eslint-js/no-console + import columnify from "columnify"; import { isEmpty } from "lodash-es"; diff --git a/tools/plugin-toolkit/src/inspectCmds/output.ts b/tools/plugin-toolkit/src/inspectCmds/output.ts index 0a470c1d09c..e53b8122f66 100644 --- a/tools/plugin-toolkit/src/inspectCmds/output.ts +++ b/tools/plugin-toolkit/src/inspectCmds/output.ts @@ -1,6 +1,7 @@ -// (C) 2021-2025 GoodData Corporation +// (C) 2021-2026 GoodData Corporation + +// oxlint-disable eslint-js/no-console -/* eslint-disable no-console */ import { isEmpty } from "lodash-es"; export type ObjectSummary = { diff --git a/tools/plugin-toolkit/src/listCmds/listCmdAction.ts b/tools/plugin-toolkit/src/listCmds/listCmdAction.ts index 3d47b3f75cc..8d676a64be0 100644 --- a/tools/plugin-toolkit/src/listCmds/listCmdAction.ts +++ b/tools/plugin-toolkit/src/listCmds/listCmdAction.ts @@ -24,7 +24,7 @@ export async function listCmdAction(listObjFn: ListObjectsFn, options: ActionOpt const listEntries = await listObjFn(config, options); - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log( columnify(listEntries, { config: { diff --git a/tools/plugin-toolkit/src/typings.d.ts b/tools/plugin-toolkit/src/typings.d.ts index 8919d1ab423..dc4c8439bae 100644 --- a/tools/plugin-toolkit/src/typings.d.ts +++ b/tools/plugin-toolkit/src/typings.d.ts @@ -1,6 +1,6 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation -/* eslint-disable no-restricted-exports */ +// oxlint-disable eslint-js/no-restricted-exports declare module "*/package.json" { export const name: string; diff --git a/tools/react-app-template/.dependency-cruiser.js b/tools/react-app-template/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/react-app-template/.dependency-cruiser.js +++ b/tools/react-app-template/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/react-app-template/package.json b/tools/react-app-template/package.json index 1837b897357..2c09df11555 100644 --- a/tools/react-app-template/package.json +++ b/tools/react-app-template/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/react-app-template", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "private": true, "description": "GoodData Template for React App", "license": "LicenseRef-LICENSE", @@ -81,8 +81,9 @@ "eslint-plugin-sonarjs": "3.0.6", "html-webpack-plugin": "5.6.4", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "source-map-loader": "4.0.2", "style-loader": "3.3.4", "typescript": "7.0.2", diff --git a/tools/react-compatibility-test/src/harness/PluginLoader.tsx b/tools/react-compatibility-test/src/harness/PluginLoader.tsx index ae464b39ada..c0803fe12cb 100644 --- a/tools/react-compatibility-test/src/harness/PluginLoader.tsx +++ b/tools/react-compatibility-test/src/harness/PluginLoader.tsx @@ -1,4 +1,4 @@ -// (C) 2019-2025 GoodData Corporation +// (C) 2019-2026 GoodData Corporation import { useCallback, useState, useEffect } from "react"; @@ -7,7 +7,6 @@ import { ErrorComponent, LoadingComponent } from "@gooddata/sdk-ui"; import { DashboardConfig } from "@gooddata/sdk-ui-dashboard"; import { IEmbeddedPlugin, useDashboardLoaderWithPluginManipulation } from "@gooddata/sdk-ui-loaders"; - function addScriptTag(url: string): { element: HTMLScriptElement; promise: Promise } { const element = document.createElement("script"); @@ -20,7 +19,7 @@ function addScriptTag(url: string): { element: HTMLScriptElement; promise: Promi } element.onload = () => { - // eslint-disable-next-line no-console + // oxlint-disable-next-line eslint-js/no-console console.log(`Dynamic Script Loaded: ${url}`); resolve(); }; @@ -40,11 +39,7 @@ function addScriptTag(url: string): { element: HTMLScriptElement; promise: Promi }; } -function loadEntry( - moduleName: string, - __webpack_init_sharing__: any, - __webpack_share_scopes__: any, -) { +function loadEntry(moduleName: string, __webpack_init_sharing__: any, __webpack_share_scopes__: any) { return async () => { // Initializes the share scope. This fills it with known provided modules from this build and all remotes await __webpack_init_sharing__("default"); @@ -78,15 +73,20 @@ const useDynamicPlugins = () => { const scriptTag = addScriptTag(`https://localhost:8000/plugin/${moduleName}.mjs`); await scriptTag.promise; - const entry = await loadEntry(moduleName, __webpack_init_sharing__, __webpack_share_scopes__)(); - + const entry = await loadEntry( + moduleName, + __webpack_init_sharing__, + __webpack_share_scopes__, + )(); const pluginFactory = await (window as any)[moduleName].get(entry.pluginKey); const plugin = pluginFactory(); const resultingFactory = plugin.default; - if(PLUGIN_PARAMETERS) { - setPlugins([{ factory: resultingFactory , parameters: JSON.stringify(PLUGIN_PARAMETERS) }]); + if (PLUGIN_PARAMETERS) { + setPlugins([ + { factory: resultingFactory, parameters: JSON.stringify(PLUGIN_PARAMETERS) }, + ]); } else { setPlugins([{ factory: resultingFactory }]); } @@ -122,10 +122,9 @@ export const PluginLoaderWrapper = (props: any) => { return ; } return ; -} +}; export const PluginLoader = (props: any) => { - const { loaderStatus, reloadPlugins, setExtraPlugins, extraPlugins, hidePluginOverlays } = useDashboardLoaderWithPluginManipulation({ dashboard: DashboardRef, diff --git a/tools/reference-workspace-mgmt/.dependency-cruiser.js b/tools/reference-workspace-mgmt/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/reference-workspace-mgmt/.dependency-cruiser.js +++ b/tools/reference-workspace-mgmt/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/reference-workspace-mgmt/package.json b/tools/reference-workspace-mgmt/package.json index 030af49eb1a..ecae9e63c81 100644 --- a/tools/reference-workspace-mgmt/package.json +++ b/tools/reference-workspace-mgmt/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/reference-workspace-mgmt", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "Command line tools for reference workspace management", "license": "MIT", "author": "GoodData", @@ -57,8 +57,9 @@ "eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-sonarjs": "3.0.6", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2", "vitest": "4.1.8" } diff --git a/tools/reference-workspace/.dependency-cruiser.js b/tools/reference-workspace/.dependency-cruiser.js index f25517f11b7..382b0a8c871 100644 --- a/tools/reference-workspace/.dependency-cruiser.js +++ b/tools/reference-workspace/.dependency-cruiser.js @@ -6,7 +6,6 @@ import { DefaultSdkRules, } from "../../common/config/dep-cruiser/default.config.js"; -// eslint-disable-next-line no-restricted-exports export default { forbidden: [...DefaultRules, ...DefaultSdkRules], options: DefaultOptions, diff --git a/tools/reference-workspace/package.json b/tools/reference-workspace/package.json index f73f03c559b..cafddc4c600 100644 --- a/tools/reference-workspace/package.json +++ b/tools/reference-workspace/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/reference-workspace", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData SDK - Reference Workspace for tests", "license": "MIT", "author": "GoodData", @@ -57,8 +57,9 @@ "eslint-plugin-no-barrel-files": "1.2.2", "eslint-plugin-sonarjs": "3.0.6", "oxfmt": "0.66.0", - "oxlint": "1.57.0", - "oxlint-tsgolint": "0.18.1", + "oxlint": "1.67.0", + "oxlint-plugin-eslint": "1.82.0", + "oxlint-tsgolint": "0.22.1", "typescript": "7.0.2" } } diff --git a/tools/reference-workspace/src/index.ts b/tools/reference-workspace/src/index.ts index 8c9a2dd5b1b..dde3bd1ecc7 100644 --- a/tools/reference-workspace/src/index.ts +++ b/tools/reference-workspace/src/index.ts @@ -1,6 +1,6 @@ // (C) 2019-2026 GoodData Corporation -/* oxlint-disable no-barrel-files/no-barrel-files */ +// oxlint-disable no-barrel-files/no-barrel-files, eslint-js/no-restricted-syntax import * as ReferenceMdExt from "./md/ext.js"; import * as ReferenceMd from "./md/full.js"; diff --git a/tools/reference-workspace/src/typings.d.ts b/tools/reference-workspace/src/typings.d.ts index df9e23b5ef9..8cf15f31bc4 100644 --- a/tools/reference-workspace/src/typings.d.ts +++ b/tools/reference-workspace/src/typings.d.ts @@ -1,4 +1,4 @@ -// (C) 2007-2025 GoodData Corporation +// (C) 2007-2026 GoodData Corporation declare module "*/package.json" { export const name: string; @@ -7,12 +7,12 @@ declare module "*/package.json" { declare module "*.svg" { const value: any; - // eslint-disable-next-line no-restricted-exports + // oxlint-disable-next-line eslint-js/no-restricted-exports export default value; } declare module "*.json" { const value: any; - // eslint-disable-next-line no-restricted-exports + // oxlint-disable-next-line eslint-js/no-restricted-exports export default value; } diff --git a/tools/stylelint-config/package.json b/tools/stylelint-config/package.json index a0b3f12b91e..a934e704cb8 100644 --- a/tools/stylelint-config/package.json +++ b/tools/stylelint-config/package.json @@ -1,6 +1,6 @@ { "name": "@gooddata/stylelint-config", - "version": "11.57.0-alpha.1", + "version": "11.57.0-alpha.3", "description": "GoodData CSS Style Guide", "keywords": [ "config",