Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the UX of the auto refresh in the Message Browser, and make it reusable #346

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ui/api/messages/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function getTopicMessages(
| undefined;
maxValueLength: number | undefined;
},
): Promise<Message[]> {
): Promise<{ messages: Message[]; ts: Date }> {
const sp = new URLSearchParams(
filterUndefinedFromObj({
"fields[records]":
Expand Down Expand Up @@ -55,7 +55,7 @@ export async function getTopicMessages(
});
const rawData = await res.json();
log.trace({ rawData }, "Received messages");
return MessageApiResponse.parse(rawData).data;
return { messages: MessageApiResponse.parse(rawData).data, ts: new Date() };
}

export async function getTopicMessage(
Expand All @@ -67,7 +67,7 @@ export async function getTopicMessage(
},
): Promise<Message | undefined> {
log.info({ kafkaId, topicId, params }, "getTopicMessage");
const messages = await getTopicMessages(kafkaId, topicId, {
const { messages } = await getTopicMessages(kafkaId, topicId, {
pageSize: 1,
partition: params.partition,
filter: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "@/libs/patternfly/react-core";
import { Skeleton } from "@patternfly/react-core";
import { notFound } from "next/navigation";
import { Suspense } from "react";
import { ReactNode, Suspense } from "react";

export const fetchCache = "force-cache";

Expand All @@ -22,6 +22,7 @@ export function TopicHeader({
}: {
params: KafkaTopicParams;
}) {
const portal = <div key={"topic-header-portal"} id={"topic-header-portal"} />;
return (
<Suspense
fallback={
Expand Down Expand Up @@ -71,18 +72,21 @@ export function TopicHeader({
</Nav>
</PageNavigation>
}
actions={[portal]}
/>
}
>
<ConnectedTopicHeader params={{ kafkaId, topicId }} />
<ConnectedTopicHeader params={{ kafkaId, topicId }} portal={portal} />
</Suspense>
);
}

async function ConnectedTopicHeader({
params: { kafkaId, topicId },
portal,
}: {
params: KafkaTopicParams;
portal: ReactNode;
}) {
const cluster = await getKafkaCluster(kafkaId);
if (!cluster) {
Expand Down Expand Up @@ -135,6 +139,7 @@ async function ConnectedTopicHeader({
</Nav>
</PageNavigation>
}
actions={[portal]}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
"use client";

import { Message } from "@/api/messages/schema";
import { RefreshInterval } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/RefreshSelector";
import { RefreshInterval, RefreshSelector } from "@/components/RefreshSelector";
import { useFilterParams } from "@/utils/useFilterParams";
import { useEffect, useState, useTransition } from "react";
import { useEffect, useLayoutEffect, useState, useTransition } from "react";
import { createPortal } from "react-dom";
import { MessagesTable } from "./_components/MessagesTable";

export function ConnectedMessagesTable({
messages,
lastRefresh,
selectedMessage,
partitions,
params,
}: {
messages: Message[];
lastRefresh: Date | undefined;
selectedMessage: Message | undefined;
partitions: number;
params: {
Expand All @@ -24,8 +27,10 @@ export function ConnectedMessagesTable({
limit: number;
};
}) {
const [refreshInterval, setRefreshInterval] = useState<RefreshInterval>();
const [automaticRefresh, setAutomaticRefresh] = useState(false);
const [refreshInterval, setRefreshInterval] = useState<RefreshInterval>(1);
const [isPending, startTransition] = useTransition();
const [isAutorefreshing, startAutorefreshTransition] = useTransition();
const updateUrl = useFilterParams(params);

function setPartition(partition: number | undefined) {
Expand Down Expand Up @@ -116,37 +121,95 @@ export function ConnectedMessagesTable({

useEffect(() => {
let interval: ReturnType<typeof setInterval>;
if (refreshInterval) {
if (automaticRefresh) {
interval = setInterval(async () => {
updateUrl({ ...params, _ts: Date.now() });
if (!isAutorefreshing) {
startAutorefreshTransition(() =>
updateUrl({ ...params, _ts: Date.now() }),
);
}
}, refreshInterval * 1000);
}
return () => clearInterval(interval);
}, [params, updateUrl, refreshInterval]);
}, [params, updateUrl, automaticRefresh, refreshInterval, isAutorefreshing]);

return (
<MessagesTable
isRefreshing={isPending}
selectedMessage={selectedMessage}
lastUpdated={new Date()}
messages={messages}
partitions={partitions}
partition={params.partition}
limit={params.limit}
filterOffset={params["filter[offset]"]}
filterEpoch={params["filter[epoch]"]}
filterTimestamp={params["filter[timestamp]"]}
refreshInterval={refreshInterval}
onPartitionChange={setPartition}
onOffsetChange={setOffset}
onTimestampChange={setTimestamp}
onEpochChange={setEpoch}
onLatest={setLatest}
onLimitChange={setLimit}
onRefresh={refresh}
onSelectMessage={setSelected}
onDeselectMessage={deselectMessage}
onRefreshInterval={setRefreshInterval}
/>
<>
<MessagesTable
isRefreshing={isPending}
selectedMessage={selectedMessage}
lastUpdated={new Date()}
messages={messages}
partitions={partitions}
partition={params.partition}
limit={params.limit}
filterOffset={params["filter[offset]"]}
filterEpoch={params["filter[epoch]"]}
filterTimestamp={params["filter[timestamp]"]}
onPartitionChange={setPartition}
onOffsetChange={setOffset}
onTimestampChange={setTimestamp}
onEpochChange={setEpoch}
onLatest={setLatest}
onLimitChange={setLimit}
onSelectMessage={setSelected}
onDeselectMessage={deselectMessage}
/>
<ConnectedRefreshSelector
isRefreshing={isPending}
isLive={automaticRefresh}
refreshInterval={refreshInterval}
lastRefresh={lastRefresh}
onRefresh={refresh}
onRefreshInterval={setRefreshInterval}
onToggleLive={setAutomaticRefresh}
/>
</>
);
}

function ConnectedRefreshSelector({
isRefreshing,
isLive,
refreshInterval,
lastRefresh,
onRefresh,
onRefreshInterval,
onToggleLive,
}: {
isRefreshing: boolean;
isLive: boolean;
refreshInterval: RefreshInterval;
lastRefresh: Date | undefined;
onRefresh: () => void;
onRefreshInterval: (interval: RefreshInterval) => void;
onToggleLive: (enable: boolean) => void;
}) {
const [container, setContainer] = useState<Element | undefined>();

function seekContainer() {
const el = document.getElementById("topic-header-portal");
if (el) {
setContainer(el);
} else {
setTimeout(seekContainer, 100);
}
}

useLayoutEffect(seekContainer, []); // we want to run this just once

return container
? createPortal(
<RefreshSelector
isRefreshing={isRefreshing}
isLive={isLive}
refreshInterval={refreshInterval}
lastRefresh={lastRefresh}
onRefresh={onRefresh}
onToggleLive={onToggleLive}
onIntervalChange={onRefreshInterval}
/>,
container,
)
: null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import {
import { FilterGroup } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/FilterGroup";
import { NoResultsEmptyState } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/NoResultsEmptyState";
import { PartitionSelector } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/PartitionSelector";
import {
RefreshInterval,
RefreshSelector,
} from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/RefreshSelector";
import { DateTime } from "@/components/DateTime";
import { Number } from "@/components/Number";
import { ResponsiveTable } from "@/components/table";
Expand Down Expand Up @@ -56,7 +52,6 @@ const defaultColumns = ["offset", "timestampUTC", "key", "value"];

export type MessageBrowserProps = {
isRefreshing: boolean;
refreshInterval: RefreshInterval;
selectedMessage: Message | undefined;
lastUpdated: Date | undefined;
messages: Message[];
Expand All @@ -72,8 +67,6 @@ export type MessageBrowserProps = {
onEpochChange: (value: number | undefined) => void;
onLatest: () => void;
onLimitChange: (value: number) => void;
onRefresh: () => void;
onRefreshInterval: (interval: RefreshInterval) => void;
onSelectMessage: (message: Message) => void;
onDeselectMessage: () => void;
};
Expand All @@ -88,15 +81,12 @@ export function MessagesTable({
filterOffset,
filterEpoch,
filterTimestamp,
refreshInterval,
onPartitionChange,
onOffsetChange,
onTimestampChange,
onEpochChange,
onLatest,
onLimitChange,
onRefresh,
onRefreshInterval,
onSelectMessage,
onDeselectMessage,
}: MessageBrowserProps) {
Expand Down Expand Up @@ -141,7 +131,6 @@ export function MessagesTable({
<OuterScrollContainer>
<MessagesTableToolbar
isRefreshing={isRefreshing}
refreshInterval={refreshInterval}
partitions={partitions}
partition={partition}
limit={limit}
Expand All @@ -154,8 +143,6 @@ export function MessagesTable({
onEpochChange={onEpochChange}
onLatest={onLatest}
onLimitChange={onLimitChange}
onRefresh={onRefresh}
onRefreshInterval={onRefreshInterval}
onColumnManagement={() => setShowColumnsManagement(true)}
/>
<InnerScrollContainer>
Expand Down Expand Up @@ -319,15 +306,12 @@ export function MessagesTableToolbar({
partition,
partitions,
limit,
refreshInterval,
onEpochChange,
onTimestampChange,
onOffsetChange,
onPartitionChange,
onLimitChange,
onLatest,
onRefresh,
onRefreshInterval,
onColumnManagement,
}: Pick<
MessageBrowserProps,
Expand All @@ -338,15 +322,12 @@ export function MessagesTableToolbar({
| "partition"
| "partitions"
| "limit"
| "refreshInterval"
| "onEpochChange"
| "onTimestampChange"
| "onOffsetChange"
| "onPartitionChange"
| "onLimitChange"
| "onLatest"
| "onRefresh"
| "onRefreshInterval"
> & {
onColumnManagement: () => void;
}) {
Expand Down Expand Up @@ -420,17 +401,6 @@ export function MessagesTableToolbar({
/>
</ToolbarItem>
</ToolbarToggleGroup>
{/* icon buttons */}
<ToolbarGroup variant="icon-button-group">
<ToolbarItem>
<RefreshSelector
isRefreshing={isRefreshing}
refreshInterval={refreshInterval}
onClick={onRefresh}
onChange={onRefreshInterval}
/>
</ToolbarItem>
</ToolbarGroup>

<ToolbarItem>
<Button onClick={onColumnManagement} variant={"link"}>
Expand All @@ -445,6 +415,9 @@ export function MessagesTableToolbar({
isDisabled={isRefreshing}
/>
</ToolbarGroup>
<ToolbarGroup variant="icon-button-group">
<ToolbarItem></ToolbarItem>
</ToolbarGroup>
</ToolbarContent>
</Toolbar>
);
Expand All @@ -469,7 +442,6 @@ export function MessagesTableSkeleton({
>
<MessagesTableToolbar
isRefreshing={true}
refreshInterval={undefined}
partitions={1}
partition={partition}
limit={limit}
Expand All @@ -482,8 +454,6 @@ export function MessagesTableSkeleton({
onEpochChange={() => {}}
onLatest={() => {}}
onLimitChange={() => {}}
onRefresh={() => {}}
onRefreshInterval={() => {}}
onColumnManagement={() => {}}
/>
<ResponsiveTable
Expand Down
Loading