Skip to content

Commit

Permalink
feat: add button to delete a topic
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewinci committed Nov 4, 2022
1 parent b54e0d2 commit d7005e6
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 33 deletions.
7 changes: 7 additions & 0 deletions src-tauri/src/api/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ pub async fn get_topic_info(cluster_id: &str, topic_name: &str, state: tauri::St
Ok(cluster.admin_client.get_topic_info(topic_name).await?)
}

#[tauri::command]
pub async fn delete_topic(cluster_id: &str, topic_name: &str, state: tauri::State<'_, AppState>) -> Result<()> {
debug!("Retrieve topic info for {}", topic_name);
let cluster = state.get_cluster(cluster_id).await;
Ok(cluster.admin_client.delete_topic(topic_name).await?)
}

#[tauri::command]
pub async fn create_topic(
cluster_id: &str,
Expand Down
8 changes: 8 additions & 0 deletions src-tauri/src/lib/admin/topic_admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub trait TopicAdmin {
// topics
async fn list_topics(&self) -> Result<Vec<Topic>>;
fn get_topic(&self, topic_name: &str) -> Result<Topic>;
async fn delete_topic(&self, topic_name: &str) -> Result<()>;
async fn get_topic_info(&self, topic_name: &str) -> Result<TopicInfo>;
async fn create_topic(&self, topic_name: &str, partitions: i32, isr: i32, compacted: bool) -> Result<()>;
async fn get_last_offsets(&self, topic_names: &[&str]) -> Result<HashMap<String, Vec<PartitionOffset>>>;
Expand Down Expand Up @@ -48,6 +49,13 @@ impl TopicAdmin for KafkaAdmin {
}
}

async fn delete_topic(&self, topic_name: &str) -> Result<()> {
self.admin_client
.delete_topics(&[topic_name], &AdminOptions::default())
.await?;
Ok(())
}

async fn get_topic_info(&self, topic_name: &str) -> Result<TopicInfo> {
let topic = self.get_topic(topic_name)?;

Expand Down
3 changes: 2 additions & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod lib;

use crate::api::{
admin::{
create_topic, describe_consumer_group, get_consumer_group_state, get_last_offsets, get_topic_info,
create_topic, delete_topic, describe_consumer_group, get_consumer_group_state, get_last_offsets, get_topic_info,
list_consumer_groups, list_topics, set_consumer_group,
},
configuration::{get_configuration, write_configuration},
Expand Down Expand Up @@ -36,6 +36,7 @@ fn main() {
list_topics,
get_topic_info,
create_topic,
delete_topic,
get_last_offsets,
// admin consumer groups
get_consumer_group_state,
Expand Down
4 changes: 2 additions & 2 deletions src/pages/schema-registry/schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const Schema = ({ schemaName, clusterId }: SchemaProps) => {
<Container>
<Group noWrap style={{ maxHeight: 50 }} position={"apart"}>
<PageHeader title={schemaName} subtitle={`Compatibility level: ${subject?.compatibility}`} />
{state?.version && <Tool clusterId={clusterId} subject={schemaName} version={state.version} />}
{state?.version && <Tools clusterId={clusterId} subject={schemaName} version={state.version} />}
</Group>
<Divider my={10} />
{!isLoading && subject && (
Expand Down Expand Up @@ -81,7 +81,7 @@ const CustomPrism = styled(Prism)`
}
`;

const Tool = ({ clusterId, subject, version }: { clusterId: string; subject: string; version: number }) => {
const Tools = ({ clusterId, subject, version }: { clusterId: string; subject: string; version: number }) => {
const navigate = useNavigate();
const { success } = useNotifications();
const openDeleteSubjectModal = () =>
Expand Down
35 changes: 21 additions & 14 deletions src/pages/topics/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,26 @@ export const TopicsPage = () => {
if (!clusterId) {
throw Error("Missing clusterId in path!");
}
return (
<Allotment>
<Allotment.Pane minSize={430} maxSize={topicName ? 600 : undefined}>
<TopicList
clusterId={clusterId}
onTopicSelected={(activeTopic) => navigate(`/cluster/${clusterId}/topic/${activeTopic}`)}
/>
</Allotment.Pane>
{topicName && (
<Allotment.Pane minSize={520}>
<Topic clusterId={clusterId} topicName={topicName} />
</Allotment.Pane>
)}
</Allotment>
const topicList = (
<TopicList
clusterId={clusterId}
onTopicSelected={(activeTopic) => navigate(`/cluster/${clusterId}/topic/${activeTopic}`)}
/>
);
if (topicName) {
return (
<Allotment>
<Allotment.Pane minSize={430} maxSize={topicName ? 600 : undefined}>
{topicList}
</Allotment.Pane>
{topicName && (
<Allotment.Pane minSize={520}>
<Topic clusterId={clusterId} topicName={topicName} />
</Allotment.Pane>
)}
</Allotment>
);
} else {
return topicList;
}
};
59 changes: 51 additions & 8 deletions src/pages/topics/topic.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { ActionIcon, Badge, Button, Center, Container, Group, Loader, Tooltip, Text } from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons";
import { ActionIcon, Badge, Button, Center, Container, Group, Loader, Text, Menu } from "@mantine/core";
import { IconInfoCircle, IconTool, IconTrash } from "@tabler/icons";
import { RecordsList } from "./record-list";
import { getConsumerState, stopConsumer } from "../../tauri/consumer";
import { PageHeader } from "../../components";
import { openConsumerModal } from "./consumer-modal";
import { useQuery } from "@tanstack/react-query";
import { getLastOffsets, getTopicInfo } from "../../tauri/admin";
import { deleteTopic, getLastOffsets, getTopicInfo } from "../../tauri/admin";
import { useState } from "react";
import CodeEditor from "@uiw/react-textarea-code-editor";
import { Allotment } from "allotment";
import { useNavigate } from "react-router-dom";
import { openConfirmModal } from "@mantine/modals";
import { useNotifications } from "../../providers";

export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName: string }) => {
const { data, isLoading } = useQuery(
Expand Down Expand Up @@ -50,11 +53,7 @@ export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName:
subtitle={`Estimated Records: ${estimatedRecord ?? "..."}, Cleanup policy: ${
topicInfo?.cleanupPolicy ?? "..."
}, Partitions: ${topicInfo?.partitionCount ?? "..."}`}>
<Tooltip position="bottom" label="Topic info">
<ActionIcon>
<IconInfoCircle />
</ActionIcon>
</Tooltip>
<Tools clusterId={clusterId} topic={topicName} />
</PageHeader>
{isLoading && (
<Center mt={10}>
Expand Down Expand Up @@ -129,3 +128,47 @@ export const Topic = ({ clusterId, topicName }: { clusterId: string; topicName:
</>
);
};

const Tools = ({ clusterId, topic }: { clusterId: string; topic: string }) => {
const navigate = useNavigate();
const { success } = useNotifications();
const openDeleteTopicModal = () =>
openConfirmModal({
title: "Are you sure to delete this topic?",
children: (
<>
<Text color="red" size="sm">
The topic {topic} will be deleted. This action is not reversible!
</Text>
<Text size="sm">Note: this operation may fail if the ACLs do not allow the deletion.</Text>
</>
),
labels: { confirm: "Confirm", cancel: "Cancel" },
onConfirm: async () =>
await deleteTopic(clusterId, topic).then((_) => {
success(`Topic ${topic} delete successfully`);
navigate(`/cluster/${clusterId}/topics`);
}),
});

const openInfoModal = () => console.log("Not implemented yet");

return (
<Menu position="bottom-end" trigger="hover" openDelay={100} closeDelay={400}>
<Menu.Target>
<ActionIcon size={28} sx={{ marginRight: "10px" }}>
<IconTool />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>Tools</Menu.Label>
<Menu.Item icon={<IconInfoCircle size={14} />} onClick={openInfoModal}>
Topic info
</Menu.Item>
<Menu.Item color="red" icon={<IconTrash size={14} />} onClick={openDeleteTopicModal}>
Delete topic
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
};
17 changes: 9 additions & 8 deletions src/tauri/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export const setConsumerGroup = (
): Promise<void> => {
return invoke<void>("set_consumer_group", { clusterId, consumerGroupName, topics, offsetConfig }).catch(
(err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to create the consumer group ${consumerGroupName}`,
Expand All @@ -24,7 +23,6 @@ export const setConsumerGroup = (

export const getConsumerGroupState = (clusterId: string, consumerGroupName: string): Promise<string> => {
return invoke<string>("get_consumer_group_state", { clusterId, consumerGroupName }).catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to get the consumer group state`,
Expand All @@ -41,7 +39,6 @@ export const describeConsumerGroup = (
): Promise<ConsumerGroupInfo> => {
return invoke<ConsumerGroupInfo>("describe_consumer_group", { clusterId, consumerGroupName, ignoreCache }).catch(
(err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to describe the consumer group`,
Expand All @@ -54,7 +51,6 @@ export const describeConsumerGroup = (

export const getConsumerGroups = (clusterId: string): Promise<string[]> => {
return invoke<string[]>("list_consumer_groups", { clusterId }).catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to retrieve the list of consumer groups`,
Expand All @@ -72,7 +68,6 @@ export const createTopic = (
compacted: boolean
): Promise<void> => {
return invoke<void>("create_topic", { clusterId, topicName, partitions, isr, compacted }).catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to create the new topic`,
Expand All @@ -86,7 +81,6 @@ export const listTopics = (clusterId: string): Promise<string[]> =>
invoke<{ name: string }[]>("list_topics", { clusterId })
.then((topics) => topics.map((t) => t.name))
.catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to retrieve the list of topics`,
Expand All @@ -97,7 +91,6 @@ export const listTopics = (clusterId: string): Promise<string[]> =>

export const getTopicInfo = (clusterId: string, topicName: string): Promise<TopicInfo> =>
invoke<TopicInfo>("get_topic_info", { clusterId, topicName }).catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to retrieve topic info`,
Expand All @@ -106,9 +99,17 @@ export const getTopicInfo = (clusterId: string, topicName: string): Promise<Topi
throw err;
});

export const deleteTopic = (clusterId: string, topicName: string): Promise<void> =>
invoke<void>("delete_topic", { clusterId, topicName }).catch((err: TauriError) => {
addNotification({
type: "error",
title: `Unable to delete the topic`,
description: format(err),
});
throw err;
});
export const getLastOffsets = (clusterId: string, topicNames: [string]): Promise<Record<string, [PartitionOffset]>> =>
invoke<Record<string, [PartitionOffset]>>("get_last_offsets", { clusterId, topicNames }).catch((err: TauriError) => {
console.error(err);
addNotification({
type: "error",
title: `Unable to retrieve the last offset`,
Expand Down

0 comments on commit d7005e6

Please sign in to comment.