Compare commits
12 Commits
python-sec
...
react-rout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1a95da338 | ||
|
|
8d5219dfab | ||
|
|
0bebf28273 | ||
|
|
05eaeb9614 | ||
|
|
b5da1e91ad | ||
|
|
a41cc6f2c1 | ||
|
|
21e82d4e1b | ||
|
|
7228610d15 | ||
|
|
31d313942f | ||
|
|
88d7f53f71 | ||
|
|
cfc800b74f | ||
|
|
8f0035422d |
38
bun.lock
38
bun.lock
@@ -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=="],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))}`;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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" />}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 /> },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -43,7 +43,6 @@ const BillingInfoValue = ({
|
||||
return (
|
||||
<BillingInternalLink
|
||||
to={internalLinkMap[lineItem.link]}
|
||||
href={internalLinkMap[lineItem.link]}
|
||||
data-testid="test-link"
|
||||
{...props}
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ type EditTableDataContainerProps = {
|
||||
tableId: string;
|
||||
objectId?: string;
|
||||
};
|
||||
location: Location<{ query?: string }>;
|
||||
location: Location;
|
||||
};
|
||||
|
||||
export const EditTableDataContainer = ({
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user