Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { FilterByBroker } from "./FilterByBroker";

const meta: Meta<typeof FilterByBroker> = {
component: FilterByBroker,
args: {
selectedBroker: "Broker1",
brokerList: ["Broker1", "Broker2", "Broker3"],
},
} as Meta<typeof FilterByBroker>;

export default meta;
type Story = StoryObj<typeof FilterByBroker>;

export const Default: Story = {};
77 changes: 77 additions & 0 deletions ui/components/ClusterOverview/components/FilterByBroker.tsx
Original file line number Diff line number Diff line change
@@ -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<Element, 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<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isBrokerSelectOpen}
isDisabled={disableToolbar || brokerList.length === 0}
style={{ width: "250px" }}
>
{selectedBroker || t("all_brokers")}
</MenuToggle>
);

return (
<ToolbarItem>
<Select
id="broker-select"
isOpen={isBrokerSelectOpen}
selected={selectedBroker || t("all_brokers")}
onSelect={onBrokerSelect}
onOpenChange={setIsBrokerSelectOpen}
toggle={toggle}
shouldFocusToggleOnSelect
>
<SelectList>
<SelectOption value={t("all_brokers")}>
{t("all_brokers")}
</SelectOption>
{brokerList.map((broker, index) => (
<SelectOption key={`broker-${index}`} value={broker}>
{broker}
</SelectOption>
))}
</SelectList>
</Select>
</ToolbarItem>
);
}
17 changes: 17 additions & 0 deletions ui/components/ClusterOverview/components/FilterByTime.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { FilterByTime } from "./FilterByTime";

const meta: Meta<typeof FilterByTime> = {
component: FilterByTime,
args: {
keyText: "string",
disableToolbar: false,
ariaLabel: "the aria label",
duration: 60,
},
} as Meta<typeof FilterByTime>;

export default meta;
type Story = StoryObj<typeof FilterByTime>;

export const Default: Story = {};
103 changes: 103 additions & 0 deletions ui/components/ClusterOverview/components/FilterByTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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<Element, 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);
};

const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isTimeSelectOpen}
isDisabled={disableToolbar}
style={{ width: "250px" }}
>
{DurationOptionsMap[duration]}
</MenuToggle>
);

return (
<ToolbarItem>
<label hidden id={`${keyText}-label`}>
{ariaLabel}
</label>
<Select
id={`filter-by-time-${keyText}`}
isOpen={isTimeSelectOpen}
selected={DurationOptionsMap[duration]}
onSelect={onTimeSelect}
onOpenChange={setIsTimeSelectOpen}
toggle={toggle}
shouldFocusToggleOnSelect
>
<SelectList>
{Object.values(DurationOptionsMap).map((label, idx) => (
<SelectOption key={`${keyText}-${idx}`} value={label}>
{label}
</SelectOption>
))}
</SelectList>
</Select>
</ToolbarItem>
);
}
44 changes: 44 additions & 0 deletions ui/components/ClusterOverview/components/FilterByTopic.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import { FilterByTopic } from "./FilterByTopic";

const meta: Meta<typeof FilterByTopic> = {
component: FilterByTopic,
args: {
selectedTopic: undefined,
topicList: ["lorem", "dolor", "ipsum"],
disableToolbar: false,
},
} as Meta<typeof FilterByTopic>;

export default meta;
type Story = StoryObj<typeof FilterByTopic>;

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",
],
};
132 changes: 132 additions & 0 deletions ui/components/ClusterOverview/components/FilterByTopic.tsx
Original file line number Diff line number Diff line change
@@ -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<any>();
const menuRef = useRef<any>();

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 = [
<SelectOption
key="all-topics"
itemId="all-topics"
isSelected={selectedTopic === undefined}
>
{allTopicsLabel}
</SelectOption>,
<SelectGroup label="Filter by topic" key="topic-filter-group">
{filteredTopics.map((topic, index) => (
<SelectOption
key={`topic-filter-${index + 1}`}
itemId={topic}
title={topic}
isSelected={selectedTopic === topic}
>
{topic}
</SelectOption>
))}
</SelectGroup>,
];

if (filter && menuItems.length === 1) {
menuItems.push(
<SelectOption isDisabled key="no-results">
{t("common:no_results_found")}
</SelectOption>,
);
}

const toggle = (
<MenuToggle
ref={toggleRef}
onClick={() => setIsOpen(!isOpen)}
isExpanded={isOpen}
isDisabled={disableToolbar || topicList.length === 0}
className="appserv-metrics-filterbytopic"
>
{selectedTopic || allTopicsLabel}
</MenuToggle>
);

const menu = (
<Menu
ref={menuRef}
onSelect={onSelect}
activeItemId={selectedTopic ?? "all-topics"}
isScrollable
>
<MenuSearch>
<MenuSearchInput>
<SearchInput
value={filter}
onChange={(_, val) => setFilter(val)}
onClear={(evt) => {
evt.stopPropagation();
setFilter("");
}}
/>
</MenuSearchInput>
</MenuSearch>

<MenuContent maxMenuHeight="200px">
<MenuList>{menuItems}</MenuList>
</MenuContent>
</Menu>
);

return (
<ToolbarItem>
<MenuContainer
toggle={toggle}
toggleRef={toggleRef}
menu={menu}
menuRef={menuRef}
isOpen={isOpen}
onOpenChange={setIsOpen}
onOpenChangeKeys={["Escape"]}
/>
</ToolbarItem>
);
}
Loading
Loading