From 8a6b46c5c511346dcf3981feba114649d46fb737 Mon Sep 17 00:00:00 2001 From: Eric Fu Date: Wed, 8 May 2024 14:52:03 +0800 Subject: [PATCH] feat: add database and schema in dashboard (#16610) --- dashboard/components/Relations.tsx | 31 ++++++++++++++++++-- dashboard/lib/api/api.ts | 4 ++- dashboard/lib/api/fetch.ts | 3 +- dashboard/lib/api/streaming.ts | 37 +++++++++++++++++++++++- src/meta/src/controller/catalog.rs | 10 +++++++ src/meta/src/dashboard/mod.rs | 37 +++++++++++++++++++++++- src/meta/src/manager/catalog/database.rs | 4 +++ src/meta/src/manager/catalog/mod.rs | 5 +++- 8 files changed, 123 insertions(+), 8 deletions(-) diff --git a/dashboard/components/Relations.tsx b/dashboard/components/Relations.tsx index 6d04d041b368..8f536897fb80 100644 --- a/dashboard/components/Relations.tsx +++ b/dashboard/components/Relations.tsx @@ -33,7 +33,13 @@ import Link from "next/link" import { Fragment } from "react" import Title from "../components/Title" import useFetch from "../lib/api/fetch" -import { Relation, StreamingJob } from "../lib/api/streaming" +import { + Relation, + StreamingJob, + getDatabases, + getSchemas, + getUsers, +} from "../lib/api/streaming" import extractColumnInfo from "../lib/extractInfo" import { Sink as RwSink, @@ -114,7 +120,22 @@ export function Relations( getRelations: () => Promise, extraColumns: Column[] ) { - const { response: relationList } = useFetch(getRelations) + const { response: relationList } = useFetch(async () => { + const relations = await getRelations() + const users = await getUsers() + const databases = await getDatabases() + const schemas = await getSchemas() + return relations.map((r) => { + // Add owner, schema, and database names. It's linear search but the list is small. + const owner = users.find((u) => u.id === r.owner) + const ownerName = owner?.name + const schema = schemas.find((s) => s.id === r.schemaId) + const schemaName = schema?.name + const database = databases.find((d) => d.id === r.databaseId) + const databaseName = database?.name + return { ...r, ownerName, schemaName, databaseName } + }) + }) const [modalData, setModalId] = useCatalogModal(relationList) const modal = ( @@ -129,6 +150,8 @@ export function Relations( Id + Database + Schema Name Owner {extraColumns.map((c) => ( @@ -153,8 +176,10 @@ export function Relations( {r.id} + {r.databaseName} + {r.schemaName} {r.name} - {r.owner} + {r.ownerName} {extraColumns.map((c) => ( {c.content(r)} ))} diff --git a/dashboard/lib/api/api.ts b/dashboard/lib/api/api.ts index 0df045429548..6c5d88554245 100644 --- a/dashboard/lib/api/api.ts +++ b/dashboard/lib/api/api.ts @@ -26,7 +26,9 @@ export const PREDEFINED_API_ENDPOINTS = [ ] export const DEFAULT_API_ENDPOINT: string = - process.env.NODE_ENV === "production" ? PROD_API_ENDPOINT : MOCK_API_ENDPOINT + process.env.NODE_ENV === "production" + ? PROD_API_ENDPOINT + : MOCK_API_ENDPOINT; // EXTERNAL_META_NODE_API_ENDPOINT to debug with RisingWave servers export const API_ENDPOINT_KEY = "risingwave.dashboard.api.endpoint" diff --git a/dashboard/lib/api/fetch.ts b/dashboard/lib/api/fetch.ts index 7aa34826e6a5..6cf980202b54 100644 --- a/dashboard/lib/api/fetch.ts +++ b/dashboard/lib/api/fetch.ts @@ -33,6 +33,7 @@ export default function useFetch( const [response, setResponse] = useState() const toast = useErrorToast() + // NOTE(eric): Don't put `fetchFn` in the dependency array. It might be a lambda function useEffect(() => { const fetchData = async () => { if (when) { @@ -52,7 +53,7 @@ export default function useFetch( const timer = setInterval(fetchData, intervalMs) return () => clearInterval(timer) - }, [toast, fetchFn, intervalMs, when]) + }, [toast, intervalMs, when]) return { response } } diff --git a/dashboard/lib/api/streaming.ts b/dashboard/lib/api/streaming.ts index 4295e639fb21..1a8e97081caa 100644 --- a/dashboard/lib/api/streaming.ts +++ b/dashboard/lib/api/streaming.ts @@ -17,12 +17,20 @@ import _ from "lodash" import sortBy from "lodash/sortBy" -import { Sink, Source, Table, View } from "../../proto/gen/catalog" +import { + Database, + Schema, + Sink, + Source, + Table, + View, +} from "../../proto/gen/catalog" import { ListObjectDependenciesResponse_ObjectDependencies as ObjectDependencies, TableFragments, } from "../../proto/gen/meta" import { ColumnCatalog, Field } from "../../proto/gen/plan_common" +import { UserInfo } from "../../proto/gen/user" import api from "./api" export async function getFragments(): Promise { @@ -37,7 +45,14 @@ export interface Relation { id: number name: string owner: number + schemaId: number + databaseId: number columns: (ColumnCatalog | Field)[] + + // For display + ownerName?: string + schemaName?: string + databaseName?: string } export interface StreamingJob extends Relation { @@ -135,6 +150,26 @@ export async function getViews() { return views } +export async function getUsers() { + let users: UserInfo[] = (await api.get("/users")).map(UserInfo.fromJSON) + users = sortBy(users, (x) => x.id) + return users +} + +export async function getDatabases() { + let databases: Database[] = (await api.get("/databases")).map( + Database.fromJSON + ) + databases = sortBy(databases, (x) => x.id) + return databases +} + +export async function getSchemas() { + let schemas: Schema[] = (await api.get("/schemas")).map(Schema.fromJSON) + schemas = sortBy(schemas, (x) => x.id) + return schemas +} + export async function getObjectDependencies() { let objDependencies: ObjectDependencies[] = ( await api.get("/object_dependencies") diff --git a/src/meta/src/controller/catalog.rs b/src/meta/src/controller/catalog.rs index 7607915bd13f..481331163c28 100644 --- a/src/meta/src/controller/catalog.rs +++ b/src/meta/src/controller/catalog.rs @@ -2522,6 +2522,11 @@ impl CatalogController { inner.list_databases().await } + pub async fn list_schemas(&self) -> MetaResult> { + let inner = self.inner.read().await; + inner.list_schemas().await + } + pub async fn list_all_state_tables(&self) -> MetaResult> { let inner = self.inner.read().await; inner.list_all_state_tables().await @@ -2612,6 +2617,11 @@ impl CatalogController { inner.list_views().await } + pub async fn list_users(&self) -> MetaResult> { + let inner = self.inner.read().await; + inner.list_users().await + } + pub async fn get_table_by_name( &self, database_name: &str, diff --git a/src/meta/src/dashboard/mod.rs b/src/meta/src/dashboard/mod.rs index b72854c6a398..ec52c3a3ee53 100644 --- a/src/meta/src/dashboard/mod.rs +++ b/src/meta/src/dashboard/mod.rs @@ -55,7 +55,7 @@ pub(super) mod handlers { use itertools::Itertools; use risingwave_common_heap_profiling::COLLAPSED_SUFFIX; use risingwave_pb::catalog::table::TableType; - use risingwave_pb::catalog::{Sink, Source, Table, View}; + use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Table, View}; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::PbTableFragments; @@ -63,6 +63,7 @@ pub(super) mod handlers { GetBackPressureResponse, HeapProfilingResponse, ListHeapProfilingResponse, StackTraceResponse, }; + use risingwave_pb::user::PbUserInfo; use serde_json::json; use thiserror_ext::AsReport; @@ -195,6 +196,37 @@ pub(super) mod handlers { Ok(Json(table_fragments)) } + pub async fn list_users(Extension(srv): Extension) -> Result>> { + let users = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_users().await, + MetadataManager::V2(mgr) => mgr.catalog_controller.list_users().await.map_err(err)?, + }; + + Ok(Json(users)) + } + + pub async fn list_databases( + Extension(srv): Extension, + ) -> Result>> { + let databases = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_databases().await, + MetadataManager::V2(mgr) => { + mgr.catalog_controller.list_databases().await.map_err(err)? + } + }; + + Ok(Json(databases)) + } + + pub async fn list_schemas(Extension(srv): Extension) -> Result>> { + let schemas = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_schemas().await, + MetadataManager::V2(mgr) => mgr.catalog_controller.list_schemas().await.map_err(err)?, + }; + + Ok(Json(schemas)) + } + pub async fn list_object_dependencies( Extension(srv): Extension, ) -> Result>> { @@ -388,6 +420,9 @@ impl DashboardService { .route("/internal_tables", get(list_internal_tables)) .route("/sources", get(list_sources)) .route("/sinks", get(list_sinks)) + .route("/users", get(list_users)) + .route("/databases", get(list_databases)) + .route("/schemas", get(list_schemas)) .route("/object_dependencies", get(list_object_dependencies)) .route("/metrics/cluster", get(prometheus::list_prometheus_cluster)) .route( diff --git a/src/meta/src/manager/catalog/database.rs b/src/meta/src/manager/catalog/database.rs index ed128ee3e1ec..1cf466caddde 100644 --- a/src/meta/src/manager/catalog/database.rs +++ b/src/meta/src/manager/catalog/database.rs @@ -296,6 +296,10 @@ impl DatabaseManager { self.databases.values().cloned().collect_vec() } + pub fn list_schemas(&self) -> Vec { + self.schemas.values().cloned().collect_vec() + } + pub fn list_creating_background_mvs(&self) -> Vec { self.tables .values() diff --git a/src/meta/src/manager/catalog/mod.rs b/src/meta/src/manager/catalog/mod.rs index 665eb45fc3c3..4eb2970fea85 100644 --- a/src/meta/src/manager/catalog/mod.rs +++ b/src/meta/src/manager/catalog/mod.rs @@ -3515,6 +3515,10 @@ impl CatalogManager { self.core.lock().await.database.list_databases() } + pub async fn list_schemas(&self) -> Vec { + self.core.lock().await.database.list_schemas() + } + pub async fn list_tables(&self) -> Vec
{ self.core.lock().await.database.list_tables() } @@ -3866,7 +3870,6 @@ impl CatalogManager { Ok(()) } - #[cfg(test)] pub async fn list_users(&self) -> Vec { self.core.lock().await.user.list_users() }