Compare commits

...

12 Commits

Author SHA1 Message Date
Uladzimir Havenchyk
d1a95da338 progress 2026-02-23 23:22:37 +03:00
Uladzimir Havenchyk
8d5219dfab cleanup 2026-02-20 14:51:11 +03:00
Uladzimir Havenchyk
0bebf28273 cleanup 2026-02-20 14:34:48 +03:00
Uladzimir Havenchyk
05eaeb9614 cleanup 2026-02-20 14:31:53 +03:00
Uladzimir Havenchyk
b5da1e91ad Merge remote-tracking branch 'origin/master' into react-router-2026 2026-02-20 14:21:43 +03:00
Uladzimir Havenchyk
a41cc6f2c1 finish migration 2026-02-20 14:16:40 +03:00
Uladzimir Havenchyk
21e82d4e1b wip 2026-02-20 10:22:33 +03:00
Uladzimir Havenchyk
7228610d15 progress 2026-02-20 09:06:55 +03:00
Uladzimir Havenchyk
31d313942f continue 2026-02-19 15:50:20 +03:00
Uladzimir Havenchyk
88d7f53f71 WIP 2026-02-19 15:24:54 +03:00
Uladzimir Havenchyk
cfc800b74f fixup 2026-02-19 15:20:17 +03:00
Uladzimir Havenchyk
8f0035422d upgrade react router 2026-02-19 15:20:15 +03:00
534 changed files with 9669 additions and 4611 deletions

View File

