diff --git a/.vscode/settings.json b/.vscode/settings.json index 8258f29976c..a2eb678fc10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,7 +40,7 @@ "[typescriptreact]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[less]": { diff --git a/snuba/admin/clickhouse/database_clusters.py b/snuba/admin/clickhouse/database_clusters.py new file mode 100644 index 00000000000..d27b5a6fdff --- /dev/null +++ b/snuba/admin/clickhouse/database_clusters.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass +from typing import Sequence + +from snuba.admin.clickhouse.common import get_ro_node_connection +from snuba.admin.clickhouse.nodes import get_storage_info +from snuba.clusters.cluster import ClickhouseClientSettings + + +@dataclass(frozen=True) +class Node: + cluster: str + host_name: str + host_address: str + shard: int + replica: int + version: str + + +@dataclass(frozen=True) +class HostInfo: + host: str + port: int + storage_name: str + + +def get_node_info() -> Sequence[Node]: + node_info = [] + hosts = set() + for storage_info in get_storage_info(): + for node in storage_info["dist_nodes"]: + hosts.add( + HostInfo(node["host"], node["port"], storage_info["storage_name"]) + ) + + for node in storage_info["local_nodes"]: + hosts.add( + HostInfo(node["host"], node["port"], storage_info["storage_name"]) + ) + + for host_info in hosts: + connection = get_ro_node_connection( + host_info.host, + host_info.port, + host_info.storage_name, + ClickhouseClientSettings.QUERY, + ) + nodes = [ + Node(*result) + for result in connection.execute( + "SELECT cluster, host_name, host_address, shard_num, replica_num, version() FROM system.clusters WHERE is_local = 1;" + ).results + ] + node_info.extend(nodes) + + return node_info diff --git a/snuba/admin/package.json b/snuba/admin/package.json index 60573ae26a2..d5d75eb6d2d 100644 --- a/snuba/admin/package.json +++ b/snuba/admin/package.json @@ -11,8 +11,9 @@ "@codemirror/lang-sql": "^6.7.0", "@codemirror/state": "^6.4.1", "@codemirror/view": "^6.32.0", - "@mantine/core": "^6.0.15", - "@mantine/hooks": "^6.0.15", + "@mantine/core": "6.0.21", + "@mantine/dates": "6.0.21", + "@mantine/hooks": "6.0.21", "@mantine/prism": "^6.0.15", "@mantine/tiptap": "^6.0.15", "@sentry/react": "^7.88.0", @@ -25,8 +26,10 @@ "@tiptap/starter-kit": "^2.0.3", "@types/react": "^18.0.20", "@types/react-dom": "^18.2.6", + "dayjs": "^1.11.13", "jest-dom": "^4.0.0", "lowlight": "^2.9.0", + "mantine-react-table": "^1.3.4", "react": "^18.2.0", "react-dom": "^18.2.0", "react-select-event": "^5.5.1", @@ -34,13 +37,13 @@ "typescript": "^4.8.3" }, "devDependencies": { - "@emotion/react": "^11.11.1", + "@emotion/react": "^11.13.3", "@jest/globals": "^29.4.3", "@mantine/core": "^6.0.15", "@mantine/hooks": "^6.0.15", "@mantine/prism": "^6.0.15", "@mantine/tiptap": "^6.0.15", - "@tabler/icons-react": "^2.23.0", + "@tabler/icons-react": "^3.17.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.4.3", "@tiptap/extension-link": "^2.0.3", diff --git a/snuba/admin/static/api_client.tsx b/snuba/admin/static/api_client.tsx index 9e82fac1768..2df5a6269b2 100644 --- a/snuba/admin/static/api_client.tsx +++ b/snuba/admin/static/api_client.tsx @@ -39,6 +39,7 @@ import { AllocationPolicy } from "SnubaAdmin/capacity_management/types"; import { ReplayInstruction, Topic } from "SnubaAdmin/dead_letter_queue/types"; import { AutoReplacementsBypassProjectsData } from "SnubaAdmin/auto_replacements_bypass_projects/types"; +import { ClickhouseNodeInfo } from "./database_clusters/types"; interface Client { getSettings: () => Promise; @@ -60,6 +61,7 @@ interface Client { getDescriptions: () => Promise; getAuditlog: () => Promise; getClickhouseNodes: () => Promise<[ClickhouseNodeData]>; + getClickhouseNodeInfo: () => Promise<[ClickhouseNodeInfo]> getSnubaDatasetNames: () => Promise; getAllowedProjects: () => Promise; executeSnQLQuery: (query: SnQLRequest) => Promise; @@ -207,6 +209,11 @@ function Client(): Client { }); }, + getClickhouseNodeInfo: () => { + const url = baseUrl + "clickhouse_node_info"; + return fetch(url).then((resp) => resp.json()); + }, + getSnubaDatasetNames: () => { const url = baseUrl + "snuba_datasets"; return fetch(url).then((resp) => resp.json()); diff --git a/snuba/admin/static/data.tsx b/snuba/admin/static/data.tsx index 4c02d37b514..c6828dc7de6 100644 --- a/snuba/admin/static/data.tsx +++ b/snuba/admin/static/data.tsx @@ -16,6 +16,7 @@ import SnubaExplain from "SnubaAdmin/snuba_explain"; import Welcome from "SnubaAdmin/welcome"; import DeleteTool from "SnubaAdmin/delete_tool"; import ViewCustomJobs from "SnubaAdmin/manual_jobs"; +import DatabaseClusters from "./database_clusters"; const NAV_ITEMS = [ { id: "overview", display: "🤿 Snuba Admin", component: Welcome }, @@ -100,6 +101,11 @@ const NAV_ITEMS = [ display: "▶️ View/Run Custom Jobs", component: ViewCustomJobs, }, + { + id: "database-clusters", + display: "🗂️ Database Clusters", + component: DatabaseClusters, + }, ]; export { NAV_ITEMS }; diff --git a/snuba/admin/static/database_clusters/index.tsx b/snuba/admin/static/database_clusters/index.tsx new file mode 100644 index 00000000000..816d7af0dc7 --- /dev/null +++ b/snuba/admin/static/database_clusters/index.tsx @@ -0,0 +1,53 @@ +import { MantineReactTable, MRT_ColumnDef, useMantineReactTable } from "mantine-react-table"; +import React, { useMemo } from "react"; +import { useEffect, useState } from "react"; +import Client from "SnubaAdmin/api_client"; +import { ClickhouseNodeInfo } from "./types"; + +function DatabaseClusters(props: { api: Client }) { + const [nodeData, setNodeData] = useState([]); + + useEffect(() => { + props.api.getClickhouseNodeInfo().then((res) => { + setNodeData(res); + }); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'cluster', + header: 'Cluster', + }, + { + accessorFn: (row) => `${row.host_name} (${row.host_address})`, + header: 'Host', + }, + { + accessorKey: 'shard', + header: 'Shard', + }, + { + accessorKey: 'replica', + header: 'Replica', + }, + { + accessorKey: 'version', + header: 'Version', + filterVariant: 'multi-select', + }, + ], + [], + ); + + const table = useMantineReactTable({ + columns, + data: nodeData, + initialState: { showColumnFilters: true }, + enableFacetedValues: true, + }); + + return ; +} + +export default DatabaseClusters diff --git a/snuba/admin/static/database_clusters/types.tsx b/snuba/admin/static/database_clusters/types.tsx new file mode 100644 index 00000000000..4ac5fd3d86d --- /dev/null +++ b/snuba/admin/static/database_clusters/types.tsx @@ -0,0 +1,10 @@ +type ClickhouseNodeInfo = { + cluster: string, + host_name: string, + host_address: string, + shard: number, + replica: number, + version: string, +}; + +export { ClickhouseNodeInfo }; diff --git a/snuba/admin/tool_policies.py b/snuba/admin/tool_policies.py index ce1d297e5f7..4c8a131d58e 100644 --- a/snuba/admin/tool_policies.py +++ b/snuba/admin/tool_policies.py @@ -33,6 +33,7 @@ class AdminTools(Enum): SNUBA_EXPLAIN = "snuba-explain" DELETE_TOOL = "delete_tool" MANUAL_JOBS = "view-jobs" + DATABASE_CLUSTERS = "database-clusters" DEVELOPER_TOOLS: set[AdminTools] = {AdminTools.SNQL_TO_SQL, AdminTools.QUERY_TRACING} diff --git a/snuba/admin/views.py b/snuba/admin/views.py index 2a9b9fd7952..a68ab208cdb 100644 --- a/snuba/admin/views.py +++ b/snuba/admin/views.py @@ -22,6 +22,7 @@ get_storages_with_allocation_policies, ) from snuba.admin.clickhouse.common import InvalidCustomQuery +from snuba.admin.clickhouse.database_clusters import get_node_info from snuba.admin.clickhouse.migration_checks import run_migration_checks_and_policies from snuba.admin.clickhouse.nodes import get_storage_info from snuba.admin.clickhouse.predefined_cardinality_analyzer_queries import ( @@ -1263,3 +1264,9 @@ def deletes_enabled() -> Response: @check_tool_perms(tools=[AdminTools.MANUAL_JOBS]) def get_job_specs() -> Response: return make_response(jsonify(list_job_specs()), 200) + + +@application.route("/clickhouse_node_info") +@check_tool_perms(tools=[AdminTools.DATABASE_CLUSTERS]) +def clickhouse_node_info() -> Response: + return make_response(jsonify(get_node_info()), 200) diff --git a/snuba/admin/yarn.lock b/snuba/admin/yarn.lock index 3618e483f39..ef256e1eeb1 100644 --- a/snuba/admin/yarn.lock +++ b/snuba/admin/yarn.lock @@ -481,16 +481,16 @@ resolved "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz" integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== -"@emotion/babel-plugin@^11.11.0": - version "11.11.0" - resolved "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz" - integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== +"@emotion/babel-plugin@^11.12.0": + version "11.12.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2" + integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw== dependencies: "@babel/helper-module-imports" "^7.16.7" "@babel/runtime" "^7.18.3" - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/serialize" "^1.1.2" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.2.0" babel-plugin-macros "^3.1.0" convert-source-map "^1.5.0" escape-string-regexp "^4.0.0" @@ -498,76 +498,76 @@ source-map "^0.5.7" stylis "4.2.0" -"@emotion/cache@^11.11.0": - version "11.11.0" - resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== +"@emotion/cache@^11.13.0": + version "11.13.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7" + integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/sheet" "^1.2.2" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" stylis "4.2.0" -"@emotion/hash@^0.9.1": - version "0.9.1" - resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz" - integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== -"@emotion/react@^11.11.1": - version "11.11.1" - resolved "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz" - integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== +"@emotion/react@^11.13.3": + version "11.13.3" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4" + integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== dependencies: "@babel/runtime" "^7.18.3" - "@emotion/babel-plugin" "^11.11.0" - "@emotion/cache" "^11.11.0" - "@emotion/serialize" "^1.1.2" - "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" + "@emotion/babel-plugin" "^11.12.0" + "@emotion/cache" "^11.13.0" + "@emotion/serialize" "^1.3.1" + "@emotion/use-insertion-effect-with-fallbacks" "^1.1.0" + "@emotion/utils" "^1.4.0" + "@emotion/weak-memoize" "^0.4.0" hoist-non-react-statics "^3.3.1" -"@emotion/serialize@^1.1.2": - version "1.1.2" - resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz" - integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.1.tgz#490b660178f43d2de8e92b278b51079d726c05c3" + integrity sha512-dEPNKzBPU+vFPGa+z3axPRn8XVDetYORmDC0wAiej+TNcOZE70ZMJa0X7JdeoM6q/nWTMZeLpN/fTnD9o8MQBA== dependencies: - "@emotion/hash" "^0.9.1" - "@emotion/memoize" "^0.8.1" - "@emotion/unitless" "^0.8.1" - "@emotion/utils" "^1.2.1" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.0" csstype "^3.0.2" -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== -"@emotion/unitless@^0.8.1": - version "0.8.1" - resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz" - integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== -"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz" - integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== +"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf" + integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== +"@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.0.tgz#262f1d02aaedb2ec91c83a0955dd47822ad5fbdd" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== "@floating-ui/core@^1.3.1": version "1.3.1" @@ -922,22 +922,29 @@ "@linaria/logger" "^4.0.0" babel-merge "^3.0.0" -"@mantine/core@^6.0.15": - version "6.0.15" - resolved "https://registry.npmjs.org/@mantine/core/-/core-6.0.15.tgz" - integrity sha512-CN2UV2RXumxac75cWgUPMcHiE36T4ciIpFf20JwpazshnwFNu7scvy6GJDwUouf0zTBLnPMAD1S/B4mIRc3aQw== +"@mantine/core@6.0.21": + version "6.0.21" + resolved "https://registry.yarnpkg.com/@mantine/core/-/core-6.0.21.tgz#6e3a1b8d0f6869518a644d5f5e3d55a5db7e1e51" + integrity sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg== dependencies: "@floating-ui/react" "^0.19.1" - "@mantine/styles" "6.0.15" - "@mantine/utils" "6.0.15" + "@mantine/styles" "6.0.21" + "@mantine/utils" "6.0.21" "@radix-ui/react-scroll-area" "1.0.2" react-remove-scroll "^2.5.5" react-textarea-autosize "8.3.4" -"@mantine/hooks@^6.0.15": - version "6.0.15" - resolved "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.15.tgz" - integrity sha512-2CtNKw/tdiXjeseldrg1J2jy+WKrwiCY/J6UMkZqlZ8aM0j3vFVl5cnyn46i5KzbdGqNjW01aihfJJfkeQh4oQ== +"@mantine/dates@6.0.21": + version "6.0.21" + resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-6.0.21.tgz#a140da22bd4188aff446ddb5d3bcf37b658b0608" + integrity sha512-nSX7MxNkHyyDJqEJOT7Wg930jBfgWz+3pnoWo601cYDvFjh5GgcRz66v36rnMJFK1/56k5G9rWzUOzuM94j6hg== + dependencies: + "@mantine/utils" "6.0.21" + +"@mantine/hooks@6.0.21": + version "6.0.21" + resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-6.0.21.tgz#bc009d8380ad18455b90f3ddaf484de16a13da95" + integrity sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew== "@mantine/prism@^6.0.15": version "6.0.15" @@ -947,10 +954,10 @@ "@mantine/utils" "6.0.15" prism-react-renderer "^1.2.1" -"@mantine/styles@6.0.15": - version "6.0.15" - resolved "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.15.tgz" - integrity sha512-lOcEshBVbaN55gqsaiRPDY3///gfE+0o13ePp5PbCIA1sTKCvHz9aojHxXIICQV0ua9CXyleHzn5G0Ypw21/NQ== +"@mantine/styles@6.0.21": + version "6.0.21" + resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-6.0.21.tgz#8ea097fc76cbb3ed55f5cfd719d2f910aff5031b" + integrity sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg== dependencies: clsx "1.1.1" csstype "3.0.9" @@ -967,6 +974,11 @@ resolved "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.15.tgz" integrity sha512-iVaobFQTCQWG6SRi3im0/nONKCtGRPobG7bXn9GiIT96E4t7uTPglQpo/ZktDrF1XCL8CO/HoQmks4A2iXuMFw== +"@mantine/utils@6.0.21": + version "6.0.21" + resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-6.0.21.tgz#6185506e91cba3e308aaa8ea9ababc8e767995d6" + integrity sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ== + "@popperjs/core@^2.11.6", "@popperjs/core@^2.9.0": version "2.11.7" resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz" @@ -1302,18 +1314,48 @@ dependencies: tslib "^2.4.0" -"@tabler/icons-react@^2.23.0": - version "2.23.0" - resolved "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.23.0.tgz" - integrity sha512-+H4mC1EZVCzCRhnPwZEVTI0veVCJuAKlopeCnRlfsYcmzgJm6Ye234c4A2qrLPQoi1Y29uN9+kqCyuYW007jPg== +"@tabler/icons-react@^3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@tabler/icons-react/-/icons-react-3.17.0.tgz#badafce6618f6b8104262538e3f55d34d09c2e29" + integrity sha512-Ndm9Htv7KpIU1PYYrzs5EMhyA3aZGcgaxUp9Q1XOxcRZ+I0X+Ub2WS5f4bkRyDdL1s0++k2T9XRgmg2pG113sw== + dependencies: + "@tabler/icons" "3.17.0" + +"@tabler/icons@3.17.0": + version "3.17.0" + resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-3.17.0.tgz#329ca3e4cb533c5a6a61467fe5d6de14a0813020" + integrity sha512-sCSfAQ0w93KSnSL7tS08n73CdIKpuHP8foeLMWgDKiZaCs8ZE//N3ytazCk651ZtruTtByI3b+ZDj7nRf+hHvA== + +"@tanstack/match-sorter-utils@8.8.4": + version "8.8.4" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" + integrity sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw== + dependencies: + remove-accents "0.4.2" + +"@tanstack/react-table@8.10.6": + version "8.10.6" + resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.10.6.tgz#a8c03cc06ac890bce4404739b9356059c4259dd4" + integrity sha512-D0VEfkIYnIKdy6SHiBNEaMc4SxO+MV7ojaPhRu8jP933/gbMi367+Wul2LxkdovJ5cq6awm0L1+jgxdS/unzIg== + dependencies: + "@tanstack/table-core" "8.10.6" + +"@tanstack/react-virtual@3.0.0-beta.63": + version "3.0.0-beta.63" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.63.tgz#a68d31b7454c99f40667ec3d85d696a4dd058ef9" + integrity sha512-n4aaZs3g9U2oZjFp8dAeT1C2g4rr/3lbCo2qWbD9NquajKnGx7R+EfLBAHJ6pVMmfsTMZ0XCBwkIs7U74R/s0A== dependencies: - "@tabler/icons" "2.23.0" - prop-types "^15.7.2" + "@tanstack/virtual-core" "3.0.0-beta.63" -"@tabler/icons@2.23.0": - version "2.23.0" - resolved "https://registry.npmjs.org/@tabler/icons/-/icons-2.23.0.tgz" - integrity sha512-dU54aBwaxG0H+jQ4BdrqtYFN5L7PZevvlnzyL6XeOZgfDS3+sVNCtuG3JmpTEqQSwGLYC1IEwogPGA/Iit2bOA== +"@tanstack/table-core@8.10.6": + version "8.10.6" + resolved "https://registry.yarnpkg.com/@tanstack/table-core/-/table-core-8.10.6.tgz#c79d145dfc3dc9947a2b1cdac82cd4ec4cef822a" + integrity sha512-9t8brthhAmCBIjzk7fCDa/kPKoLQTtA31l9Ir76jYxciTlHU61r/6gYm69XF9cbg9n88gVL5y7rNpeJ2dc1AFA== + +"@tanstack/virtual-core@3.0.0-beta.63": + version "3.0.0-beta.63" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.63.tgz#fa6ffc8b5b9bfef19006ea330f00456381c30361" + integrity sha512-KhhfRYSoQpl0y+2axEw+PJZd/e/9p87PDpPompxcXnweNpt9ZHCT/HuNx7MKM9PVY/xzg9xJSWxwnSCrO+d6PQ== "@testing-library/dom@>=7": version "9.2.0" @@ -2425,6 +2467,11 @@ data-urls@^3.0.2: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" +dayjs@^1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== + debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" @@ -3880,6 +3927,15 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +mantine-react-table@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/mantine-react-table/-/mantine-react-table-1.3.4.tgz#0fdaba69e344b1c59a1616f9288a8e381472f83c" + integrity sha512-rD0CaeC4RCU7k/ZKvfj5ijFFMd4clGpeg/EwMcogYFioZjj8aNfD78osTNNYr90AnOAFGnd7ZnderLK89+W1ZQ== + dependencies: + "@tanstack/match-sorter-utils" "8.8.4" + "@tanstack/react-table" "8.10.6" + "@tanstack/react-virtual" "3.0.0-beta.63" + markdown-it@^13.0.1: version "13.0.1" resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz" @@ -4251,7 +4307,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -4582,6 +4638,11 @@ regexp.prototype.flags@^1.4.3: define-properties "^1.1.3" functions-have-names "^1.2.2" +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" diff --git a/tests/admin/test_api.py b/tests/admin/test_api.py index 82e4ba41eb7..e935f312368 100644 --- a/tests/admin/test_api.py +++ b/tests/admin/test_api.py @@ -1,10 +1,11 @@ from __future__ import annotations -from typing import Any +from typing import Any, Sequence from unittest import mock import pytest import simplejson as json +from attr import dataclass from flask.testing import FlaskClient from snuba import state @@ -22,6 +23,11 @@ ) +@dataclass +class FakeClickhouseResult: + results: Sequence[Any] + + @pytest.fixture def admin_api() -> FlaskClient: from snuba.admin.views import application @@ -840,3 +846,30 @@ def test_prod_mql_query_invalid_project_query(admin_api: FlaskClient) -> None: assert response.status_code == 400 data = json.loads(response.data) assert data["error"]["message"] == "Cannot access the following project ids: {2}" + + +@mock.patch("snuba.admin.clickhouse.database_clusters.get_ro_node_connection") +@pytest.mark.redis_db +@pytest.mark.clickhouse_db +def test_clickhouse_node_info( + get_ro_node_connection_mock: mock.Mock, admin_api: FlaskClient +) -> None: + expected_result = { + "cluster": "Cluster", + "host_name": "Host", + "host_address": "127.0.0.1", + "shard": 1, + "replica": 1, + "version": "v1", + } + + connection_mock = mock.Mock() + connection_mock.execute.return_value = FakeClickhouseResult( + [("Cluster", "Host", "127.0.0.1", 1, 1, "v1")] + ) + get_ro_node_connection_mock.return_value = connection_mock + response = admin_api.get("/clickhouse_node_info") + assert response.status_code == 200 + + response_data = json.loads(response.data) + assert len(response_data) > 0 and response_data[0] == expected_result