From a0b23507e4d9ff67d765cff5a368da0af63d03ea Mon Sep 17 00:00:00 2001 From: Xinhao Xu <84456268+xxhZs@users.noreply.github.com> Date: Tue, 14 May 2024 18:38:50 +0800 Subject: [PATCH] feat(dashboard): add suscription in catalog (#16747) --- dashboard/components/Layout.tsx | 1 + dashboard/components/Relations.tsx | 16 +++++++----- dashboard/lib/api/streaming.ts | 16 ++++++++++-- dashboard/mock-server.js | 4 +++ dashboard/package-lock.json | 27 ++++++++++++++++++++ dashboard/package.json | 1 + dashboard/pages/subscriptions.tsx | 38 +++++++++++++++++++++++++++++ src/meta/src/controller/catalog.rs | 23 +++++++++++++++++ src/meta/src/dashboard/mod.rs | 18 +++++++++++++- src/meta/src/manager/catalog/mod.rs | 7 ++++++ 10 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 dashboard/pages/subscriptions.tsx diff --git a/dashboard/components/Layout.tsx b/dashboard/components/Layout.tsx index 7be9f727bdde..6daa7e821ce3 100644 --- a/dashboard/components/Layout.tsx +++ b/dashboard/components/Layout.tsx @@ -140,6 +140,7 @@ function Layout({ children }: { children: React.ReactNode }) { Internal Tables Sinks Views + Subscriptions
Streaming diff --git a/dashboard/components/Relations.tsx b/dashboard/components/Relations.tsx index 8f536897fb80..49161feea908 100644 --- a/dashboard/components/Relations.tsx +++ b/dashboard/components/Relations.tsx @@ -183,12 +183,16 @@ export function Relations( {extraColumns.map((c) => ( {c.content(r)} ))} - - {r.columns - .filter((col) => ("isHidden" in col ? !col.isHidden : true)) - .map((col) => extractColumnInfo(col)) - .join(", ")} - + {r.columns && r.columns.length > 0 && ( + + {r.columns + .filter((col) => + "isHidden" in col ? !col.isHidden : true + ) + .map((col) => extractColumnInfo(col)) + .join(", ")} + + )} ))} diff --git a/dashboard/lib/api/streaming.ts b/dashboard/lib/api/streaming.ts index 1a8e97081caa..948cd567d3f2 100644 --- a/dashboard/lib/api/streaming.ts +++ b/dashboard/lib/api/streaming.ts @@ -22,6 +22,7 @@ import { Schema, Sink, Source, + Subscription, Table, View, } from "../../proto/gen/catalog" @@ -47,9 +48,9 @@ export interface Relation { owner: number schemaId: number databaseId: number - columns: (ColumnCatalog | Field)[] // For display + columns?: (ColumnCatalog | Field)[] ownerName?: string schemaName?: string databaseName?: string @@ -66,6 +67,8 @@ export function relationType(x: Relation) { return "SINK" } else if ((x as Source).info !== undefined) { return "SOURCE" + } else if ((x as Subscription).dependentTableId !== undefined) { + return "SUBSCRIPTION" } else { return "UNKNOWN" } @@ -98,7 +101,8 @@ export async function getRelations() { await getTables(), await getIndexes(), await getSinks(), - await getSources() + await getSources(), + await getSubscriptions() ) relations = sortBy(relations, (x) => x.id) return relations @@ -150,6 +154,14 @@ export async function getViews() { return views } +export async function getSubscriptions() { + let subscriptions: Subscription[] = (await api.get("/subscriptions")).map( + Subscription.fromJSON + ) + subscriptions = sortBy(subscriptions, (x) => x.id) + return subscriptions +} + export async function getUsers() { let users: UserInfo[] = (await api.get("/users")).map(UserInfo.fromJSON) users = sortBy(users, (x) => x.id) diff --git a/dashboard/mock-server.js b/dashboard/mock-server.js index 2db52df788e2..50c55e12686b 100644 --- a/dashboard/mock-server.js +++ b/dashboard/mock-server.js @@ -45,6 +45,10 @@ app.get("/indexes", (req, res, next) => { res.json(require("./mock/indexes.json")) }) +app.get("/indexes", (req, res, next) => { + res.json(require("./mock/indexes.json")) +}) + app.get("/internal_tables", (req, res, next) => { res.json(require("./mock/internal_tables.json")) }) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 064357bf3f1f..5bf9ae127252 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -6,6 +6,7 @@ "": { "hasInstallScript": true, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -3350,6 +3351,14 @@ "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.1.0.tgz", "integrity": "sha512-PeaBcTmdZWcFf7n1aM+oiOdZc+sy14qi0emPIeUuGMTjbP0xLGrZu43kdpHnWSXy7/r4Ubp/vlg50MCV8+9Isg==" }, + "node_modules/16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "dependencies": { + "numeric": "^1.2.6" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -8680,6 +8689,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "node_modules/nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", @@ -11586,6 +11600,14 @@ } }, "dependencies": { + "16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "requires": { + "numeric": "^1.2.6" + } + }, "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -18034,6 +18056,11 @@ "set-blocking": "^2.0.0" } }, + "numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index f3c1436aca98..a3716f7802cc 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -13,6 +13,7 @@ "clean": "rm -rf .next/ && rm -rf out/" }, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", diff --git a/dashboard/pages/subscriptions.tsx b/dashboard/pages/subscriptions.tsx new file mode 100644 index 000000000000..b2daa38c3f95 --- /dev/null +++ b/dashboard/pages/subscriptions.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2024 RisingWave Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import { Column, Relations } from "../components/Relations" +import { getSubscriptions } from "../lib/api/streaming" +import { Subscription as RwSubscription } from "../proto/gen/catalog" + +export default function Subscriptions() { + const subscriptionRetentionSeconds: Column = { + name: "Retention Seconds", + width: 3, + content: (r) => r.retentionSeconds ?? "unknown", + } + + const subscriptionDependentTableId: Column = { + name: "Dependent Table Id", + width: 3, + content: (r) => r.dependentTableId ?? "unknown", + } + return Relations("Subscriptions", getSubscriptions, [ + subscriptionRetentionSeconds, + subscriptionDependentTableId, + ]) +} diff --git a/src/meta/src/controller/catalog.rs b/src/meta/src/controller/catalog.rs index b8d88fabad22..409672138d79 100644 --- a/src/meta/src/controller/catalog.rs +++ b/src/meta/src/controller/catalog.rs @@ -581,6 +581,29 @@ impl CatalogController { } })); + let subscription_dependencies: Vec<(SubscriptionId, TableId)> = Subscription::find() + .select_only() + .columns([ + subscription::Column::SubscriptionId, + subscription::Column::DependentTableId, + ]) + .join(JoinType::InnerJoin, subscription::Relation::Object.def()) + .filter( + subscription::Column::SubscriptionState + .eq(Into::::into(SubscriptionState::Created)) + .and(subscription::Column::DependentTableId.is_not_null()), + ) + .into_tuple() + .all(&inner.db) + .await?; + + obj_dependencies.extend(subscription_dependencies.into_iter().map( + |(subscription_id, table_id)| PbObjectDependencies { + object_id: subscription_id as _, + referenced_object_id: table_id as _, + }, + )); + Ok(obj_dependencies) } diff --git a/src/meta/src/dashboard/mod.rs b/src/meta/src/dashboard/mod.rs index ec52c3a3ee53..122955403261 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::{PbDatabase, PbSchema, Sink, Source, Table, View}; + use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Subscription, Table, View}; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::PbTableFragments; @@ -141,6 +141,21 @@ pub(super) mod handlers { list_table_catalogs_inner(&srv.metadata_manager, TableType::Index).await } + pub async fn list_subscription( + Extension(srv): Extension, + ) -> Result>> { + let subscriptions = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_subscriptions().await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_subscriptions() + .await + .map_err(err)?, + }; + + Ok(Json(subscriptions)) + } + pub async fn list_internal_tables( Extension(srv): Extension, ) -> Result>> { @@ -417,6 +432,7 @@ impl DashboardService { .route("/materialized_views", get(list_materialized_views)) .route("/tables", get(list_tables)) .route("/indexes", get(list_indexes)) + .route("/subscriptions", get(list_subscription)) .route("/internal_tables", get(list_internal_tables)) .route("/sources", get(list_sources)) .route("/sinks", get(list_sinks)) diff --git a/src/meta/src/manager/catalog/mod.rs b/src/meta/src/manager/catalog/mod.rs index ab452dfe05b2..b600a4a16c54 100644 --- a/src/meta/src/manager/catalog/mod.rs +++ b/src/meta/src/manager/catalog/mod.rs @@ -3702,6 +3702,13 @@ impl CatalogManager { } } + for subscription in core.subscriptions.values() { + dependencies.push(PbObjectDependencies { + object_id: subscription.id, + referenced_object_id: subscription.dependent_table_id, + }); + } + dependencies }