@@ -66,7 +66,6 @@
"@tiptap/suggestion": "^3.2.0",
"@types/js-yaml": "^4.0.9",
"@types/react-router": "3.0.27",
"@types/redux-auth-wrapper": "^2.0.16",
"@uiw/react-codemirror": "^4.23.6",
"@visx/axis": "^3.10.1",
"@visx/clip-path": "^3.3.0",
@@ -145,13 +144,11 @@
"react-markdown": "^9.0.0",
"react-redux": "^9.1.2",
"react-resizable": "^3.0.5",
"react-router": "3",
"react-router-redux": "^4.0.8",
"react-router-dom": "^7",
"react-transition-group": "^4.4.5",
"react-use": "^17.5.1",
"react-virtualized": "9.22.6",
"redux-actions": "^2.0.1",
"redux-auth-wrapper": "^2.1.0",
"redux-undo": "^1.1.0",
"rehype-external-links": "^2.0.1",
"remark-gfm": "^4.0.0",
@@ -264,7 +261,6 @@
"@types/react-grid-layout": "^1.3.5",
"@types/react-is": "^18.2.4",
"@types/react-resizable": "^3.0.4",
"@types/react-router-redux": "^4.0.53",
"@types/react-textarea-autosize": "^4.3.6",
"@types/react-transition-group": "^4.4.5",
"@types/react-virtualized": "^9.22.2",
@@ -1782,8 +1778,6 @@
"@types/react-router": ["@types/react-router@3.0.27", "", { "dependencies": { "@types/history": "^3", "@types/react": "*" } }, "sha512-Sq4bfTZ8TuHFTHXyzA3tu/BTGm/d4B8CwtOK4qOyJNoJMkY87qMOazkBizMHNpszVR1+kYOCNLjPdh/wAD7ESw=="],
"@types/react-router-redux": ["@types/react-router-redux@4.0.55", "", { "dependencies": { "@types/history": "^3.2.5", "redux": "^3.6.0" } }, "sha512-2fbxIc9OTmVyOUgBnRa3gcbMJT035Scu1bmkYMEiv7IzMht24W+gGzIrlYurSz3cijY4r9d16O/0+nycZ0RXjg=="],
"@types/react-textarea-autosize": ["@types/react-textarea-autosize@4.3.6", "", { "dependencies": { "@types/react": "*" } }, "sha512-cTf8tCem0c8A7CERYbTuF+bRFaqYu7N7HLCa6ZhUhDx8XnUsTpGx5udMWljt87JpciUKuUkImKPEsy6kcKhrcQ=="],
"@types/react-transition-group": ["@types/react-transition-group@4.4.5", "", { "dependencies": { "@types/react": "*" } }, "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA=="],
@@ -1794,8 +1788,6 @@
"@types/redux-actions": ["@types/redux-actions@2.6.5", "", {}, "sha512-RgXOigay5cNweP+xH1ru+Vaaj1xXYLpWIfSVO8cSA8Ii2xvR+HRfWYdLe1UVOA8X0kIklalGOa0DTDyld0obkg=="],
"@types/redux-auth-wrapper": ["@types/redux-auth-wrapper@2.0.16", "", { "dependencies": { "@types/history": "^4.7.11", "@types/react": "*", "redux": "^3.7.2" } }, "sha512-Uj3IFMqVD4e9aCiBPrw+Ix5msJvWlrjRAVa1iV529zNzEYhdtLfVq9/tM4K9yZWMRjLFqXbTam2qz2bgnklrSA=="],
"@types/resolve": ["@types/resolve@1.20.6", "", {}, "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ=="],
"@types/retry": ["@types/retry@0.12.2", "", {}, "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow=="],
@@ -2434,8 +2426,6 @@
"create-hmac": ["create-hmac@1.1.7", "", { "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", "inherits": "^2.0.1", "ripemd160": "^2.0.0", "safe-buffer": "^5.0.1", "sha.js": "^2.4.8" } }, "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg=="],
"create-react-class": ["create-react-class@15.7.0", "", { "dependencies": { "loose-envify": "^1.3.1", "object-assign": "^4.1.1" } }, "sha512-QZv4sFWG9S5RUvkTYWbflxeZX+JG7Cz0Tn33rQBJ+WFQTqTfUTjMjiv9tnfXazjsO5r0KhPs+AqCjyrQX6h2ng=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
"cron-expression-validator": ["cron-expression-validator@1.0.20", "", {}, "sha512-g0osBTdp+1ryDw2vzlG6UpDPaa4fO94ZChF2R0lEnRurbuUEL74XEVX7xZJ13m4Mq/gb3ni6UQu8+Oqt+eocsw=="],
@@ -2604,8 +2594,6 @@
"decode-tiff": ["decode-tiff@0.2.1", "", {}, "sha512-v/7hQBv/DrOVQ+Eljg0BLMRbXZYuuw3YZ8duZuFxYpo6qUkdn7oFRkN95RZKbnh08EHNjrMXMbEUNhTLuhPvvA=="],
"decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
"decompress-response": ["decompress-response@3.3.0", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M="],
"dedent": ["dedent@0.6.0", "", {}, "sha1-Dm2o8M5Sg471zsXI+TlrDBtko8s="],
@@ -4412,9 +4400,9 @@
"react-resizable": ["react-resizable@3.0.5", "", { "dependencies": { "prop-types": "15.x", "react-draggable": "^4.0.3" } }, "sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w=="],
"react-router": ["react-router@3.2.6", "", { "dependencies": { "create-react-class": "^15.5.1", "history": "^3.0.0", "hoist-non-react-statics": "^3.3.2", "invariant": "^2.2.1", "loose-envify": "^1.2.0", "prop-types": "^15.7.2", "react-is": "^16.13.0", "warning": "^3.0.0" } }, "sha512-nlxtQE8B22hb/JxdaslI1tfZacxFU8x8BJryXOnR2RxB4vc01zuHYAHAIgmBkdk1kzXaA25hZxK6KAH/+CXArw=="],
"react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="],
"react-router-redux": ["react-router-redux@4.0.8", "", {}, "sha1-InQDWWtRUeGCN32rg1tdRfD4BU4="],
"react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="],
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
@@ -4458,12 +4446,10 @@
"reduce-reducers": ["reduce-reducers@0.4.3", "", {}, "sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw=="],
"redux": ["redux@3.7.2", "", { "dependencies": { "lodash": "^4.2.1", "lodash-es": "^4.2.1", "loose-envify": "^1.1.0", "symbol-observable": "^1.0.3" } }, "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A=="],
"redux": ["redux@4.2.0", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA=="],
"redux-actions": ["redux-actions@2.6.5", "", { "dependencies": { "invariant": "^2.2.4", "just-curry-it": "^3.1.0", "loose-envify": "^1.4.0", "reduce-reducers": "^0.4.3", "to-camel-case": "^1.0.0" } }, "sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw=="],
"redux-auth-wrapper": ["redux-auth-wrapper@2.1.0", "", { "dependencies": { "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4", "lodash.isempty": "^4.4.0", "prop-types": "^15.5.0", "query-string": "^5.1.1" } }, "sha512-UtU64cJk2pWcMMfgWIVoyBVG0p8ZiGJ++vqrvQ5r5ghZZOLRq+M5aTS0RRNneiB+aCCZBzI+txFSaKYVRrv8qQ=="],
"redux-thunk": ["redux-thunk@3.1.0", "", {}, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="],
"redux-undo": ["redux-undo@1.1.0", "", {}, "sha512-zzLFh2qeF0MTIlzDhDLm9NtkfBqCllQJ3OCuIl5RKlG/ayHw6GUdIFdMhzMS9NnrnWdBX5u//ExMOHpfudGGOg=="],
@@ -4636,6 +4622,8 @@
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
@@ -4846,8 +4834,6 @@
"swc-loader": ["swc-loader@0.2.6", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg=="],
"symbol-observable": ["symbol-observable@1.2.0", "", {}, "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="],
"symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
"synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="],
@@ -5628,8 +5614,6 @@
"@types/react-virtualized/@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
"@types/redux-auth-wrapper/@types/history": ["@types/history@4.7.11", "", {}, "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA=="],
"@types/serve-index/@types/express": ["@types/express@4.17.13", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
@@ -5930,8 +5914,6 @@
"diffie-hellman/bn.js": ["bn.js@4.11.9", "", {}, "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="],
"dnd-core/redux": ["redux@4.2.0", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA=="],
"dom-serializer/domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
@@ -6600,7 +6582,7 @@
"react-remove-scroll-bar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"react-router/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"react-router/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -6628,8 +6610,6 @@
"reduce-function-call/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"redux-auth-wrapper/query-string": ["query-string@5.1.1", "", { "dependencies": { "decode-uri-component": "^0.2.0", "object-assign": "^4.1.0", "strict-uri-encode": "^1.0.0" } }, "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw=="],
"reg-cli/glob": ["glob@7.2.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q=="],
"reg-cli/make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
@@ -7808,10 +7788,6 @@
"qs/side-channel/object-inspect": ["object-inspect@1.13.1", "", {}, "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="],
"react-dnd-html5-backend/dnd-core/redux": ["redux@4.2.0", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA=="],
"react-dnd/dnd-core/redux": ["redux@4.2.0", "", { "dependencies": { "@babel/runtime": "^7.9.2" } }, "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA=="],
"react-docgen-typescript-plugin/find-cache-dir/make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
"react-docgen/@types/babel__core/@types/babel__traverse": ["@types/babel__traverse@7.14.2", "", { "dependencies": { "@babel/types": "^7.3.0" } }, "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA=="],

View File

@@ -1,7 +1,5 @@
import type { Location } from "history";
import { useCallback } from "react";
import { withRouter } from "react-router";
import { push } from "react-router-redux";
import { useAsyncFn, useMount } from "react-use";
import { updateDataPermission } from "metabase/admin/permissions/permissions";
@@ -14,6 +12,8 @@ import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErr
import { useDatabaseQuery } from "metabase/common/hooks";
import { getParentPath } from "metabase/hoc/ModalRoute";
import { useDispatch } from "metabase/lib/redux";
import { useRouter } from "metabase/router";
import { useNavigation } from "metabase/routing";
import { updateImpersonation } from "metabase-enterprise/advanced_permissions/reducer";
import { getImpersonation } from "metabase-enterprise/advanced_permissions/selectors";
import type {
@@ -31,10 +31,6 @@ import { ImpersonationModalView } from "./ImpersonationModalView";
interface ImpersonationModalProps {
params: ImpersonationModalParams;
location: Location;
route: {
path: string;
};
}
const parseParams = (params: ImpersonationModalParams): ImpersonationParams => {
@@ -47,11 +43,10 @@ const parseParams = (params: ImpersonationModalParams): ImpersonationParams => {
};
};
const ImpersonationModalInner = ({
route,
params,
location,
}: ImpersonationModalProps) => {
export const ImpersonationModal = ({ params }: ImpersonationModalProps) => {
const { push } = useNavigation();
const { routes, location } = useRouter();
const route = routes[routes.length - 1];
const [
{
loading: isImpersonationLoading,
@@ -90,10 +85,11 @@ const ImpersonationModalInner = ({
draftImpersonation?.attribute ?? impersonation?.attribute;
const dispatch = useDispatch();
const parentPath = route ? getParentPath(route, location as Location) : "/";
const close = useCallback(() => {
dispatch(push(getParentPath(route, location)));
}, [dispatch, route, location]);
push(parentPath);
}, [parentPath, push]);
const handleSave = useCallback(
(attribute: UserAttributeKey) => {
@@ -125,8 +121,8 @@ const ImpersonationModalInner = ({
);
const handleCancel = useCallback(() => {
dispatch(push(getParentPath(route, location)));
}, [dispatch, route, location]);
push(parentPath);
}, [parentPath, push]);
useMount(() => {
dispatch(fetchUserAttributes());
@@ -158,5 +154,3 @@ const ImpersonationModalInner = ({
/>
);
};
export const ImpersonationModal = withRouter(ImpersonationModalInner);

View File

@@ -1,7 +1,6 @@
import { combineReducers } from "@reduxjs/toolkit";
import userEvent from "@testing-library/user-event";
import fetchMock from "fetch-mock";
import { Route } from "react-router";
import {
setupDatabaseEndpoints,
@@ -10,6 +9,7 @@ import {
setupUserAttributesEndpoint,
} from "__support__/server-mocks";
import {
Route,
renderWithProviders,
screen,
waitFor,

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import Database from "metabase-lib/v1/metadata/Database";
import type { Database as IDatabase } from "metabase-types/api";
import { createMockDatabase } from "metabase-types/api/mocks";

View File

@@ -1,4 +1,3 @@
import { Link } from "react-router";
import { t } from "ttag";
import { DatabaseConnectionHealthInfo } from "metabase/admin/databases/components/DatabaseConnectionHealthInfo";
@@ -11,6 +10,7 @@ import {
isDbModifiable,
} from "metabase/admin/databases/utils";
import { useUpdateDatabaseMutation } from "metabase/api";
import { Link } from "metabase/common/components/Link";
import { useConfirmation } from "metabase/common/hooks/use-confirmation";
import * as Urls from "metabase/lib/urls";
import type { WritableConnectionInfoSectionProps } from "metabase/plugins/oss/permissions";

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { DataPermissionValue } from "metabase/admin/permissions/types";
@@ -7,6 +6,7 @@ import {
getGroupFocusPermissionsUrl,
} from "metabase/admin/permissions/utils/urls";
import { ModalRoute } from "metabase/hoc/ModalRoute";
import { pushPath } from "metabase/lib/navigation";
import {
PLUGIN_ADMIN_PERMISSIONS_DATABASE_ACTIONS,
PLUGIN_ADMIN_PERMISSIONS_DATABASE_GROUP_ROUTES,
@@ -151,8 +151,8 @@ export function initializePlugin() {
label: t`Edit Impersonated`,
iconColor: "warning",
icon: "database",
actionCreator: (entityId, groupId, view) =>
push(getEditImpersonationUrl(entityId, groupId, view)),
actionCreator: (entityId, groupId, view) => () =>
pushPath(getEditImpersonationUrl(entityId, groupId, view)),
});
PLUGIN_DATA_PERMISSIONS.upgradeViewPermissionsIfNeeded =

View File

@@ -1,6 +1,4 @@
import { useMemo, useState } from "react";
import type { Route } from "react-router";
import { push } from "react-router-redux";
import { t } from "ttag";
import { SettingsSection } from "metabase/admin/components/SettingsSection";
@@ -13,8 +11,8 @@ import { LeaveRouteConfirmModal } from "metabase/common/components/LeaveConfirmM
import { DelayedLoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
import { DatabaseForm } from "metabase/databases/components/DatabaseForm";
import type { DatabaseFormConfig } from "metabase/databases/types";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Box, Flex, ScrollArea, Title } from "metabase/ui";
import type { Database, DatabaseData } from "metabase-types/api";
@@ -34,12 +32,10 @@ type WritableConnectionInfoPageParams = {
type WritableConnectionInfoPageProps = {
params: WritableConnectionInfoPageParams;
route: Route;
};
export function WritableConnectionInfoPage({
params,
route,
}: WritableConnectionInfoPageProps) {
const databaseId = Urls.extractEntityId(params.databaseId);
const {
@@ -52,34 +48,32 @@ export function WritableConnectionInfoPage({
return <DelayedLoadingAndErrorWrapper loading={isLoading} error={error} />;
}
return <WritableConnectionInfoPageBody database={database} route={route} />;
return <WritableConnectionInfoPageBody database={database} />;
}
type WritableConnectionInfoPageBodyProps = {
database: Database;
route: Route;
};
function WritableConnectionInfoPageBody({
database,
route,
}: WritableConnectionInfoPageBodyProps) {
const { push } = useNavigation();
const title = getTitle(database);
const initialValues = useMemo(() => getInitialValues(database), [database]);
const [isDirty, setIsDirty] = useState(false);
const [updateDatabase, { isLoading: isSaving }] = useUpdateDatabaseMutation();
const dispatch = useDispatch();
const handleSubmit = async (newValues: DatabaseData) => {
await updateDatabase({
id: database.id,
write_data_details: getSubmitDetails(newValues),
}).unwrap();
dispatch(push(Urls.viewDatabase(database.id)));
push(Urls.viewDatabase(database.id));
};
const handleCancel = () => {
dispatch(push(Urls.viewDatabase(database.id)));
push(Urls.viewDatabase(database.id));
};
return (
@@ -108,7 +102,7 @@ function WritableConnectionInfoPageBody({
</SettingsSection>
</Box>
</Box>
<LeaveRouteConfirmModal isEnabled={isDirty && !isSaving} route={route} />
<LeaveRouteConfirmModal isEnabled={isDirty && !isSaving} />
</Flex>
);
}

View File

@@ -1,6 +1,5 @@
import type { PayloadAction } from "@reduxjs/toolkit";
import { createAction, createSlice } from "@reduxjs/toolkit";
import { push } from "react-router-redux";
import {
LOAD_DATA_PERMISSIONS,
@@ -16,20 +15,16 @@ import {
DATABASES_BASE_PATH,
GROUPS_BASE_PATH,
} from "metabase/admin/permissions/utils/urls";
import { pushPath } from "metabase/lib/navigation";
import type { GroupId, Impersonation } from "metabase-types/api";
export const getImpersonatedPostAction = (
entityId: EntityId,
groupId: GroupId,
view: "database" | "group",
) =>
view === "database"
? push(
`${DATABASES_BASE_PATH}/${entityId.databaseId}/impersonated/group/${groupId}`,
)
: push(
`${GROUPS_BASE_PATH}/${groupId}/impersonated/database/${entityId.databaseId}`,
);
export const getImpersonatedPostAction =
(entityId: EntityId, groupId: GroupId, view: "database" | "group") => () =>
pushPath(
view === "database"
? `${DATABASES_BASE_PATH}/${entityId.databaseId}/impersonated/group/${groupId}`
: `${GROUPS_BASE_PATH}/${groupId}/impersonated/database/${entityId.databaseId}`,
);
const UPDATE_IMPERSONATION =
"metabase-enterprise/advanced-permissions/UPDATE_IMPERSONATION";

View File

@@ -1,12 +1,3 @@
import { Route } from "react-router";
import { WritableConnectionInfoPage } from "./pages/WritableConnectionInfoPage";
export function getWritableConnectionInfoRoutes() {
return (
<Route
path=":databaseId/write-data"
component={WritableConnectionInfoPage}
/>
);
return null;
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect } from "react";
import type { Route } from "react-router";
import _ from "underscore";
import { ApplicationPermissionsHelp } from "metabase/admin/permissions/components/ApplicationPermissionsHelp";
@@ -40,7 +39,6 @@ interface ApplicationPermissionsPageProps {
initialize: () => void;
savePermissions: () => void;
updatePermission: any;
route: Route;
}
const ApplicationPermissionsPage = ({
@@ -49,7 +47,6 @@ const ApplicationPermissionsPage = ({
initialize,
savePermissions,
updatePermission,
route,
}: ApplicationPermissionsPageProps) => {
useEffect(() => {
initialize();
@@ -73,7 +70,6 @@ const ApplicationPermissionsPage = ({
<PermissionsPageLayout
tab="application"
isDirty={isDirty}
route={route}
helpContent={<ApplicationPermissionsHelp />}
onSave={savePermissions}
onLoad={() => initialize()}

View File

@@ -1,10 +1,4 @@
import { Route } from "react-router";
import ApplicationPermissionsPage from "./pages/ApplicationPermissionsPage";
const getRoutes = () => (
<Route path="application" component={ApplicationPermissionsPage} />
);
const getRoutes = () => null;
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default getRoutes;

View File

@@ -3,13 +3,13 @@ import "../components/AuditTableVisualization";
import { chain } from "icepick";
import PropTypes from "prop-types";
import { useState } from "react";
import { push } from "react-router-redux";
import _ from "underscore";
import { PaginationControls } from "metabase/common/components/PaginationControls";
import { usePagination } from "metabase/common/hooks/use-pagination";
import CS from "metabase/css/core/index.css";
import { connect } from "metabase/lib/redux";
import { useNavigation } from "metabase/routing";
import { getMetadata } from "metabase/selectors/metadata";
import Question from "metabase-lib/v1/Question";
@@ -48,6 +48,7 @@ function AuditTable({
onLoad,
...rest
}) {
const { push } = useNavigation();
const [loadedCount, setLoadedCount] = useState(0);
const { handleNextPage, handlePreviousPage, page } = usePagination();
@@ -64,7 +65,7 @@ function AuditTable({
const question = new Question(card, metadata);
const shouldShowPagination = page > 0 || loadedCount === pageSize;
const handleChangeLocation = (url) => dispatch(push(url));
const handleChangeLocation = (url) => push(url);
return (
<div>

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import _ from "underscore";
export const getColumnName = (column) => column.remapped_to || column.name;
@@ -53,9 +52,8 @@ const AuditDrill = ({ question, clicked }) => {
name: "detail",
title: `View this`,
default: true,
action() {
const url = columnNameToUrl[column.name](value, clicked);
return push(url);
url() {
return columnNameToUrl[column.name](value, clicked);
},
},
];
@@ -76,10 +74,8 @@ const AuditDrill = ({ question, clicked }) => {
name: "detail",
title: `View this`,
default: true,
action() {
return push(
`/admin/audit/query/${encodeURIComponent(String(value))}`,
);
url() {
return `/admin/audit/query/${encodeURIComponent(String(value))}`;
},
},
];

View File

@@ -1,8 +1,8 @@
import Cookies from "js-cookie";
import { replace } from "react-router-redux";
import { logout, refreshSession } from "metabase/auth/actions";
import { isSameOrSiteUrlOrigin } from "metabase/lib/dom";
import { replacePath } from "metabase/lib/navigation";
export const SESSION_KEY = "metabase.TIMEOUT";
export const COOKIE_POOLING_TIMEOUT = 3000;
@@ -33,7 +33,7 @@ export const createSessionMiddleware = (
const redirectUrl = getRedirectUrl();
if (wasLoggedIn && !!redirectUrl) {
store.dispatch(replace(redirectUrl));
replacePath(redirectUrl);
}
intervalId = setInterval(async () => {
@@ -46,7 +46,7 @@ export const createSessionMiddleware = (
await store.dispatch(refreshSession())?.unwrap();
if (redirectUrl !== null) {
store.dispatch(replace(redirectUrl));
replacePath(redirectUrl);
}
} else {
const url = location.pathname + location.search + location.hash;

View File

@@ -1,8 +1,8 @@
import FakeTimers from "@sinonjs/fake-timers";
import Cookie from "js-cookie";
import { replace } from "react-router-redux";
import { logout, refreshSession } from "metabase/auth/actions";
import { replacePath } from "metabase/lib/navigation";
import {
COOKIE_POOLING_TIMEOUT,
@@ -15,8 +15,8 @@ jest.mock("metabase/auth/actions", () => ({
logout: jest.fn(),
refreshSession: jest.fn(() => Promise.resolve()),
}));
jest.mock("react-router-redux", () => ({
replace: jest.fn(),
jest.mock("metabase/lib/navigation", () => ({
replacePath: jest.fn(),
}));
let clock;
@@ -136,8 +136,10 @@ describe("createSessionMiddleware", () => {
handleAction(actionStub);
clock.tick(COOKIE_POOLING_TIMEOUT);
expect(dispatchMock).toHaveBeenCalled();
expect(replace).toHaveBeenCalledWith("/question/1?query=5#hash");
expect(dispatchMock).not.toHaveBeenCalledWith(
expect.objectContaining({ type: "ROUTER_REPLACE" }),
);
expect(replacePath).toHaveBeenCalledWith("/question/1?query=5#hash");
});
});
@@ -166,7 +168,10 @@ describe("createSessionMiddleware", () => {
// wait for the refreshSession to resolve
await Promise.resolve();
expect(replace).toHaveBeenCalledWith("/question/1?query=5#hash");
expect(dispatchMock).not.toHaveBeenCalledWith(
expect.objectContaining({ type: "ROUTER_REPLACE" }),
);
expect(replacePath).toHaveBeenCalledWith("/question/1?query=5#hash");
});
});
});

View File

@@ -1,3 +1,5 @@
import type { Location } from "history";
import { useLocation, useParams } from "react-router-dom";
import { t } from "ttag";
import { skipToken, useListCollectionItemsQuery } from "metabase/api";
@@ -6,6 +8,7 @@ import { UserHasSeen } from "metabase/common/components/UserHasSeen/UserHasSeen"
import { ModalRoute } from "metabase/hoc/ModalRoute";
import * as Urls from "metabase/lib/urls";
import { PLUGIN_COLLECTIONS } from "metabase/plugins";
import { createModalRoute } from "metabase/routing";
import { Badge, Icon, Menu } from "metabase/ui";
import { useListStaleCollectionItemsQuery } from "metabase-enterprise/api/collection";
import { hasPremiumFeature } from "metabase-enterprise/settings";
@@ -14,6 +17,23 @@ import { CleanupCollectionModal } from "./CleanupCollectionModal";
import { getDateFilterValue } from "./CleanupCollectionModal/utils";
import { canCleanUp } from "./utils";
const CleanupCollectionModalWithRouteProps = ({
onClose,
}: {
onClose: () => void;
}) => {
const params = useParams<{ slug?: string }>();
const location = useLocation();
return (
<CleanupCollectionModal
onClose={onClose}
params={{ slug: params.slug }}
location={location as unknown as Location}
/>
);
};
/**
* Initialize clean_up plugin features that depend on hasPremiumFeature.
*/
@@ -80,5 +100,8 @@ export function initializePlugin() {
PLUGIN_COLLECTIONS.cleanUpRoute = (
<ModalRoute path="cleanup" modal={CleanupCollectionModal} />
);
PLUGIN_COLLECTIONS.cleanUpRouteObjects = [
createModalRoute("cleanup", CleanupCollectionModalWithRouteProps),
];
}
}

View File

@@ -1,11 +1,11 @@
import { useState } from "react";
import { push } from "react-router-redux";
import { t } from "ttag";
import { ForwardRefLink } from "metabase/common/components/Link";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { PLUGIN_SNIPPET_FOLDERS } from "metabase/plugins";
import { useNavigation } from "metabase/routing";
import {
canUserCreateNativeQueries,
canUserCreateQueries,
@@ -23,7 +23,7 @@ export const CreateMenu = ({
metricCollectionId?: CollectionId;
canWriteToMetricCollection?: boolean;
}) => {
const dispatch = useDispatch();
const { push } = useNavigation();
const [modal, setModal] = useState<"snippet-folder" | "publish-table">();
const closeModal = () => setModal(undefined);
@@ -104,7 +104,7 @@ export const CreateMenu = ({
<PublishTableModal
opened={modal === "publish-table"}
onClose={closeModal}
onPublished={(table) => dispatch(push(Urls.dataStudioTable(table.id)))}
onPublished={(table) => push(Urls.dataStudioTable(table.id))}
/>
</>
);

View File

@@ -1,6 +1,6 @@
import { Link } from "react-router";
import { t } from "ttag";
import { Link } from "metabase/common/components/Link";
import { useSelector } from "metabase/lib/redux";
import { dataStudioArchivedSnippets } from "metabase/lib/urls";
import { getUserIsAdmin } from "metabase/selectors/user";

View File

@@ -1,8 +1,7 @@
import userEvent from "@testing-library/user-event";
import { Route } from "react-router";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import { dataStudioArchivedSnippets } from "metabase/lib/urls";
import type { EnterpriseSettings } from "metabase-types/api";
import { createMockUser } from "metabase-types/api/mocks";

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import { jt, t } from "ttag";
import { useGetTableSelectionInfoQuery } from "metabase/api";
@@ -10,10 +9,10 @@ import {
FormProvider,
FormSubmitButton,
} from "metabase/forms";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useMetadataToasts } from "metabase/metadata/hooks";
import type { PublishTablesModalProps } from "metabase/plugins";
import { useNavigation } from "metabase/routing";
import {
Box,
Button,
@@ -99,6 +98,7 @@ function ModalBody({
onPublish,
onClose,
}: ModalBodyProps) {
const { push } = useNavigation();
const { data, isLoading, error } = useGetTableSelectionInfoQuery({
database_ids: databaseIds,
schema_ids: schemaIds,
@@ -106,7 +106,6 @@ function ModalBody({
});
const [publishTables] = usePublishTablesMutation();
const { sendSuccessToast } = useMetadataToasts();
const dispatch = useDispatch();
if (isLoading || error != null || data == null) {
return <DelayedLoadingAndErrorWrapper loading={isLoading} error={error} />;
@@ -124,7 +123,7 @@ function ModalBody({
if (collection != null) {
sendSuccessToast(
t`Published`,
() => dispatch(push(Urls.dataStudioLibrary())),
() => push(Urls.dataStudioLibrary()),
t`Go to ${collection.name}`,
);
} else {

View File

@@ -5,7 +5,10 @@ import { hasPremiumFeature } from "metabase-enterprise/settings";
import { CreateLibraryModal } from "./components/CreateLibraryModal";
import { PublishTablesModal } from "./components/PublishTablesModal";
import { UnpublishTablesModal } from "./components/UnpublishTablesModal";
import { getDataStudioLibraryRoutes } from "./routes";
import {
getDataStudioLibraryRouteObjects,
getDataStudioLibraryRoutes,
} from "./routes";
import {
useGetLibraryChildCollectionByType,
useGetLibraryCollection,
@@ -16,6 +19,8 @@ export function initializePlugin() {
if (hasPremiumFeature("library")) {
PLUGIN_LIBRARY.isEnabled = true;
PLUGIN_LIBRARY.getDataStudioLibraryRoutes = getDataStudioLibraryRoutes;
PLUGIN_LIBRARY.getDataStudioLibraryRouteObjects =
getDataStudioLibraryRouteObjects;
PLUGIN_LIBRARY.useGetLibraryCollection = useGetLibraryCollection;
PLUGIN_LIBRARY.useGetLibraryChildCollectionByType =
useGetLibraryChildCollectionByType;

View File

@@ -1,13 +1,13 @@
import { type ReactNode, useState } from "react";
import { push } from "react-router-redux";
import { c, t } from "ttag";
import { ForwardRefLink } from "metabase/common/components/Link";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { ArchiveCardModal } from "metabase/questions/components/ArchiveCardModal";
import { CardCopyModal } from "metabase/questions/components/CardCopyModal";
import { MoveCardModal } from "metabase/questions/components/MoveCardModal";
import { useNavigation } from "metabase/routing";
import { getMetadata } from "metabase/selectors/metadata";
import { ActionIcon, Icon, Menu } from "metabase/ui";
import * as Lib from "metabase-lib";
@@ -118,18 +118,18 @@ type MetricModalProps = {
};
function MetricModal({ card, modalType, onClose }: MetricModalProps) {
const dispatch = useDispatch();
const { push } = useNavigation();
const handleCopy = (newCard: Card) => {
dispatch(push(Urls.dataStudioMetric(newCard.id)));
push(Urls.dataStudioMetric(newCard.id));
};
const handleArchive = () => {
dispatch(push(Urls.dataStudioLibrary()));
push(Urls.dataStudioLibrary());
};
const handleUnarchive = () => {
dispatch(push(Urls.dataStudioMetric(card.id)));
push(Urls.dataStudioMetric(card.id));
};
switch (modalType) {

View File

@@ -1,8 +1,8 @@
import { Fragment, useMemo } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import { useGetCardQuery, useGetTableQuery } from "metabase/api";
import { Link } from "metabase/common/components/Link";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { getMetadata } from "metabase/selectors/metadata";

View File

@@ -1,5 +1,4 @@
import { useLayoutEffect, useMemo, useState } from "react";
import type { Route } from "react-router";
import { useLatest } from "react-use";
import { t } from "ttag";
@@ -31,10 +30,9 @@ type MetricQueryPageParams = {
type MetricQueryPageProps = {
params: MetricQueryPageParams;
route: Route;
};
export function MetricQueryPage({ params, route }: MetricQueryPageProps) {
export function MetricQueryPage({ params }: MetricQueryPageProps) {
const cardId = Urls.extractEntityId(params.cardId);
const { card, isLoading, error } = useLoadCardWithMetadata(cardId);
@@ -46,15 +44,14 @@ export function MetricQueryPage({ params, route }: MetricQueryPageProps) {
);
}
return <MetricQueryPageBody card={card} route={route} />;
return <MetricQueryPageBody card={card} />;
}
type MetricQueryPageBodyProps = {
card: CardType;
route: Route;
};
function MetricQueryPageBody({ card, route }: MetricQueryPageBodyProps) {
function MetricQueryPageBody({ card }: MetricQueryPageBodyProps) {
const metadata = useSelector(getMetadata);
const [datasetQuery, setDatasetQuery] = useState(card.dataset_query);
const [uiState, setUiState] = useState(getInitialUiState);
@@ -163,7 +160,6 @@ function MetricQueryPageBody({ card, route }: MetricQueryPageBodyProps) {
/>
)}
<LeaveRouteConfirmModal
route={route}
isEnabled={isDirty && !isSaving && !isCheckingDependencies}
/>
</>

View File

@@ -1,12 +1,10 @@
import { useDisclosure } from "@mantine/hooks";
import type { Location } from "history";
import { useMemo, useState } from "react";
import { Link, type Route } from "react-router";
import { push } from "react-router-redux";
import { t } from "ttag";
import { useGetDefaultCollectionId } from "metabase/collections/hooks";
import { LeaveRouteConfirmModal } from "metabase/common/components/LeaveConfirmModal";
import { Link } from "metabase/common/components/Link";
import { DataStudioBreadcrumbs } from "metabase/data-studio/common/components/DataStudioBreadcrumbs";
import { PageContainer } from "metabase/data-studio/common/components/PageContainer";
import {
@@ -15,9 +13,10 @@ import {
PaneHeaderInput,
} from "metabase/data-studio/common/components/PaneHeader";
import { getResultMetadata } from "metabase/data-studio/common/utils";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { getInitialUiState } from "metabase/querying/editor/components/QueryEditor";
import { useNavigation } from "metabase/routing";
import { getMetadata } from "metabase/selectors/metadata";
import { Card } from "metabase/ui";
import * as Lib from "metabase-lib";
@@ -35,11 +34,11 @@ type NewMetricPageQuery = {
};
type NewMetricPageProps = {
location: Location<NewMetricPageQuery>;
route: Route;
location: { search: string };
};
export function NewMetricPage({ location, route }: NewMetricPageProps) {
export function NewMetricPage({ location }: NewMetricPageProps) {
const { push } = useNavigation();
const metadata = useSelector(getMetadata);
const [name, setName] = useState("");
const [datasetQuery, setDatasetQuery] = useState(() =>
@@ -48,11 +47,13 @@ export function NewMetricPage({ location, route }: NewMetricPageProps) {
const [uiState, setUiState] = useState(getInitialUiState);
const [isModalOpened, { open: openModal, close: closeModal }] =
useDisclosure();
const queryParams = Object.fromEntries(
new URLSearchParams(location.search),
) as NewMetricPageQuery;
const initialCollectionId = Urls.extractCollectionId(
location.query.collectionId,
queryParams.collectionId,
);
const defaultCollectionId = useGetDefaultCollectionId();
const dispatch = useDispatch();
const query = useMemo(
() => getQuery(datasetQuery, metadata),
@@ -79,7 +80,7 @@ export function NewMetricPage({ location, route }: NewMetricPageProps) {
);
const handleCreate = (card: CardType) => {
dispatch(push(Urls.dataStudioMetric(card.id)));
push(Urls.dataStudioMetric(card.id));
};
const handleChangeQuery = (query: Lib.Query) => {
@@ -87,7 +88,7 @@ export function NewMetricPage({ location, route }: NewMetricPageProps) {
};
const handleCancel = () => {
dispatch(push(Urls.dataStudioLibrary()));
push(Urls.dataStudioLibrary());
};
return (
@@ -137,7 +138,7 @@ export function NewMetricPage({ location, route }: NewMetricPageProps) {
onClose={closeModal}
/>
)}
<LeaveRouteConfirmModal route={route} isEnabled={!isModalOpened} />
<LeaveRouteConfirmModal isEnabled={!isModalOpened} />
</>
);
}

View File

@@ -1,4 +1,5 @@
import { IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
import { PLUGIN_CACHING, PLUGIN_DEPENDENCIES } from "metabase/plugins";
@@ -7,23 +8,70 @@ import { MetricOverviewPage } from "./pages/MetricOverviewPage";
import { MetricQueryPage } from "./pages/MetricQueryPage";
import { NewMetricPage } from "./pages/NewMetricPage";
export function getDataStudioMetricRoutes() {
const NewMetricPageWithRouteProps = () => {
const location = useLocation();
return <NewMetricPage location={location} />;
};
const MetricOverviewPageWithRouteProps = () => {
const params = useParams<{ cardId?: string }>();
return <MetricOverviewPage params={{ cardId: params.cardId ?? "" }} />;
};
const MetricQueryPageWithRouteProps = () => {
const params = useParams<{ cardId?: string }>();
return <MetricQueryPage params={{ cardId: params.cardId ?? "" }} />;
};
const MetricDependenciesPageWithRouteProps = () => {
const params = useParams<{ cardId?: string }>();
return <MetricDependenciesPage params={{ cardId: params.cardId ?? "" }} />;
};
const MetricCachingPageWithRouteProps = () => {
const params = useParams<{ cardId?: string }>();
return (
<Route path="metrics">
<Route path="new" component={NewMetricPage} />
<Route path=":cardId" component={MetricOverviewPage} />
<Route path=":cardId/query" component={MetricQueryPage} />
{PLUGIN_DEPENDENCIES.isEnabled && (
<Route path=":cardId/dependencies" component={MetricDependenciesPage}>
<IndexRoute component={PLUGIN_DEPENDENCIES.DependencyGraphPage} />
</Route>
)}
{PLUGIN_CACHING.isGranularCachingEnabled() && (
<Route
path=":cardId/caching"
component={PLUGIN_CACHING.MetricCachingPage}
/>
)}
</Route>
<PLUGIN_CACHING.MetricCachingPage
params={{ cardId: params.cardId ?? "" }}
/>
);
};
export function getDataStudioMetricRoutes() {
return null;
}
export function getDataStudioMetricRouteObjects(): RouteObject[] {
return [
{
path: "metrics",
children: [
{ path: "new", element: <NewMetricPageWithRouteProps /> },
{ path: ":cardId", element: <MetricOverviewPageWithRouteProps /> },
{ path: ":cardId/query", element: <MetricQueryPageWithRouteProps /> },
...(PLUGIN_DEPENDENCIES.isEnabled
? [
{
path: ":cardId/dependencies",
element: <MetricDependenciesPageWithRouteProps />,
children: [
{
index: true,
element: <PLUGIN_DEPENDENCIES.DependencyGraphPage />,
},
],
} satisfies RouteObject,
]
: []),
...(PLUGIN_CACHING.isGranularCachingEnabled()
? [
{
path: ":cardId/caching",
element: <MetricCachingPageWithRouteProps />,
} satisfies RouteObject,
]
: []),
],
},
];
}

View File

@@ -1,20 +1,27 @@
import { IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { getDataStudioSegmentRoutes } from "metabase/data-studio/segments/routes";
import { getDataStudioSegmentRouteObjects } from "metabase/data-studio/segments/routes";
import { LibrarySectionLayout } from "./LibrarySectionLayout";
import { getDataStudioMetricRoutes } from "./metrics/routes";
import { getDataStudioSnippetRoutes } from "./snippets/routes";
import { getDataStudioTableRoutes } from "./tables/routes";
import { getDataStudioMetricRouteObjects } from "./metrics/routes";
import { getDataStudioSnippetRouteObjects } from "./snippets/routes";
import { getDataStudioTableRouteObjects } from "./tables/routes";
export const getDataStudioLibraryRoutes = () => {
return (
<Route path="library">
<IndexRoute component={LibrarySectionLayout} />
{getDataStudioTableRoutes()}
{getDataStudioMetricRoutes()}
{getDataStudioSegmentRoutes()}
{getDataStudioSnippetRoutes()}
</Route>
);
return null;
};
export const getDataStudioLibraryRouteObjects = (): RouteObject[] => {
return [
{
path: "library",
children: [
{ index: true, element: <LibrarySectionLayout /> },
...getDataStudioTableRouteObjects(),
...getDataStudioMetricRouteObjects(),
...getDataStudioSegmentRouteObjects(),
...getDataStudioSnippetRouteObjects(),
],
},
];
};

View File

@@ -1,9 +1,9 @@
import type { ReactNode } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import { useUpdateSnippetMutation } from "metabase/api";
import { getErrorMessage } from "metabase/api/utils";
import { Link } from "metabase/common/components/Link";
import { useToast } from "metabase/common/hooks";
import { DataStudioBreadcrumbs } from "metabase/data-studio/common/components/DataStudioBreadcrumbs";
import {

View File

@@ -1,9 +1,7 @@
import { Route } from "react-router";
import { setupEnterpriseOnlyPlugin } from "__support__/enterprise";
import type { ENTERPRISE_PLUGIN_NAME } from "__support__/enterprise-typed";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type {
EnterpriseSettings,
NativeQuerySnippet,

View File

@@ -1,12 +1,11 @@
import { Link } from "react-router";
import { push } from "react-router-redux";
import { t } from "ttag";
import { useUpdateSnippetMutation } from "metabase/api";
import { getErrorMessage } from "metabase/api/utils";
import { Link } from "metabase/common/components/Link";
import { useToast } from "metabase/common/hooks";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Button, Group, Modal, Stack, Text } from "metabase/ui";
import type { NativeQuerySnippet } from "metabase-types/api";
@@ -17,7 +16,7 @@ type ArchiveSnippetModalProps = {
export function ArchiveSnippetModal(props: ArchiveSnippetModalProps) {
const { snippet, onClose } = props;
const dispatch = useDispatch();
const { push } = useNavigation();
const [updateSnippet, { isLoading }] = useUpdateSnippetMutation();
const [sendToast] = useToast();
@@ -49,7 +48,7 @@ export function ArchiveSnippetModal(props: ArchiveSnippetModalProps) {
),
icon: "check",
});
dispatch(push(Urls.dataStudioLibrary()));
push(Urls.dataStudioLibrary());
onClose();
}
};

View File

@@ -1,9 +1,9 @@
import { useCallback, useState } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import { useUpdateSnippetMutation } from "metabase/api";
import { getErrorMessage } from "metabase/api/utils";
import { Link } from "metabase/common/components/Link";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { SectionLayout } from "metabase/data-studio/app/components/SectionLayout";
import { DataStudioBreadcrumbs } from "metabase/data-studio/common/components/DataStudioBreadcrumbs";

View File

@@ -1,6 +1,5 @@
import { sql } from "@codemirror/lang-sql";
import { useLayoutEffect, useMemo, useState } from "react";
import type { Route } from "react-router";
import { usePreviousDistinct } from "react-use";
import { t } from "ttag";
@@ -34,10 +33,9 @@ type EditSnippetPageParams = {
type EditSnippetPageProps = {
params: EditSnippetPageParams;
route: Route;
};
export function EditSnippetPage({ params, route }: EditSnippetPageProps) {
export function EditSnippetPage({ params }: EditSnippetPageProps) {
const snippetId = Urls.extractEntityId(params.snippetId);
const [sendToast] = useToast();
const remoteSyncReadOnly = useSelector(
@@ -179,7 +177,6 @@ export function EditSnippetPage({ params, route }: EditSnippetPageProps) {
</PageContainer>
<LeaveRouteConfirmModal
key={snippetId}
route={route}
isEnabled={isDirty && !isSaving}
/>
</>

View File

@@ -1,10 +1,8 @@
import { Route } from "react-router";
import { setupEnterpriseOnlyPlugin } from "__support__/enterprise";
import type { ENTERPRISE_PLUGIN_NAME } from "__support__/enterprise-typed";
import { setupNativeQuerySnippetEndpoints } from "__support__/server-mocks";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type {
EnterpriseSettings,
NativeQuerySnippet,

View File

@@ -1,7 +1,5 @@
import { sql } from "@codemirror/lang-sql";
import { useEffect, useMemo, useState } from "react";
import type { Route } from "react-router";
import { push } from "react-router-redux";
import { t } from "ttag";
import { useCreateSnippetMutation } from "metabase/api";
@@ -19,9 +17,10 @@ import {
PaneHeaderActions,
PaneHeaderInput,
} from "metabase/data-studio/common/components/PaneHeader";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { PLUGIN_REMOTE_SYNC, PLUGIN_SNIPPET_FOLDERS } from "metabase/plugins";
import { useNavigation } from "metabase/routing";
import { Card, Flex, Stack } from "metabase/ui";
import type {
NativeQuerySnippet,
@@ -32,12 +31,8 @@ import S from "./NewSnippetPage.module.css";
const SNIPPET_NAME_MAX_LENGTH = 254;
type NewSnippetPageProps = {
route: Route;
};
export function NewSnippetPage({ route }: NewSnippetPageProps) {
const dispatch = useDispatch();
export function NewSnippetPage() {
const { push } = useNavigation();
const [sendToast] = useToast();
const [name, setName] = useState(t`New SQL snippet`);
const [description, setDescription] = useState("");
@@ -74,9 +69,9 @@ export function NewSnippetPage({ route }: NewSnippetPageProps) {
useEffect(() => {
if (savedSnippet) {
dispatch(push(Urls.dataStudioSnippet(savedSnippet.id)));
push(Urls.dataStudioSnippet(savedSnippet.id));
}
}, [savedSnippet, dispatch]);
}, [savedSnippet, push]);
const handleSave = async () => {
if (!PLUGIN_SNIPPET_FOLDERS.isEnabled) {
@@ -87,7 +82,7 @@ export function NewSnippetPage({ route }: NewSnippetPageProps) {
};
const handleCancel = () => {
dispatch(push(Urls.dataStudioLibrary()));
push(Urls.dataStudioLibrary());
};
const handleCollectionSelected = async (
@@ -167,10 +162,7 @@ export function NewSnippetPage({ route }: NewSnippetPageProps) {
</Stack>
</Flex>
</PageContainer>
<LeaveRouteConfirmModal
route={route}
isEnabled={!savedSnippet && !isSaving}
/>
<LeaveRouteConfirmModal isEnabled={!savedSnippet && !isSaving} />
<PLUGIN_SNIPPET_FOLDERS.CollectionPickerModal
isOpen={isCollectionPickerOpen}
onSelect={handleCollectionSelected}

View File

@@ -1,4 +1,5 @@
import { IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { useParams } from "react-router-dom";
import { PLUGIN_DEPENDENCIES } from "metabase/plugins";
@@ -7,20 +8,33 @@ import { EditSnippetPage } from "./pages/EditSnippetPage";
import { NewSnippetPage } from "./pages/NewSnippetPage";
import { SnippetDependenciesPage } from "./pages/SnippetDependenciesPage";
const EditSnippetPageWithRouteProps = () => {
const params = useParams<{ snippetId?: string }>();
return <EditSnippetPage params={{ snippetId: params.snippetId ?? "" }} />;
};
export function getDataStudioSnippetRoutes() {
return (
<>
<Route path="snippets/new" component={NewSnippetPage} />
<Route path="snippets/archived" component={ArchivedSnippetsPage} />
<Route path="snippets/:snippetId" component={EditSnippetPage} />
{PLUGIN_DEPENDENCIES.isEnabled && (
<Route
path="snippets/:snippetId/dependencies"
component={SnippetDependenciesPage}
>
<IndexRoute component={PLUGIN_DEPENDENCIES.DependencyGraphPage} />
</Route>
)}
</>
);
return null;
}
export function getDataStudioSnippetRouteObjects(): RouteObject[] {
return [
{ path: "snippets/new", element: <NewSnippetPage /> },
{ path: "snippets/archived", element: <ArchivedSnippetsPage /> },
{ path: "snippets/:snippetId", element: <EditSnippetPageWithRouteProps /> },
...(PLUGIN_DEPENDENCIES.isEnabled
? [
{
path: "snippets/:snippetId/dependencies",
element: <SnippetDependenciesPage />,
children: [
{
index: true,
element: <PLUGIN_DEPENDENCIES.DependencyGraphPage />,
},
],
} satisfies RouteObject,
]
: []),
];
}

View File

@@ -1,11 +1,11 @@
import { useState } from "react";
import { push } from "react-router-redux";
import { c, t } from "ttag";
import { ForwardRefLink } from "metabase/common/components/Link";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { PLUGIN_LIBRARY, PLUGIN_REMOTE_SYNC } from "metabase/plugins";
import { useNavigation } from "metabase/routing";
import { ActionIcon, Icon, Menu } from "metabase/ui";
import type { Table } from "metabase-types/api";
@@ -16,7 +16,7 @@ type TableMoreMenuProps = {
};
export function TableMoreMenu({ table }: TableMoreMenuProps) {
const dispatch = useDispatch();
const { push } = useNavigation();
const [modalType, setModalType] = useState<TableModalType>();
const remoteSyncReadOnly = useSelector(
PLUGIN_REMOTE_SYNC.getIsRemoteSyncReadOnly,
@@ -24,7 +24,7 @@ export function TableMoreMenu({ table }: TableMoreMenuProps) {
const handleUnpublish = () => {
setModalType(undefined);
dispatch(push(Urls.dataStudioLibrary()));
push(Urls.dataStudioLibrary());
};
return (

View File

@@ -1,10 +1,9 @@
import userEvent from "@testing-library/user-event";
import { Route } from "react-router";
import { setupEnterpriseOnlyPlugin } from "__support__/enterprise";
import type { ENTERPRISE_PLUGIN_NAME } from "__support__/enterprise-typed";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type { EnterpriseSettings, TokenFeatures } from "metabase-types/api";
import {
createMockTable,

View File

@@ -1,9 +1,7 @@
import { Route } from "react-router";
import { setupEnterpriseOnlyPlugin } from "__support__/enterprise";
import type { ENTERPRISE_PLUGIN_NAME } from "__support__/enterprise-typed";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type {
EnterpriseSettings,
Table,

View File

@@ -1,9 +1,7 @@
import { Route } from "react-router";
import { setupEnterpriseOnlyPlugin } from "__support__/enterprise";
import type { ENTERPRISE_PLUGIN_NAME } from "__support__/enterprise-typed";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type {
EnterpriseSettings,
Table,

View File

@@ -1,4 +1,5 @@
import { IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { useParams } from "react-router-dom";
import { PublishedTableMeasureDependenciesPage } from "metabase/data-studio/measures/pages/PublishedTableMeasureDependenciesPage";
import { PublishedTableMeasureDetailPage } from "metabase/data-studio/measures/pages/PublishedTableMeasureDetailPage";
@@ -9,7 +10,7 @@ import { PublishedTableSegmentDependenciesPage } from "metabase/data-studio/segm
import { PublishedTableSegmentDetailPage } from "metabase/data-studio/segments/pages/PublishedTableSegmentDetailPage";
import { PublishedTableSegmentRevisionHistoryPage } from "metabase/data-studio/segments/pages/PublishedTableSegmentRevisionHistoryPage";
import { PLUGIN_DEPENDENCIES } from "metabase/plugins";
import { IsAdmin } from "metabase/route-guards";
import { IsAdminGuard } from "metabase/routing";
import { TableDependenciesPage } from "./pages/TableDependenciesPage";
import { TableFieldsPage } from "./pages/TableFieldsPage";
@@ -17,57 +18,230 @@ import { TableMeasuresPage } from "./pages/TableMeasuresPage";
import { TableOverviewPage } from "./pages/TableOverviewPage";
import { TableSegmentsPage } from "./pages/TableSegmentsPage";
export function getDataStudioTableRoutes() {
type TableRouteParams = {
tableId?: string;
fieldId?: string;
segmentId?: string;
measureId?: string;
};
const TableOverviewPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return <TableOverviewPage params={{ tableId: params.tableId ?? "" }} />;
};
const TableFieldsPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<Route path="tables">
<Route path=":tableId" component={TableOverviewPage} />
<Route path=":tableId/fields" component={TableFieldsPage} />
<Route path=":tableId/fields/:fieldId" component={TableFieldsPage} />
<Route path=":tableId/segments" component={TableSegmentsPage} />
<Route path=":tableId/segments/new" component={IsAdmin}>
<IndexRoute component={PublishedTableNewSegmentPage} />
</Route>
<Route
path=":tableId/segments/:segmentId"
component={PublishedTableSegmentDetailPage}
/>
<Route
path=":tableId/segments/:segmentId/revisions"
component={PublishedTableSegmentRevisionHistoryPage}
/>
{PLUGIN_DEPENDENCIES.isEnabled && (
<Route
path=":tableId/segments/:segmentId/dependencies"
component={PublishedTableSegmentDependenciesPage}
>
<IndexRoute component={PLUGIN_DEPENDENCIES.DependencyGraphPage} />
</Route>
)}
<Route path=":tableId/measures" component={TableMeasuresPage} />
<Route path=":tableId/measures/new" component={IsAdmin}>
<IndexRoute component={PublishedTableNewMeasurePage} />
</Route>
<Route
path=":tableId/measures/:measureId"
component={PublishedTableMeasureDetailPage}
/>
<Route
path=":tableId/measures/:measureId/revisions"
component={PublishedTableMeasureRevisionHistoryPage}
/>
{PLUGIN_DEPENDENCIES.isEnabled && (
<Route
path=":tableId/measures/:measureId/dependencies"
component={PublishedTableMeasureDependenciesPage}
>
<IndexRoute component={PLUGIN_DEPENDENCIES.DependencyGraphPage} />
</Route>
)}
{PLUGIN_DEPENDENCIES.isEnabled && (
<Route path=":tableId/dependencies" component={TableDependenciesPage}>
<IndexRoute component={PLUGIN_DEPENDENCIES.DependencyGraphPage} />
</Route>
)}
</Route>
<TableFieldsPage
params={{ tableId: params.tableId ?? "", fieldId: params.fieldId }}
/>
);
};
const TableSegmentsPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return <TableSegmentsPage params={{ tableId: params.tableId ?? "" }} />;
};
const PublishedTableNewSegmentPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableNewSegmentPage params={{ tableId: params.tableId ?? "" }} />
);
};
const PublishedTableSegmentDetailPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableSegmentDetailPage
params={{
tableId: params.tableId ?? "",
segmentId: params.segmentId ?? "",
}}
/>
);
};
const PublishedTableSegmentRevisionHistoryPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableSegmentRevisionHistoryPage
params={{
tableId: params.tableId ?? "",
segmentId: params.segmentId ?? "",
}}
/>
);
};
const PublishedTableSegmentDependenciesPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableSegmentDependenciesPage
params={{
tableId: params.tableId ?? "",
segmentId: params.segmentId ?? "",
}}
/>
);
};
const TableMeasuresPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return <TableMeasuresPage params={{ tableId: params.tableId ?? "" }} />;
};
const PublishedTableNewMeasurePageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableNewMeasurePage params={{ tableId: params.tableId ?? "" }} />
);
};
const PublishedTableMeasureDetailPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableMeasureDetailPage
params={{
tableId: params.tableId ?? "",
measureId: params.measureId ?? "",
}}
/>
);
};
const PublishedTableMeasureRevisionHistoryPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableMeasureRevisionHistoryPage
params={{
tableId: params.tableId ?? "",
measureId: params.measureId ?? "",
}}
/>
);
};
const PublishedTableMeasureDependenciesPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return (
<PublishedTableMeasureDependenciesPage
params={{
tableId: params.tableId ?? "",
measureId: params.measureId ?? "",
}}
/>
);
};
const TableDependenciesPageWithRouteProps = () => {
const params = useParams<TableRouteParams>();
return <TableDependenciesPage params={{ tableId: params.tableId ?? "" }} />;
};
export function getDataStudioTableRoutes() {
return null;
}
export function getDataStudioTableRouteObjects(): RouteObject[] {
return [
{
path: "tables",
children: [
{ path: ":tableId", element: <TableOverviewPageWithRouteProps /> },
{ path: ":tableId/fields", element: <TableFieldsPageWithRouteProps /> },
{
path: ":tableId/fields/:fieldId",
element: <TableFieldsPageWithRouteProps />,
},
{
path: ":tableId/segments",
element: <TableSegmentsPageWithRouteProps />,
},
{
path: ":tableId/segments/new",
element: (
<IsAdminGuard>
<PublishedTableNewSegmentPageWithRouteProps />
</IsAdminGuard>
),
},
{
path: ":tableId/segments/:segmentId",
element: <PublishedTableSegmentDetailPageWithRouteProps />,
},
{
path: ":tableId/segments/:segmentId/revisions",
element: <PublishedTableSegmentRevisionHistoryPageWithRouteProps />,
},
...(PLUGIN_DEPENDENCIES.isEnabled
? [
{
path: ":tableId/segments/:segmentId/dependencies",
element: (
<PublishedTableSegmentDependenciesPageWithRouteProps />
),
children: [
{
index: true,
element: <PLUGIN_DEPENDENCIES.DependencyGraphPage />,
},
],
} satisfies RouteObject,
]
: []),
{
path: ":tableId/measures",
element: <TableMeasuresPageWithRouteProps />,
},
{
path: ":tableId/measures/new",
element: (
<IsAdminGuard>
<PublishedTableNewMeasurePageWithRouteProps />
</IsAdminGuard>
),
},
{
path: ":tableId/measures/:measureId",
element: <PublishedTableMeasureDetailPageWithRouteProps />,
},
{
path: ":tableId/measures/:measureId/revisions",
element: <PublishedTableMeasureRevisionHistoryPageWithRouteProps />,
},
...(PLUGIN_DEPENDENCIES.isEnabled
? [
{
path: ":tableId/measures/:measureId/dependencies",
element: (
<PublishedTableMeasureDependenciesPageWithRouteProps />
),
children: [
{
index: true,
element: <PLUGIN_DEPENDENCIES.DependencyGraphPage />,
},
],
} satisfies RouteObject,
]
: []),
...(PLUGIN_DEPENDENCIES.isEnabled
? [
{
path: ":tableId/dependencies",
element: <TableDependenciesPageWithRouteProps />,
children: [
{
index: true,
element: <PLUGIN_DEPENDENCIES.DependencyGraphPage />,
},
],
} satisfies RouteObject,
]
: []),
],
},
];
}

View File

@@ -1,13 +1,12 @@
import { useMemo } from "react";
import { Link } from "react-router";
import { push } from "react-router-redux";
import { t } from "ttag";
import { useDispatch } from "metabase/lib/redux";
import { ForwardRefLink } from "metabase/common/components/Link";
import * as Urls from "metabase/lib/urls";
import { transformEdit } from "metabase/lib/urls";
import { useMetadataToasts } from "metabase/metadata/hooks";
import type { EditTransformMenuProps } from "metabase/plugins/oss/database";
import { useNavigation } from "metabase/routing";
import {
Box,
Button,
@@ -29,7 +28,7 @@ import { getCheckoutDisabledMessage } from "metabase-enterprise/data-studio/work
import type { DatabaseId, Transform } from "metabase-types/api";
export function EditTransformMenu({ transform }: EditTransformMenuProps) {
const dispatch = useDispatch();
const { push } = useNavigation();
const { sendErrorToast } = useMetadataToasts();
const sourceDatabaseId = getTransformDatabaseId(transform);
@@ -98,7 +97,7 @@ export function EditTransformMenu({ transform }: EditTransformMenuProps) {
id: number;
name?: string;
}) => {
dispatch(push(Urls.dataStudioWorkspace(workspace.id, transform.id)));
push(Urls.dataStudioWorkspace(workspace.id, transform.id));
};
const handleCreateWorkspace = async ({
@@ -116,7 +115,7 @@ export function EditTransformMenu({ transform }: EditTransformMenuProps) {
database_id: Number(databaseId),
}).unwrap();
dispatch(push(Urls.dataStudioWorkspace(workspace.id, transform.id)));
push(Urls.dataStudioWorkspace(workspace.id, transform.id));
} catch (error) {
sendErrorToast(t`Failed to create workspace`);
}
@@ -137,7 +136,7 @@ export function EditTransformMenu({ transform }: EditTransformMenuProps) {
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
component={Link}
component={ForwardRefLink}
to={transformEdit(transform.id)}
leftSection={<Icon name="pencil" />}
>

View File

@@ -1,12 +1,11 @@
import userEvent from "@testing-library/user-event";
import { Route } from "react-router";
import {
setupCreateWorkspaceEndpoint,
setupWorkspaceCheckoutEndpoint,
setupWorkspacesEndpoint,
} from "__support__/server-mocks";
import { renderWithProviders, screen, waitFor } from "__support__/ui";
import { Route, renderWithProviders, screen, waitFor } from "__support__/ui";
import type {
Transform,
Workspace,

View File

@@ -2,17 +2,17 @@ import { useDisclosure } from "@mantine/hooks";
import cx from "classnames";
import dayjs from "dayjs";
import { useCallback, useMemo, useState } from "react";
import { push } from "react-router-redux";
import { t } from "ttag";
import { ConfirmModal } from "metabase/common/components/ConfirmModal/ConfirmModal";
import { ForwardRefLink } from "metabase/common/components/Link";
import S from "metabase/data-studio/app/pages/DataStudioLayout/DataStudioLayout.module.css";
import type { MetabaseColorKey } from "metabase/lib/colors/types";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useMetadataToasts } from "metabase/metadata/hooks";
import type { WorkspacesSectionProps } from "metabase/plugins/oss/database";
import { useNavigation } from "metabase/routing";
import { getLocation } from "metabase/selectors/routing";
import {
ActionIcon,
@@ -45,7 +45,7 @@ import type {
const TOOLTIP_OPEN_DELAY = 700;
export function WorkspacesSection({ showLabel }: WorkspacesSectionProps) {
const dispatch = useDispatch();
const { push } = useNavigation();
const [isWorkspacesExpanded, setIsWorkspacesExpanded] = useState(true);
const { pathname } = useSelector(getLocation);
const { data: workspacesData, isLoading: areWorkspacesLoading } =
@@ -62,11 +62,11 @@ export function WorkspacesSection({ showLabel }: WorkspacesSectionProps) {
const handleCreateWorkspace = useCallback(async () => {
try {
const workspace = await createWorkspace().unwrap();
dispatch(push(Urls.dataStudioWorkspace(workspace.id)));
push(Urls.dataStudioWorkspace(workspace.id));
} catch (error) {
sendErrorToast(t`Failed to create workspace`);
}
}, [createWorkspace, dispatch, sendErrorToast]);
}, [createWorkspace, push, sendErrorToast]);
const [archiveWorkspace] = useArchiveWorkspaceMutation();
const [unarchiveWorkspace] = useUnarchiveWorkspaceMutation();
@@ -94,7 +94,7 @@ export function WorkspacesSection({ showLabel }: WorkspacesSectionProps) {
try {
await deleteWorkspace(id).unwrap();
sendSuccessToast(t`Workspace deleted successfully`);
dispatch(push(Urls.dataStudioWorkspaceList()));
push(Urls.dataStudioWorkspaceList());
} catch (error) {
sendErrorToast(t`Failed to delete workspace`);
}
@@ -276,7 +276,7 @@ function WorkspaceItem({
to={Urls.dataStudioWorkspace(workspace.id)}
p="0.75rem"
bdrs="md"
name={workspace.name}
aria-label={workspace.name}
>
<Flex align="flex-start" justify="space-between" gap="xs">
<Stack gap="xs" style={{ flex: 1, minWidth: 0 }}>

View File

@@ -4,7 +4,10 @@ import { hasPremiumFeature } from "metabase-enterprise/settings";
import { AdminDatabaseWorkspacesSection } from "./admin/AdminDatabaseWorkspacesSection";
import { EditTransformMenu } from "./components/EditTransformMenu";
import { WorkspacesSection } from "./components/WorkspaceSection";
import { getDataStudioWorkspaceRoutes } from "./routes";
import {
getDataStudioWorkspaceRouteObjects,
getDataStudioWorkspaceRoutes,
} from "./routes";
export function initializePlugin() {
if (hasPremiumFeature("workspaces")) {
@@ -14,6 +17,8 @@ export function initializePlugin() {
PLUGIN_WORKSPACES.WorkspacesSection = WorkspacesSection;
PLUGIN_WORKSPACES.getDataStudioWorkspaceRoutes =
getDataStudioWorkspaceRoutes;
PLUGIN_WORKSPACES.getDataStudioWorkspaceRouteObjects =
getDataStudioWorkspaceRouteObjects;
PLUGIN_WORKSPACES.EditTransformMenu = EditTransformMenu;
}
}

View File

@@ -1,15 +1,14 @@
import { useEffect, useMemo } from "react";
import { replace } from "react-router-redux";
import { t } from "ttag";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Box, Text, Title } from "metabase/ui";
import { useGetWorkspacesQuery } from "metabase-enterprise/api";
export function WorkspaceListPage() {
const dispatch = useDispatch();
const { replace } = useNavigation();
const { data: workspacesData, error, isFetching } = useGetWorkspacesQuery();
const workspaces = useMemo(
@@ -24,8 +23,8 @@ export function WorkspaceListPage() {
return;
}
dispatch(replace(Urls.dataStudioWorkspace(firstWorkspaceId)));
}, [dispatch, error, firstWorkspaceId]);
replace(Urls.dataStudioWorkspace(firstWorkspaceId));
}, [error, firstWorkspaceId, replace]);
if (error) {
return <LoadingAndErrorWrapper error={error} loading={false} />;

View File

@@ -12,8 +12,6 @@ import {
import classNames from "classnames";
import { useCallback, useEffect, useRef, useState } from "react";
import { ResizableBox } from "react-resizable";
import type { Route } from "react-router";
import { replace } from "react-router-redux";
import { useLocation } from "react-use";
import { t } from "ttag";
@@ -23,9 +21,9 @@ import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErr
import { ResizeHandle } from "metabase/common/components/ResizeHandle";
import { Sortable } from "metabase/common/components/Sortable";
import { PaneHeaderInput } from "metabase/data-studio/common/components/PaneHeader";
import { useDispatch } from "metabase/lib/redux";
import { checkNotNull } from "metabase/lib/types";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { NAME_MAX_LENGTH } from "metabase/transforms/constants";
import {
ActionIcon,
@@ -65,19 +63,14 @@ type WorkspacePageProps = {
params: {
workspaceId: string;
};
route: Route;
transformId?: string;
};
function WorkspacePageContent({
params,
route,
transformId,
}: WorkspacePageProps) {
function WorkspacePageContent({ params, transformId }: WorkspacePageProps) {
const { replace } = useNavigation();
const pointerSensor = useSensor(PointerSensor, {
activationConstraint: { distance: 10 },
});
const dispatch = useDispatch();
const workspaceId = parseInt(params.workspaceId, 10);
const {
@@ -144,15 +137,9 @@ function WorkspacePageContent({
const parsedId = parseInt(transformId, 10);
await handleNavigateToTransform(isNaN(parsedId) ? transformId : parsedId);
dispatch(replace(Urls.dataStudioWorkspace(workspaceId)));
replace(Urls.dataStudioWorkspace(workspaceId));
})();
}, [
transformId,
isLoading,
workspaceId,
handleNavigateToTransform,
dispatch,
]);
}, [transformId, isLoading, workspaceId, handleNavigateToTransform, replace]);
const handleTabClose = useCallback(
(event: React.MouseEvent, tab: WorkspaceTab, index: number) => {
@@ -535,12 +522,12 @@ function WorkspacePageContent({
/>
)}
<LeaveRouteConfirmModal isEnabled={hasUnsavedChanges} route={route} />
<LeaveRouteConfirmModal isEnabled={hasUnsavedChanges} />
</Stack>
);
}
export const WorkspacePage = ({ params, route }: WorkspacePageProps) => {
export const WorkspacePage = ({ params }: WorkspacePageProps) => {
const workspaceId = Number(params.workspaceId);
const { search } = useLocation();
const transformId = new URLSearchParams(search).get("transformId");
@@ -550,7 +537,6 @@ export const WorkspacePage = ({ params, route }: WorkspacePageProps) => {
<WorkspacePageContent
key={workspaceId}
params={params}
route={route}
transformId={transformId ?? undefined}
/>
</WorkspaceProvider>

View File

@@ -1,10 +1,9 @@
import { useCallback, useState } from "react";
import { replace } from "react-router-redux";
import { t } from "ttag";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useMetadataToasts } from "metabase/metadata/hooks";
import { useNavigation } from "metabase/routing";
import {
useLazyGetWorkspaceTablesQuery,
useMergeWorkspaceMutation,
@@ -38,7 +37,7 @@ export function useWorkspaceActions({
workspaceTransforms,
availableTransforms,
}: UseWorkspaceActionsParams) {
const dispatch = useDispatch();
const { replace } = useNavigation();
const { sendErrorToast, sendSuccessToast } = useMetadataToasts();
const { addOpenedTab, addOpenedTransform, setActiveTransformRef } =
@@ -66,7 +65,7 @@ export function useWorkspaceActions({
);
return;
}
dispatch(replace(Urls.transformList()));
replace(Urls.transformList());
sendSuccessToast(
t`Workspace '${response.workspace.name}' merged successfully`,
);
@@ -75,7 +74,7 @@ export function useWorkspaceActions({
throw error;
}
},
[workspaceId, mergeWorkspace, sendErrorToast, dispatch, sendSuccessToast],
[workspaceId, mergeWorkspace, replace, sendErrorToast, sendSuccessToast],
);
const handleWorkspaceNameChange = useCallback(

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { push } from "react-router-redux";
import { t } from "ttag";
import { useLazyGetTransformQuery } from "metabase/api";
@@ -7,6 +6,7 @@ import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useRegisterMetabotContextProvider } from "metabase/metabot";
import { PLUGIN_METABOT } from "metabase/plugins";
import { useNavigation } from "metabase/routing";
import { getIsWorkspace } from "metabase/selectors/routing";
import { METABOT_PROFILE_OVERRIDES } from "metabase-enterprise/metabot/constants";
import type {
@@ -86,6 +86,7 @@ export function useWorkspaceMetabot({
setTab,
handleNavigateToTransform,
}: UseWorkspaceMetabotParams): UseWorkspaceMetabotReturn {
const { push } = useNavigation();
const dispatch = useDispatch();
const isWorkspace = useEnterpriseSelector(getIsWorkspace);
const {
@@ -392,7 +393,7 @@ export function useWorkspaceMetabot({
return;
}
dispatch(push(navigateToPath));
push(navigateToPath);
setNavigateToPath(null);
}, [
navigateToPath,
@@ -401,8 +402,8 @@ export function useWorkspaceMetabot({
setActiveTab,
setNavigateToPath,
handleNavigateToTransform,
dispatch,
isWorkspace,
push,
]);
return {

View File

@@ -1,13 +1,21 @@
import { IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { useParams } from "react-router-dom";
import { WorkspaceListPage } from "./pages/WorkspaceListPage/WorkspaceListPage";
import { WorkspacePage } from "./pages/WorkspacePage/WorkspacePage";
const WorkspacePageWithRouteProps = () => {
const params = useParams<{ workspaceId?: string }>();
return <WorkspacePage params={{ workspaceId: params.workspaceId ?? "" }} />;
};
export function getDataStudioWorkspaceRoutes() {
return (
<>
<IndexRoute component={WorkspaceListPage} />
<Route path=":workspaceId" component={WorkspacePage} />
</>
);
return null;
}
export function getDataStudioWorkspaceRouteObjects(): RouteObject[] {
return [
{ index: true, element: <WorkspaceListPage /> },
{ path: ":workspaceId", element: <WorkspacePageWithRouteProps /> },
];
}

View File

@@ -1,5 +1,4 @@
import { useEffect, useState } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import {
@@ -13,6 +12,7 @@ import {
import { hasDbRoutingEnabled } from "metabase/admin/databases/utils";
import { skipToken, useListUserAttributesQuery } from "metabase/api";
import { getErrorMessage } from "metabase/api/utils";
import { Link } from "metabase/common/components/Link";
import { useSetting } from "metabase/common/hooks";
import { useToast } from "metabase/common/hooks/use-toast";
import { useSelector } from "metabase/lib/redux";

View File

@@ -1,4 +1,3 @@
import { Link } from "react-router";
import { match } from "ts-pattern";
import { t } from "ttag";
@@ -7,6 +6,7 @@ import {
hasFeature,
hasWritableConnectionDetails,
} from "metabase/admin/databases/utils";
import { Link } from "metabase/common/components/Link";
import { Text } from "metabase/ui";
import type { Database } from "metabase-types/api";

View File

@@ -1,6 +1,4 @@
import { useMemo } from "react";
import type { Route } from "react-router";
import { push, replace } from "react-router-redux";
import { t } from "ttag";
import { DatabaseEditConnectionForm } from "metabase/admin/databases/components/DatabaseEditConnectionForm";
@@ -12,6 +10,7 @@ import { usePageTitle } from "metabase/hooks/use-page-title";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { addUndo } from "metabase/redux/undo";
import { useNavigation } from "metabase/routing";
import { Flex, Icon, Modal, Text } from "metabase/ui";
import { useCreateDestinationDatabaseMutation } from "metabase-enterprise/api";
import type { Database, DatabaseData } from "metabase-types/api";
@@ -23,11 +22,10 @@ import { pickPrefillFieldsFromPrimaryDb } from "./utils";
export const DestinationDatabaseConnectionModal = ({
params: { databaseId, destinationDatabaseId },
route,
}: {
params: { databaseId: string; destinationDatabaseId?: string };
route: Route;
}) => {
const { push, replace } = useNavigation();
const dispatch = useDispatch();
// eslint-disable-next-line metabase/no-unconditional-metabase-links-render -- Admin settings
@@ -59,9 +57,9 @@ export const DestinationDatabaseConnectionModal = ({
const handleCloseModal = (method = "push") => {
const dbId = parseInt(databaseId, 10);
if (method === "push") {
dispatch(push(Urls.viewDatabase(dbId)));
push(Urls.viewDatabase(dbId));
} else {
dispatch(replace(Urls.viewDatabase(dbId)));
replace(Urls.viewDatabase(dbId));
}
};
@@ -141,7 +139,6 @@ export const DestinationDatabaseConnectionModal = ({
handleSaveDb={handleSaveDatabase}
onSubmitted={handleOnSubmit}
onCancel={handleCloseModal}
route={route}
config={{
name: { isSlug: true },
engine: { fieldState: "hidden" },

View File

@@ -1,10 +1,9 @@
import { useMemo } from "react";
import { Link } from "react-router";
import { c, t } from "ttag";
import { DatabaseConnectionHealthInfo } from "metabase/admin/databases/components/DatabaseConnectionHealthInfo";
import { useListDatabasesQuery } from "metabase/api";
import { ForwardRefLink } from "metabase/common/components/Link";
import { ForwardRefLink, Link } from "metabase/common/components/Link";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { useSelector } from "metabase/lib/redux";
import { getUserIsAdmin } from "metabase/selectors/user";

View File

@@ -1,8 +1,7 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Modal } from "metabase/ui";
import { DestinationDatabasesList } from "../DestinationDatabasesList";
@@ -14,11 +13,10 @@ export const DestinationDatabasesModal = ({
}: {
params: { databaseId: string };
}) => {
const { push } = useNavigation();
const primaryDbId = parseInt(params.databaseId, 10);
const dispatch = useDispatch();
const handleCloseModal = () => {
dispatch(push(Urls.viewDatabase(primaryDbId)));
push(Urls.viewDatabase(primaryDbId));
};
return (

View File

@@ -1,11 +1,10 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { DeleteDatabaseModal } from "metabase/admin/databases/components/DeleteDatabaseModel/DeleteDatabaseModal";
import { useDeleteDatabaseMutation, useGetDatabaseQuery } from "metabase/api";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { useDispatch } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Modal } from "metabase/ui";
export const RemoveDestinationDatabaseModal = ({
@@ -13,8 +12,7 @@ export const RemoveDestinationDatabaseModal = ({
}: {
params: { databaseId: string; destinationDatabaseId: string };
}) => {
const dispatch = useDispatch();
const { push } = useNavigation();
const dbId = parseInt(params.databaseId, 10);
const destDbId = parseInt(params.destinationDatabaseId, 10);
@@ -22,7 +20,7 @@ export const RemoveDestinationDatabaseModal = ({
const [deleteDatabase] = useDeleteDatabaseMutation();
const handleCloseModal = () => {
dispatch(push(Urls.viewDatabase(dbId)));
push(Urls.viewDatabase(dbId));
};
const handleDelete = async () => {

View File

@@ -1,25 +1,19 @@
import { useEffect } from "react";
import { replace } from "react-router-redux";
import { useDispatch } from "metabase/lib/redux";
import { useNavigation } from "metabase/routing";
import * as Urls from "metabase-enterprise/urls";
import type { Database } from "metabase-types/api";
export function useRedirectDestinationDatabase(
database: Pick<Database, "id" | "router_database_id"> | undefined,
) {
const dispatch = useDispatch();
const { replace } = useNavigation();
useEffect(() => {
if (database?.router_database_id) {
dispatch(
replace(
Urls.editDestinationDatabase(
database.router_database_id,
database.id,
),
),
replace(
Urls.editDestinationDatabase(database.router_database_id, database.id),
);
}
}, [dispatch, database?.router_database_id, database?.id]);
}, [database?.router_database_id, database?.id, replace]);
}

View File

@@ -1,13 +1,9 @@
import { IndexRoute, Route } from "react-router";
import { t } from "ttag";
import { PLUGIN_DB_ROUTING } from "metabase/plugins";
import { hasPremiumFeature } from "metabase-enterprise/settings";
import { DatabaseRoutingSection } from "./DatabaseRoutingSection";
import { DestinationDatabaseConnectionModal } from "./DestinationDatabaseConnectionModal";
import { DestinationDatabasesModal } from "./DestinationDatabasesModal";
import { RemoveDestinationDatabaseModal } from "./RemoveDestinationDatabaseModal";
import { useRedirectDestinationDatabase } from "./hooks";
/**
@@ -36,19 +32,6 @@ export function initializePlugin() {
PLUGIN_DB_ROUTING.useRedirectDestinationDatabase =
useRedirectDestinationDatabase;
PLUGIN_DB_ROUTING.getDestinationDatabaseRoutes = (IsAdmin: any) => (
<Route path="destination-databases">
<IndexRoute component={DestinationDatabasesModal} />
<Route component={IsAdmin}>
<Route path="create" component={DestinationDatabaseConnectionModal} />
</Route>
<Route path=":destinationDatabaseId">
<IndexRoute component={DestinationDatabaseConnectionModal} />
<Route component={IsAdmin}>
<Route path="remove" component={RemoveDestinationDatabaseModal} />
</Route>
</Route>
</Route>
);
PLUGIN_DB_ROUTING.getDestinationDatabaseRoutes = (_IsAdmin: any) => null;
}
}

View File

@@ -1,7 +1,7 @@
import { Fragment } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import { Link } from "metabase/common/components/Link";
import CS from "metabase/css/core/index.css";
import {
Form,

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { getIcon, renderWithProviders, screen } from "__support__/ui";
import { Route, getIcon, renderWithProviders, screen } from "__support__/ui";
import type { IconName } from "metabase/ui";
import registerVisualizations from "metabase/visualizations/register";
import type {

View File

@@ -1,7 +1,5 @@
import { Route } from "react-router";
import { setupListBrokenGraphNodesEndpoint } from "__support__/server-mocks";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type { DependencyNode } from "metabase-types/api";
import {
createMockAnalysisFindingError,

View File

@@ -1,8 +1,8 @@
import type { ReactNode } from "react";
import { Link } from "react-router";
import { t } from "ttag";
import { DateTime } from "metabase/common/components/DateTime";
import { Link } from "metabase/common/components/Link";
import CS from "metabase/css/core/index.css";
import * as Urls from "metabase/lib/urls";
import { getUserName } from "metabase/lib/user";

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type { DependencyNode } from "metabase-types/api";
import {
createMockCardDependencyNode,

View File

@@ -1,6 +1,6 @@
import { Link } from "react-router";
import { t } from "ttag";
import { Link } from "metabase/common/components/Link";
import CS from "metabase/css/core/index.css";
import { Anchor, Breadcrumbs, FixedSizeIcon, Group } from "metabase/ui";
import type { DependencyNode } from "metabase-types/api";

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type { DependencyNode } from "metabase-types/api";
import {
createMockCardDependencyNode,

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import type { DependencyNode } from "metabase-types/api";
import {
createMockCardDependencyNode,

View File

@@ -1,7 +1,7 @@
import cx from "classnames";
import { Fragment } from "react";
import { Link } from "react-router";
import { Link } from "metabase/common/components/Link";
import CS from "metabase/css/core/index.css";
import { Box, Group, type GroupProps } from "metabase/ui";

View File

@@ -1,9 +1,8 @@
import { useDisclosure } from "@mantine/hooks";
import { useState } from "react";
import { push } from "react-router-redux";
import { trackDependencyEntitySelected } from "metabase/data-studio/analytics";
import { useDispatch } from "metabase/lib/redux";
import { useNavigation } from "metabase/routing";
import { Card } from "metabase/ui";
import type {
DependencyEntry,
@@ -27,14 +26,14 @@ export function GraphEntryInput({
isGraphFetching,
getGraphUrl,
}: GraphEntryInputProps) {
const { push } = useNavigation();
const [searchModels, setSearchModels] =
useState<SearchModel[]>(SEARCH_MODELS);
const dispatch = useDispatch();
const [isPickerOpened, { open: openPicker, close: closePicker }] =
useDisclosure();
const handleEntryChange = (newEntry: DependencyEntry | undefined) => {
dispatch(push(getGraphUrl(newEntry)));
push(getGraphUrl(newEntry));
if (newEntry) {
trackDependencyEntitySelected({

View File

@@ -1,6 +1,4 @@
import { Route } from "react-router";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import * as Urls from "metabase/lib/urls";
import type { DependencyEntry, DependencyNode } from "metabase-types/api";
import {

View File

@@ -1,6 +1,6 @@
import cx from "classnames";
import { Link } from "react-router";
import { Link } from "metabase/common/components/Link";
import CS from "metabase/css/core/index.css";
import { Box, FixedSizeIcon, Group, type IconName } from "metabase/ui";

View File

@@ -10,7 +10,9 @@ import { useCheckTransformDependencies } from "./hooks/use-check-transform-depen
import { useGetDependenciesCount } from "./hooks/use-get-dependencies-count";
import { DependencyGraphPage } from "./pages/DependencyGraphPage";
import {
getDataStudioDependencyDiagnosticsRouteObjects,
getDataStudioDependencyDiagnosticsRoutes,
getDataStudioDependencyRouteObjects,
getDataStudioDependencyRoutes,
} from "./routes";
@@ -22,8 +24,12 @@ export function initializePlugin() {
PLUGIN_DEPENDENCIES.isEnabled = true;
PLUGIN_DEPENDENCIES.getDataStudioDependencyRoutes =
getDataStudioDependencyRoutes;
PLUGIN_DEPENDENCIES.getDataStudioDependencyRouteObjects =
getDataStudioDependencyRouteObjects;
PLUGIN_DEPENDENCIES.getDataStudioDependencyDiagnosticsRoutes =
getDataStudioDependencyDiagnosticsRoutes;
PLUGIN_DEPENDENCIES.getDataStudioDependencyDiagnosticsRouteObjects =
getDataStudioDependencyDiagnosticsRouteObjects;
PLUGIN_DEPENDENCIES.DependencyGraphPage = DependencyGraphPage;
PLUGIN_DEPENDENCIES.CheckDependenciesForm = CheckDependenciesForm;
PLUGIN_DEPENDENCIES.CheckDependenciesModal = CheckDependenciesModal;

View File

@@ -1,10 +1,8 @@
import type { Location } from "history";
import { useEffect, useMemo, useRef } from "react";
import { replace } from "react-router-redux";
import { useUserKeyValue } from "metabase/common/hooks/use-user-key-value";
import { useDispatch } from "metabase/lib/redux";
import type * as Urls from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { DependencyDiagnostics } from "../../components/DependencyDiagnostics";
import type {
@@ -21,7 +19,7 @@ import {
} from "./utils";
type DependencyDiagnosticsPageProps = {
location: Location;
location: { search: string };
};
type DependencyDiagnosticsPageOwnProps = DependencyDiagnosticsPageProps & {
@@ -32,8 +30,8 @@ function DependencyDiagnosticsPage({
mode,
location,
}: DependencyDiagnosticsPageOwnProps) {
const { replace } = useNavigation();
const isInitializingRef = useRef(false);
const dispatch = useDispatch();
const {
value: rawLastUsedParams,
@@ -57,15 +55,15 @@ function DependencyDiagnosticsPage({
if (withSetLastUsedParams) {
setLastUsedParams(getUserParams(params));
}
dispatch(replace(getPageUrl(mode, params)));
replace(getPageUrl(mode, params));
};
useEffect(() => {
if (!isInitializingRef.current && !isLoadingParams) {
isInitializingRef.current = true;
dispatch(replace(getPageUrl(mode, params)));
replace(getPageUrl(mode, params));
}
}, [mode, params, isLoadingParams, dispatch]);
}, [mode, params, isLoadingParams, replace]);
return (
<DependencyDiagnostics

View File

@@ -1,5 +1,4 @@
import userEvent from "@testing-library/user-event";
import { Route } from "react-router";
import {
setupListBreakingGraphNodesEndpoint,
@@ -7,6 +6,7 @@ import {
setupUserKeyValueEndpoints,
} from "__support__/server-mocks";
import {
Route,
mockGetBoundingClientRect,
renderWithProviders,
screen,

View File

@@ -1,5 +1,3 @@
import type { Location } from "history";
import * as Urls from "metabase/lib/urls";
import {
DEPENDENCY_GROUP_TYPES,
@@ -10,6 +8,10 @@ import {
import type { DependencyDiagnosticsMode } from "../../components/DependencyDiagnostics/types";
type SearchLocation = {
search: string;
};
export function getPageUrl(
mode: DependencyDiagnosticsMode,
params: Urls.DependencyDiagnosticsParams,
@@ -20,8 +22,9 @@ export function getPageUrl(
}
export function parseUrlParams(
location: Location,
location: SearchLocation,
): Urls.DependencyDiagnosticsParams {
const queryParams = Object.fromEntries(new URLSearchParams(location.search));
const {
page,
query,
@@ -29,7 +32,7 @@ export function parseUrlParams(
"include-personal-collections": includePersonalCollections,
"sort-column": sortColumn,
"sort-direction": sortDirection,
} = location.query;
} = queryParams;
return {
page: Urls.parseNumberParam(page),
@@ -72,6 +75,6 @@ export function getUserParams(
};
}
export function isEmptyParams(location: Location): boolean {
return Object.values(location.query).every((value) => value == null);
export function isEmptyParams(location: SearchLocation): boolean {
return new URLSearchParams(location.search).toString().length === 0;
}

View File

@@ -1,10 +1,8 @@
import { Route } from "react-router";
import {
setupDependecyGraphEndpoint,
setupRecentViewsAndSelectionsEndpoints,
} from "__support__/server-mocks";
import { renderWithProviders, screen } from "__support__/ui";
import { Route, renderWithProviders, screen } from "__support__/ui";
import { PLUGIN_DEPENDENCIES } from "metabase/plugins";
import { createMockDependencyGraph } from "metabase-types/api/mocks";

View File

@@ -1,4 +1,5 @@
import { IndexRedirect, IndexRoute, Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import { Navigate, useLocation } from "react-router-dom";
import {
BrokenDependencyDiagnosticsPage,
@@ -6,19 +7,35 @@ import {
} from "./pages/DependencyDiagnosticsPage";
import { DependencyGraphPage } from "./pages/DependencyGraphPage";
const BrokenDependencyListPageWithRouteProps = () => {
const location = useLocation();
return <BrokenDependencyDiagnosticsPage location={location} />;
};
const UnreferencedDependencyListPageWithRouteProps = () => {
const location = useLocation();
return <UnreferencedDependencyDiagnosticsPage location={location} />;
};
export function getDataStudioDependencyRoutes() {
return <IndexRoute component={DependencyGraphPage} />;
return null;
}
export function getDataStudioDependencyDiagnosticsRoutes() {
return (
<>
<IndexRedirect to="broken" />
<Route path="broken" component={BrokenDependencyDiagnosticsPage} />
<Route
path="unreferenced"
component={UnreferencedDependencyDiagnosticsPage}
/>
</>
);
return null;
}
export function getDataStudioDependencyRouteObjects(): RouteObject[] {
return [{ index: true, element: <DependencyGraphPage /> }];
}
export function getDataStudioDependencyDiagnosticsRouteObjects(): RouteObject[] {
return [
{ index: true, element: <Navigate to="broken" replace /> },
{ path: "broken", element: <BrokenDependencyListPageWithRouteProps /> },
{
path: "unreferenced",
element: <UnreferencedDependencyListPageWithRouteProps />,
},
];
}

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { Messages } from "metabase/admin/permissions/constants/messages";
@@ -23,6 +22,7 @@ import {
getTablesPermission,
} from "metabase/admin/permissions/utils/graph";
import { getGroupFocusPermissionsUrl } from "metabase/admin/permissions/utils/urls";
import { pushPath } from "metabase/lib/navigation";
import type { Group, GroupsPermissions } from "metabase-types/api";
export const DATA_MODEL_PERMISSION_OPTIONS = {
@@ -161,8 +161,8 @@ export const buildDataModelPermission = (
],
postActions: hasChildEntities
? {
controlled: () =>
push(getGroupFocusPermissionsUrl(groupId, entityId)),
controlled: () => () =>
pushPath(getGroupFocusPermissionsUrl(groupId, entityId)),
}
: undefined,
};

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { Messages } from "metabase/admin/permissions/constants/messages";
@@ -22,6 +21,7 @@ import {
getTablesPermission,
} from "metabase/admin/permissions/utils/graph";
import { getGroupFocusPermissionsUrl } from "metabase/admin/permissions/utils/urls";
import { pushPath } from "metabase/lib/navigation";
import type { Group, GroupsPermissions } from "metabase-types/api";
const getTooltipMessage = (isAdmin: boolean, isBlockedAccess: boolean) => {
@@ -170,8 +170,8 @@ export const buildDownloadPermission = (
],
postActions: hasChildEntities
? {
controlled: () =>
push(getGroupFocusPermissionsUrl(groupId, entityId)),
controlled: () => () =>
pushPath(getGroupFocusPermissionsUrl(groupId, entityId)),
}
: undefined,
};

View File

@@ -1,11 +1,11 @@
import type { ReactNode } from "react";
import { Link } from "react-router";
import { jt, t } from "ttag";
import type {
PermissionSubject,
SpecialGroupType,
} from "metabase/admin/permissions/types";
import { Link } from "metabase/common/components/Link";
import { getUser } from "metabase/selectors/user";
import type { User } from "metabase-types/api";
import type { AdminPathKey, State } from "metabase-types/store";

View File

@@ -1,7 +1,6 @@
import { push } from "react-router-redux";
import { getAdminPaths } from "metabase/admin/app/selectors";
import { permissionApi } from "metabase/api";
import { pushPath } from "metabase/lib/navigation";
import { createThunkAction } from "metabase/lib/redux";
import {
@@ -27,7 +26,7 @@ export const confirmDeleteMembership = createThunkAction(
: getRevokeManagerGroupsRedirect(currentUserMemberships, adminPaths);
if (redirectUrl) {
await dispatch(push(redirectUrl));
pushPath(redirectUrl);
}
},
);
@@ -49,7 +48,7 @@ export const confirmUpdateMembership = createThunkAction(
: getRevokeManagerGroupsRedirect(currentUserMemberships, adminPaths);
if (redirectUrl) {
await dispatch(push(redirectUrl));
pushPath(redirectUrl);
}
},
);
@@ -67,7 +66,7 @@ export const deleteGroup = createThunkAction(
if (isLastGroup) {
const adminPaths = getAdminPaths(getState());
const redirectUrl = getRevokedAllGroupManagersPath(adminPaths);
await dispatch(push(redirectUrl));
pushPath(redirectUrl);
}
},
);

View File

@@ -43,7 +43,6 @@ const BillingInfoValue = ({
return (
<BillingInternalLink
to={internalLinkMap[lineItem.link]}
href={internalLinkMap[lineItem.link]}
data-testid="test-link"
{...props}
>

View File

@@ -1,7 +1,5 @@
import { useDisclosure } from "@mantine/hooks";
import { useEffect, useMemo } from "react";
import { IndexRoute, Route } from "react-router";
import { push } from "react-router-redux";
import { P, match } from "ts-pattern";
import { c, jt, t } from "ttag";
import _ from "underscore";
@@ -20,7 +18,7 @@ import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErr
import { CollectionPickerModal } from "metabase/common/components/Pickers/CollectionPicker";
import { useToast } from "metabase/common/hooks";
import { getIcon } from "metabase/lib/icon";
import { useDispatch } from "metabase/lib/redux";
import { useNavigation } from "metabase/routing";
import {
Box,
Button,
@@ -50,10 +48,7 @@ import { MetabotPromptSuggestionPane } from "./MetabotAdminSuggestedPrompts";
import { useMetabotIdPath } from "./utils";
export function getAdminRoutes() {
return [
<IndexRoute key="index" component={MetabotAdminPage} />,
<Route key="route" path=":metabotId" component={MetabotAdminPage} />,
];
return [];
}
export function MetabotAdminPage() {
@@ -118,9 +113,9 @@ export function MetabotAdminPage() {
}
function MetabotNavPane() {
const { push } = useNavigation();
const { data, isLoading } = useListMetabotsQuery();
const metabotId = useMetabotIdPath();
const dispatch = useDispatch();
const metabots = useMemo(() => _.sortBy(data?.items ?? [], "id"), [data]);
@@ -128,9 +123,9 @@ function MetabotNavPane() {
const hasMetabotId = metabots?.some((metabot) => metabot.id === metabotId);
if (!hasMetabotId && metabots?.length) {
dispatch(push(`/admin/metabot/${metabots[0]?.id}`));
push(`/admin/metabot/${metabots[0]?.id}`);
}
}, [metabots, metabotId, dispatch]);
}, [metabots, metabotId, push]);
if (isLoading || !data) {
return null;

View File

@@ -1,6 +1,5 @@
import userEvent from "@testing-library/user-event";
import fetchMock from "fetch-mock";
import { Route } from "react-router";
import {
findRequests,
@@ -14,6 +13,7 @@ import {
setupMetabotsEndpoints,
} from "__support__/server-mocks/metabot";
import {
Route,
mockGetBoundingClientRect,
renderWithProviders,
screen,

View File

@@ -1,5 +1,4 @@
import _userEvent from "@testing-library/user-event";
import { Route } from "react-router";
import {
setupMetabotPromptSuggestionsEndpoint,
@@ -7,7 +6,7 @@ import {
setupRegenerateMetabotPromptSuggestionsEndpoint,
setupRemoveMetabotPromptSuggestionEndpoint,
} from "__support__/server-mocks/metabot";
import { renderWithProviders, screen, waitFor } from "__support__/ui";
import { Route, renderWithProviders, screen, waitFor } from "__support__/ui";
import { FIXED_METABOT_IDS } from "metabase-enterprise/metabot/constants";
import type { SuggestedMetabotPrompt } from "metabase-types/api";

View File

@@ -1,5 +1,4 @@
import type { ReactElement } from "react";
import { IndexRoute, Redirect } from "react-router";
import { t } from "ttag";
import { SettingsPageWrapper } from "metabase/admin/components/SettingsSection";
@@ -12,10 +11,7 @@ import { MetabotPurchasePageForNonStoreUser } from "./MetabotPurchasePageForNonS
import { MetabotPurchasePageForStoreUser } from "./MetabotPurchasePageForStoreUser";
export function getAdminRoutes() {
return [
<IndexRoute key="index" component={MetabotPurchasePage} />,
<Redirect key="redirect" from="*" to="/admin/metabot" />,
];
return [];
}
export function MetabotPurchasePage(): ReactElement {

View File

@@ -1,9 +1,7 @@
import { unifiedMergeView } from "@codemirror/merge";
import { useDisclosure } from "@mantine/hooks";
import type { UnknownAction } from "@reduxjs/toolkit";
import cx from "classnames";
import { useMemo, useState } from "react";
import { push } from "react-router-redux";
import { useLocation, useMount } from "react-use";
import { P, match } from "ts-pattern";
import { t } from "ttag";
@@ -15,6 +13,7 @@ import { useDispatch, useSelector } from "metabase/lib/redux";
import * as Urls from "metabase/lib/urls";
import { useMetadataToasts } from "metabase/metadata/hooks";
import EditorS from "metabase/query_builder/components/NativeQueryEditor/CodeMirrorEditor/CodeMirrorEditor.module.css";
import { useNavigation } from "metabase/routing";
import { getMetadata } from "metabase/selectors/metadata";
import { getIsWorkspace } from "metabase/selectors/routing";
import {
@@ -107,6 +106,7 @@ export const AgentSuggestionMessage = ({
}: {
message: MetabotAgentEditSuggestionChatMessage;
}) => {
const { push } = useNavigation();
const dispatch = useDispatch();
const metadata = useSelector(getMetadata);
const isWorkspace = useSelector(getIsWorkspace);
@@ -177,7 +177,7 @@ export const AgentSuggestionMessage = ({
return;
}
dispatch(push(getTransformUrl(suggestedTransform)) as UnknownAction);
push(getTransformUrl(suggestedTransform));
};
return (

View File

@@ -1,15 +1,15 @@
import { isFulfilled, isRejected } from "@reduxjs/toolkit";
import cx from "classnames";
import { useEffect, useState } from "react";
import { push } from "react-router-redux";
import { useLocation } from "react-router-dom";
import { usePrevious } from "react-use";
import { isMatching } from "ts-pattern";
import { t } from "ttag";
import _ from "underscore";
import { MetabotLogo } from "metabase/common/components/MetabotLogo";
import { useDispatch } from "metabase/lib/redux";
import { MetabotPromptInput } from "metabase/metabot/components/MetabotPromptInput";
import { useRouter } from "metabase/router";
import { useNavigation } from "metabase/routing";
import {
Box,
Button,
@@ -54,7 +54,7 @@ const responseHasNavigateTo = (action: SubmitInputResult) =>
);
export const MetabotQueryBuilder = () => {
const dispatch = useDispatch();
const { push } = useNavigation();
const {
setVisible,
setProfileOverride,
@@ -109,14 +109,12 @@ export const MetabotQueryBuilder = () => {
// to an empty notebook query and show the chat sidebar as it's
// highly likely the chat contains a response asking for clarification
if (!responseHasNavigateTo(action)) {
dispatch(
push(
Urls.newQuestion({
mode: "notebook",
creationType: "custom_question",
cardType: "question",
}),
),
push(
Urls.newQuestion({
mode: "notebook",
creationType: "custom_question",
cardType: "question",
}),
);
setVisible(true);
}
@@ -133,29 +131,28 @@ export const MetabotQueryBuilder = () => {
[setVisible],
);
const { router, routes } = useRouter();
const currentRoute = routes.at(-1);
const location = useLocation();
const previousPathname = usePrevious(location.pathname);
useEffect(
function cancelRequestOnRouteLeave() {
return router.setRouteLeaveHook(currentRoute, (nextLocation) => {
const isNavigatingToQuestion =
nextLocation?.pathname.startsWith("/question");
if (isDoingScience) {
if (isNavigatingToQuestion) {
// we want to open the sidebar at this point as the agent could be sending a
// navigate_to before the response has been fully completed
setVisible(true);
} else {
cancelRequest();
resetConversation(); // clear any partial response and reset profile
}
}
return true;
});
function handleRouteChangeWhileRequesting() {
if (!isDoingScience || previousPathname === location.pathname) {
return;
}
const isNavigatingToQuestion = location.pathname.startsWith("/question");
if (isNavigatingToQuestion) {
// We want to open the sidebar at this point as the agent could be
// sending a navigate_to before the response has been fully completed.
setVisible(true);
} else {
cancelRequest();
resetConversation(); // clear any partial response and reset profile
}
},
[
router,
currentRoute,
location.pathname,
previousPathname,
setVisible,
cancelRequest,
resetConversation,

View File

@@ -1,30 +1,34 @@
import { Route } from "react-router";
import { replace } from "react-router-redux";
import type { RouteObject } from "react-router-dom";
import { useSearchParams } from "react-router-dom";
import { useMount } from "react-use";
import { useDispatch } from "metabase/lib/redux";
import { useNavigation } from "metabase/routing";
import { useMetabotAgent } from "metabase-enterprise/metabot/hooks";
export const getMetabotQuickLinks = () => {
return (
<Route
key="metabot"
path="metabot/new"
component={(props) => {
const { submitInput } = useMetabotAgent("omnibot");
const prompt = String(props.location.query?.q ?? "");
const dispatch = useDispatch();
const MetabotQuickLinkRedirect = () => {
const { replace } = useNavigation();
const [searchParams] = useSearchParams();
const { submitInput } = useMetabotAgent("omnibot");
const prompt = String(searchParams.get("q") ?? "");
useMount(() => {
dispatch(replace("/"));
useMount(() => {
replace("/");
if (prompt) {
submitInput(prompt, { focusInput: true });
}
});
if (prompt) {
submitInput(prompt, { focusInput: true });
}
});
return null;
}}
/>
);
return null;
};
export const getMetabotQuickLinks = () => {
return null;
};
export const getMetabotQuickLinkRouteObjects = (): RouteObject[] => [
{
path: "metabot/new",
element: <MetabotQuickLinkRedirect />,
},
];

View File

@@ -1,4 +1,4 @@
import { Route } from "react-router";
import type { RouteObject } from "react-router-dom";
import type { MetabotContext as MetabotContextType } from "metabase/metabot";
import { PLUGIN_METABOT, PLUGIN_REDUCERS } from "metabase/plugins";
@@ -14,7 +14,7 @@ import MetabotThinkingStyles from "./components/MetabotChat/MetabotThinking.modu
import { MetabotDataStudioButton } from "./components/MetabotDataStudioButton";
import { MetabotDataStudioSidebar } from "./components/MetabotDataStudioSidebar";
import { MetabotQueryBuilder } from "./components/MetabotQueryBuilder";
import { getMetabotQuickLinks } from "./components/MetabotQuickLinks";
import { getMetabotQuickLinkRouteObjects } from "./components/MetabotQuickLinks";
import { getNewMenuItemAIExploration } from "./components/NewMenuItemAIExploration";
import { MetabotContext, MetabotProvider, defaultContext } from "./context";
import { useMetabotSQLSuggestion as useMetabotSQLSuggestionEE } from "./hooks";
@@ -51,10 +51,16 @@ export function initializePlugin() {
getMetabotVisible,
// routes
getAdminRoutes,
getMetabotRoutes: getMetabotQuickLinks,
getMetabotQueryBuilderRoute: () => (
<Route path="ask" component={MetabotQueryBuilder} />
),
getMetabotRoutes: () => null,
getMetabotRouteObjects: getMetabotQuickLinkRouteObjects,
getMetabotQueryBuilderRoute: () => null,
getMetabotQueryBuilderRouteObjects: () =>
[
{
path: "ask",
element: <MetabotQueryBuilder />,
},
] as RouteObject[],
// components
Metabot,
MetabotChat,

View File

@@ -1,14 +1,13 @@
import {
type UnknownAction,
createAsyncThunk as createAsyncThunkOriginal,
isRejected,
nanoid,
} from "@reduxjs/toolkit";
import { push } from "react-router-redux";
import { P, match } from "ts-pattern";
import _ from "underscore";
import { isEmbeddingSdk } from "metabase/embedding-sdk/config";
import { pushPath } from "metabase/lib/navigation";
import { addUndo } from "metabase/redux/undo";
import { getIsWorkspace } from "metabase/selectors/routing";
import { getUser } from "metabase/selectors/user";
@@ -352,7 +351,7 @@ export const sendAgentRequest = createAsyncThunk<
dispatch(setNavigateToPath(part.value));
if (!isEmbeddingSdk() && !isWorkspace) {
dispatch(push(part.value) as UnknownAction);
pushPath(part.value);
}
})
.with({ type: "transform_suggestion" }, ({ value }) => {

View File

@@ -1,8 +1,7 @@
import fetchMock from "fetch-mock";
import { Route } from "react-router";
import { mockSettings } from "__support__/settings";
import { renderWithProviders, screen, waitFor } from "__support__/ui";
import { Route, renderWithProviders, screen, waitFor } from "__support__/ui";
import type { Collection } from "metabase-types/api";
import { createMockCollection } from "metabase-types/api/mocks";
import { createMockState } from "metabase-types/store/mocks";

View File

@@ -32,7 +32,7 @@ interface TestState {
}
const createTestStore = (settingsOverrides: Record<string, unknown> = {}) => {
return configureStore({
const store = configureStore({
reducer: combineReducers({
remoteSyncPlugin: remoteSyncReducer,
settings: settingsReducer,
@@ -59,6 +59,7 @@ const createTestStore = (settingsOverrides: Record<string, unknown> = {}) => {
// and the test store's State type (test store uses simplified State)
.concat(remoteSyncListenerMiddleware.middleware as any),
});
return store as typeof store & { dispatch: (action: any) => any };
};
const waitForCondition = async (

View File

@@ -1,13 +1,12 @@
import type { Location } from "history";
import { useEffect } from "react";
import { withRouter } from "react-router";
import { push } from "react-router-redux";
import _ from "underscore";
import { useListUserAttributesQuery } from "metabase/api";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { getParentPath } from "metabase/hoc/ModalRoute";
import { connect } from "metabase/lib/redux";
import { useRouter } from "metabase/router";
import { useNavigation } from "metabase/routing";
import {
getGroupTableAccessPolicy,
getPolicyRequestState,
@@ -29,8 +28,7 @@ import type { GroupTableAccessPolicyParams, SandboxesState } from "../types";
interface EditSandboxingModalContainerProps {
policy: GroupTableAccessPolicy;
attributes: UserAttributeKey[];
push: (path: string) => void;
attributes: UserAttributeKey[] | null;
params: GroupTableAccessPolicyParams;
location: Location;
route: any;
@@ -43,9 +41,13 @@ interface EditSandboxingModalContainerProps {
) => void;
}
type EditSandboxingModalInnerProps = EditSandboxingModalContainerProps & {
route: any;
location: Location;
};
const EditSandboxingModalContainer = ({
policy,
push,
params,
location,
route,
@@ -54,7 +56,9 @@ const EditSandboxingModalContainer = ({
policyRequestState,
updatePolicy,
updateTableSandboxingPermission,
}: EditSandboxingModalContainerProps) => {
}: EditSandboxingModalInnerProps) => {
const { push } = useNavigation();
useEffect(() => {
fetchPolicy(params);
fetchUserAttributes();
@@ -103,15 +107,29 @@ const mapStateToProps = (
});
const mapDispatchToProps = {
push,
fetchPolicy,
updatePolicy,
fetchUserAttributes,
updateTableSandboxingPermission,
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default _.compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps),
const ConnectedEditSandboxingModalContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(EditSandboxingModalContainer);
const EditSandboxingModalContainerWithRouter = (props: any) => {
const { routes, location } = useRouter();
const route = routes[routes.length - 1];
return (
<ConnectedEditSandboxingModalContainer
{...props}
route={route}
location={location}
/>
);
};
// eslint-disable-next-line import/no-default-export -- deprecated usage
export default EditSandboxingModalContainerWithRouter;

View File

@@ -1,4 +1,3 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import _ from "underscore";
@@ -8,6 +7,7 @@ import {
getGroupFocusPermissionsUrl,
} from "metabase/admin/permissions/utils/urls";
import { ModalRoute } from "metabase/hoc/ModalRoute";
import { pushPath } from "metabase/lib/navigation";
import {
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_ACTIONS,
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_CONFIRMATIONS,
@@ -53,8 +53,8 @@ const getEditSegementedAccessUrl = (entityId, groupId, view) =>
? getDatabaseViewSandboxModalUrl(entityId, groupId)
: getGroupViewSandboxModalUrl(entityId, groupId);
const getEditSegmentedAccessPostAction = (entityId, groupId, view) =>
push(getEditSegementedAccessUrl(entityId, groupId, view));
const getEditSegmentedAccessPostAction = (entityId, groupId, view) => () =>
pushPath(getEditSegementedAccessUrl(entityId, groupId, view));
/**
* Initialize sandboxes plugin features that depend on hasPremiumFeature.
@@ -82,8 +82,8 @@ export function initializePlugin() {
label: t`Edit row and column security`,
iconColor: "brand",
icon: "pencil",
actionCreator: (entityId, groupId, view) =>
push(getEditSegementedAccessUrl(entityId, groupId, view)),
actionCreator: (entityId, groupId, view) => () =>
pushPath(getEditSegementedAccessUrl(entityId, groupId, view)),
});
PLUGIN_ADMIN_PERMISSIONS_TABLE_FIELDS_CONFIRMATIONS.push(
(permissions, groupId, entityId, newValue) =>

View File

@@ -1,11 +1,10 @@
import { push } from "react-router-redux";
import { t } from "ttag";
import { SettingsSection } from "metabase/admin/components/SettingsSection";
import { LoadingAndErrorWrapper } from "metabase/common/components/LoadingAndErrorWrapper";
import { PaginationControls } from "metabase/common/components/PaginationControls";
import { useDispatch } from "metabase/lib/redux";
import { adminToolsGrantAccess } from "metabase/lib/urls";
import { useNavigation } from "metabase/routing";
import { Box, Button, Flex, LoadingOverlay, Text, Title } from "metabase/ui";
import { useAccessGrantsQuery } from "../../hooks/useAccessGrantsQuery";
@@ -13,7 +12,7 @@ import { useAccessGrantsQuery } from "../../hooks/useAccessGrantsQuery";
import { AccessGrantList } from "./AccessGrantList";
export function SupportSettingsSection() {
const dispatch = useDispatch();
const { push } = useNavigation();
const {
accessGrants,
accessGrantsError,
@@ -50,7 +49,7 @@ export function SupportSettingsSection() {
<Box pb="lg">
<Button
disabled={!!currentAccessGrant}
onClick={() => dispatch(push(adminToolsGrantAccess()))}
onClick={() => push(adminToolsGrantAccess())}
variant="filled"
title={
currentAccessGrant

View File

@@ -1,13 +1,18 @@
import dayjs from "dayjs";
import fetchMock from "fetch-mock";
import { Route } from "react-router";
import {
setupCurrentAccessGrantEndpoint,
setupListAccessGrantsEndpoint,
setupListAccessGrantsEndpointWithError,
} from "__support__/server-mocks";
import { fireEvent, renderWithProviders, screen, within } from "__support__/ui";
import {
Route,
fireEvent,
renderWithProviders,
screen,
within,
} from "__support__/ui";
import { createMockAccessGrant } from "metabase-types/api/mocks";
import { GrantAccessModal } from "./GrantAccessModal";

View File

@@ -2,7 +2,7 @@ import { PLUGIN_TABLE_EDITING } from "metabase/plugins";
import { hasPremiumFeature } from "metabase-enterprise/settings";
import { AdminDatabaseTableEditingSection } from "./admin/AdminDatabaseTableEditingSection";
import { getRoutes } from "./routes";
import { getRouteObjects, getRoutes } from "./routes";
import { isDatabaseTableEditingEnabled } from "./settings";
import { getTableEditUrl } from "./urls";
@@ -15,6 +15,7 @@ export function initializePlugin() {
PLUGIN_TABLE_EDITING.isDatabaseTableEditingEnabled =
isDatabaseTableEditingEnabled;
PLUGIN_TABLE_EDITING.getRoutes = getRoutes;
PLUGIN_TABLE_EDITING.getRouteObjects = getRouteObjects;
PLUGIN_TABLE_EDITING.getTableEditUrl = getTableEditUrl;
PLUGIN_TABLE_EDITING.AdminDatabaseTableEditingSection =
AdminDatabaseTableEditingSection;

View File

@@ -1,12 +1,38 @@
import { Route } from "react-router";
import type { Location } from "history";
import type { RouteObject } from "react-router-dom";
import { useLocation, useParams } from "react-router-dom";
import { EditTableDataContainer } from "./table-edit/EditTableDataContainer";
export function getRoutes() {
const EditTableDataContainerWithRouteProps = () => {
const location = useLocation();
const params = useParams<{
dbId?: string;
tableId?: string;
objectId?: string;
}>();
return (
<Route
path="databases/:dbId/tables/:tableId/edit(/:objectId)"
component={EditTableDataContainer}
<EditTableDataContainer
location={location as unknown as Location}
params={{
dbId: params.dbId ?? "",
tableId: params.tableId ?? "",
objectId: params.objectId,
}}
/>
);
};
export function getRoutes() {
return null;
}
export function getRouteObjects(): RouteObject[] {
return [
{
path: "databases/:dbId/tables/:tableId/edit/:objectId?",
element: <EditTableDataContainerWithRouteProps />,
},
];
}

View File

@@ -44,7 +44,7 @@ type EditTableDataContainerProps = {
tableId: string;
objectId?: string;
};
location: Location<{ query?: string }>;
location: Location;
};
export const EditTableDataContainer = ({

View File

@@ -1,8 +1,8 @@
import { useState } from "react";
import { Link } from "react-router";
import { jt, t } from "ttag";
import { useGetTableQuery } from "metabase/api";
import { Link } from "metabase/common/components/Link";
import * as Urls from "metabase/lib/urls";
import { Anchor, Box, Button, Group, Icon, Stack, Text } from "metabase/ui";

View File

@@ -1,10 +1,10 @@
import type { Location } from "history";
import { useCallback, useEffect, useMemo } from "react";
import { push } from "react-router-redux";
import { b64url_to_utf8, utf8_to_b64url } from "metabase/lib/encoding";
import { useDispatch, useSelector } from "metabase/lib/redux";
import { loadMetadataForTable } from "metabase/questions/actions";
import { useNavigation } from "metabase/routing";
import { getMetadata } from "metabase/selectors/metadata";
import * as Lib from "metabase-lib";
import Question from "metabase-lib/v1/Question";
@@ -13,7 +13,7 @@ import type { OpaqueDatasetQuery } from "metabase-types/api";
type UseAdHocTableQueryProps = {
tableId: number;
databaseId: number;
location: Location<{ query?: string }>;
location: Location;
};
export const useAdHocTableQuery = ({
@@ -21,16 +21,14 @@ export const useAdHocTableQuery = ({
databaseId,
location,
}: UseAdHocTableQueryProps) => {
const { push } = useNavigation();
const metadata = useSelector(getMetadata);
const dispatch = useDispatch();
const queryParam = useMemo(
() =>
location.query?.query
? deserializeQueryFromUrl(location.query.query)
: null,
[location.query.query],
);
const queryParam = useMemo(() => {
const queryFromSearch = new URLSearchParams(location.search).get("query");
return queryFromSearch ? deserializeQueryFromUrl(queryFromSearch) : null;
}, [location.search]);
const metadataProvider = useMemo(
() => Lib.metadataProvider(databaseId, metadata),
@@ -72,14 +70,12 @@ export const useAdHocTableQuery = ({
if (newFilters.length > 0 || newOrderBys.length > 0) {
const searchParams = new URLSearchParams();
searchParams.set("query", serializeQueryToUrl(Lib.toJsQuery(newQuery)));
dispatch(
push(`${window.location.pathname}?${searchParams.toString()}`),
);
push(`${window.location.pathname}?${searchParams.toString()}`);
} else {
dispatch(push(window.location.pathname));
push(window.location.pathname);
}
},
[dispatch],
[push],
);
const tableQuery = useMemo(() => {

Some files were not shown because too many files have changed in this diff Show More