From ed0552c116457b24479bba0b6c43ba01aec19270 Mon Sep 17 00:00:00 2001 From: hemahg Date: Wed, 5 Nov 2025 13:40:04 +0530 Subject: [PATCH 1/2] Add filters to Overview page: Filter by Broker, Time, and Topic Signed-off-by: hemahg --- .../components/FilterByBroker.stories.tsx | 15 +++ .../components/FilterByBroker.tsx | 77 +++++++++++++ .../components/FilterByTime.stories.tsx | 17 +++ .../components/FilterByTime.tsx | 104 ++++++++++++++++++ ui/messages/en.json | 3 + 5 files changed, 216 insertions(+) create mode 100644 ui/components/ClusterOverview/components/FilterByBroker.stories.tsx create mode 100644 ui/components/ClusterOverview/components/FilterByBroker.tsx create mode 100644 ui/components/ClusterOverview/components/FilterByTime.stories.tsx create mode 100644 ui/components/ClusterOverview/components/FilterByTime.tsx diff --git a/ui/components/ClusterOverview/components/FilterByBroker.stories.tsx b/ui/components/ClusterOverview/components/FilterByBroker.stories.tsx new file mode 100644 index 000000000..e849f9291 --- /dev/null +++ b/ui/components/ClusterOverview/components/FilterByBroker.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { FilterByBroker } from "./FilterByBroker"; + +const meta: Meta = { + component: FilterByBroker, + args: { + selectedBroker: "Broker1", + brokerList: ["Broker1", "Broker2", "Broker3"], + }, +} as Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/ui/components/ClusterOverview/components/FilterByBroker.tsx b/ui/components/ClusterOverview/components/FilterByBroker.tsx new file mode 100644 index 000000000..84e1a94f9 --- /dev/null +++ b/ui/components/ClusterOverview/components/FilterByBroker.tsx @@ -0,0 +1,77 @@ +import { + MenuToggle, + MenuToggleElement, + Select, + SelectList, + SelectOption, + ToolbarItem, +} from "@/libs/patternfly/react-core"; +import { useTranslations } from "next-intl"; +import { useState } from "react"; + +export function FilterByBroker({ + selectedBroker, + brokerList, + onSetSelectedBroker, + disableToolbar, +}: { + selectedBroker: string | undefined; + brokerList: string[]; + onSetSelectedBroker: (value: string | undefined) => void; + disableToolbar: boolean; +}) { + const t = useTranslations("metrics"); + const [isBrokerSelectOpen, setIsBrokerSelectOpen] = useState(false); + + const onToggleClick = () => setIsBrokerSelectOpen((prev) => !prev); + + const onBrokerSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + if (value === t("all_brokers")) { + onSetSelectedBroker(undefined); + } else { + onSetSelectedBroker(value as string); + } + setIsBrokerSelectOpen(false); + }; + + // Define the toggle (new API pattern) + const toggle = (toggleRef: React.Ref) => ( + + {selectedBroker || t("all_brokers")} + + ); + + return ( + + + + ); +} diff --git a/ui/components/ClusterOverview/components/FilterByTime.stories.tsx b/ui/components/ClusterOverview/components/FilterByTime.stories.tsx new file mode 100644 index 000000000..de15a308f --- /dev/null +++ b/ui/components/ClusterOverview/components/FilterByTime.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/nextjs"; +import { FilterByTime } from "./FilterByTime"; + +const meta: Meta = { + component: FilterByTime, + args: { + keyText: "string", + disableToolbar: false, + ariaLabel: "the aria label", + duration: 60, + }, +} as Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/ui/components/ClusterOverview/components/FilterByTime.tsx b/ui/components/ClusterOverview/components/FilterByTime.tsx new file mode 100644 index 000000000..96922b839 --- /dev/null +++ b/ui/components/ClusterOverview/components/FilterByTime.tsx @@ -0,0 +1,104 @@ +import { + MenuToggle, + MenuToggleElement, + Select, + SelectList, + SelectOption, + ToolbarItem, +} from "@/libs/patternfly/react-core"; +import { useState } from "react"; + +export enum DurationOptions { + Last5minutes = 5, + Last15minutes = 15, + Last30minutes = 30, + Last1hour = 60, + Last3hours = 3 * 60, + Last6hours = 6 * 60, + Last12hours = 12 * 60, + Last24hours = 24 * 60, + Last2days = 2 * 24 * 60, + Last7days = 7 * 24 * 60, +} + +export const DurationOptionsMap = { + [DurationOptions.Last5minutes]: "Last 5 minutes", + [DurationOptions.Last15minutes]: "Last 15 minutes", + [DurationOptions.Last30minutes]: "Last 30 minutes", + [DurationOptions.Last1hour]: "Last 1 hour", + [DurationOptions.Last3hours]: "Last 3 hours", + [DurationOptions.Last6hours]: "Last 6 hours", + [DurationOptions.Last12hours]: "Last 12 hours", + [DurationOptions.Last24hours]: "Last 24 hours", + [DurationOptions.Last2days]: "Last 2 days", + [DurationOptions.Last7days]: "Last 7 days", +} as const; + +export function FilterByTime({ + duration, + keyText, + ariaLabel, + disableToolbar, + onDurationChange, +}: { + duration: DurationOptions; + onDurationChange: (value: DurationOptions) => void; + keyText: string; + ariaLabel: string; + disableToolbar: boolean; +}) { + const [isTimeSelectOpen, setIsTimeSelectOpen] = useState(false); + + const onToggleClick = () => setIsTimeSelectOpen((prev) => !prev); + + const onTimeSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined, + ) => { + const mapping = Object.entries(DurationOptionsMap).find( + ([, label]) => label === value, + ); + if (mapping) { + onDurationChange(parseInt(mapping[0], 10) as DurationOptions); + } + setIsTimeSelectOpen(false); + }; + + // Menu toggle (new PF6 API) + const toggle = (toggleRef: React.Ref) => ( + + {DurationOptionsMap[duration]} + + ); + + return ( + + + + + ); +} diff --git a/ui/messages/en.json b/ui/messages/en.json index 861aebe24..067d41272 100644 --- a/ui/messages/en.json +++ b/ui/messages/en.json @@ -414,6 +414,9 @@ "no_data": "No clusters available.", "not_available": "n/a" }, + "metrics": { + "all_brokers": "All Brokers" + }, "ColumnsModal": { "title": "Manage columns", "description": "Chosen fields will be displayed in the table.", From 1c9d7678c394832eea9598dca3f57b167e7aff38 Mon Sep 17 00:00:00 2001 From: hemahg Date: Thu, 20 Nov 2025 13:06:19 +0530 Subject: [PATCH 2/2] Add filter by topic component Signed-off-by: hemahg --- .../components/FilterByTime.tsx | 1 - .../components/FilterByTopic.stories.tsx | 44 ++++++ .../components/FilterByTopic.tsx | 132 ++++++++++++++++++ ui/messages/en.json | 3 +- 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 ui/components/ClusterOverview/components/FilterByTopic.stories.tsx create mode 100644 ui/components/ClusterOverview/components/FilterByTopic.tsx diff --git a/ui/components/ClusterOverview/components/FilterByTime.tsx b/ui/components/ClusterOverview/components/FilterByTime.tsx index 96922b839..4b068b199 100644 --- a/ui/components/ClusterOverview/components/FilterByTime.tsx +++ b/ui/components/ClusterOverview/components/FilterByTime.tsx @@ -64,7 +64,6 @@ export function FilterByTime({ setIsTimeSelectOpen(false); }; - // Menu toggle (new PF6 API) const toggle = (toggleRef: React.Ref) => ( = { + component: FilterByTopic, + args: { + selectedTopic: undefined, + topicList: ["lorem", "dolor", "ipsum"], + disableToolbar: false, + }, +} as Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; +Default.args = {}; + +export const Disabled: Story = {}; +Disabled.args = { + disableToolbar: true, + selectedTopic: "lorem", +}; + +export const NoTopics: Story = {}; +NoTopics.args = { + topicList: undefined, +}; + +export const MultipleTopicsWithCommonWords: Story = {}; +MultipleTopicsWithCommonWords.args = { + topicList: ["lorem dolor", "lorem ipsum", "lorem foo", "dolor", "ipsum"], +}; + +export const DoesNotBreakWithLongWords: Story = {}; +DoesNotBreakWithLongWords.args = { + topicList: [ + "lorem dolor lorem dolor lorem dolor lorem dolor lorem dolor lorem dolor", + "lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum lorem ipsum ", + "lorem foo", + "dolor", + "ipsum", + ], +}; diff --git a/ui/components/ClusterOverview/components/FilterByTopic.tsx b/ui/components/ClusterOverview/components/FilterByTopic.tsx new file mode 100644 index 000000000..af6086a30 --- /dev/null +++ b/ui/components/ClusterOverview/components/FilterByTopic.tsx @@ -0,0 +1,132 @@ +import { + Menu, + MenuList, + MenuContent, + MenuSearch, + MenuSearchInput, + SearchInput, + SelectOption, + MenuToggle, + MenuContainer, + ToolbarItem, + SelectGroup, +} from "@/libs/patternfly/react-core"; +import { FilterIcon } from "@/libs/patternfly/react-icons"; +import { useTranslations } from "next-intl"; +import { useRef, useState } from "react"; + +export function FilterByTopic({ + selectedTopic, + topicList = [], + disableToolbar, + onSetSelectedTopic, +}: { + selectedTopic: string | undefined; + topicList: string[]; + disableToolbar: boolean; + onSetSelectedTopic: (value: string | undefined) => void; +}) { + const t = useTranslations("metrics"); + const allTopicsLabel = t("all_topics"); + + const [isOpen, setIsOpen] = useState(false); + const [filter, setFilter] = useState(""); + + const toggleRef = useRef(); + const menuRef = useRef(); + + const filteredTopics = topicList.filter((topic) => + topic.toLowerCase().includes(filter.toLowerCase()), + ); + + const onSelect = (_event: any, itemId: string | number | undefined) => { + if (itemId === "all-topics") { + onSetSelectedTopic(undefined); + } else { + onSetSelectedTopic(itemId as string); + } + setIsOpen(false); + }; + + const menuItems = [ + + {allTopicsLabel} + , + + {filteredTopics.map((topic, index) => ( + + {topic} + + ))} + , + ]; + + if (filter && menuItems.length === 1) { + menuItems.push( + + {t("common:no_results_found")} + , + ); + } + + const toggle = ( + setIsOpen(!isOpen)} + isExpanded={isOpen} + isDisabled={disableToolbar || topicList.length === 0} + className="appserv-metrics-filterbytopic" + > + {selectedTopic || allTopicsLabel} + + ); + + const menu = ( + + + + setFilter(val)} + onClear={(evt) => { + evt.stopPropagation(); + setFilter(""); + }} + /> + + + + + {menuItems} + + + ); + + return ( + + + + ); +} diff --git a/ui/messages/en.json b/ui/messages/en.json index 067d41272..ad04ce5f5 100644 --- a/ui/messages/en.json +++ b/ui/messages/en.json @@ -415,7 +415,8 @@ "not_available": "n/a" }, "metrics": { - "all_brokers": "All Brokers" + "all_brokers": "All Brokers", + "all_topics": "All topics" }, "ColumnsModal": { "title": "Manage columns",