From bf7e240267311875972b429d21ea05c37fe508d7 Mon Sep 17 00:00:00 2001 From: Riccardo Forina Date: Fri, 14 Jun 2024 11:12:29 +0200 Subject: [PATCH] Refactor the feature flags --- ui/app/[locale]/AppLayout.tsx | 7 +- ui/app/[locale]/ClusterConnectionDetails.tsx | 49 +-- ui/app/[locale]/ClusterDrawer.tsx | 12 +- ui/app/[locale]/home/page.tsx | 325 ++++++++++-------- .../topics/(page)/ConnectedTopicsTable.tsx | 5 +- .../[topicId]/configuration/ConfigTable.tsx | 4 +- .../topics/[topicId]/configuration/page.tsx | 4 +- .../kafka/[kafkaId]/topics/create/page.tsx | 4 +- ui/app/[locale]/layout.tsx | 5 +- .../TopicsTable/TopicsTable.stories.tsx | 1 + ui/components/TopicsTable/TopicsTable.tsx | 21 +- .../components/EmptyStateNoTopics.stories.tsx | 1 + .../components/EmptyStateNoTopics.tsx | 9 +- ui/environment.d.ts | 2 +- ui/utils/env.ts | 13 +- 15 files changed, 259 insertions(+), 203 deletions(-) diff --git a/ui/app/[locale]/AppLayout.tsx b/ui/app/[locale]/AppLayout.tsx index c14f81e7a..114492b88 100644 --- a/ui/app/[locale]/AppLayout.tsx +++ b/ui/app/[locale]/AppLayout.tsx @@ -19,7 +19,10 @@ import { HelpIcon } from "@/libs/patternfly/react-icons"; import { useTranslations } from "next-intl"; import { PropsWithChildren, Suspense } from "react"; -export function AppLayout({ children }: PropsWithChildren) { +export function AppLayout({ + children, + showLearningLinks, +}: PropsWithChildren<{ showLearningLinks: boolean }>) { const t = useTranslations(); return ( {/**/} - + diff --git a/ui/app/[locale]/ClusterConnectionDetails.tsx b/ui/app/[locale]/ClusterConnectionDetails.tsx index 266e3ec75..72490ebbb 100644 --- a/ui/app/[locale]/ClusterConnectionDetails.tsx +++ b/ui/app/[locale]/ClusterConnectionDetails.tsx @@ -11,12 +11,13 @@ import { } from "@/libs/patternfly/react-core"; import { Divider, Stack, StackItem } from "@patternfly/react-core"; import { useTranslations } from "next-intl"; -import { enabled as learningLinksEnabled } from "@/utils/learningLinks"; export async function ClusterConnectionDetails({ clusterId, + showLearningLinks, }: { clusterId: string; + showLearningLinks: boolean; }) { const t = useTranslations(); const data = await getKafkaCluster(clusterId); @@ -102,25 +103,33 @@ export async function ClusterConnectionDetails({ - { learningLinksEnabled() && - - - - { t("learning.links.connecting") && - - - {t("ClusterConnectionDetails.developing_kafka_client_applications",)} - - - } - - - {t("ClusterConnectionDetails.streams_portal")} - - - - - } + {showLearningLinks && ( + + + + {t("learning.links.connecting") && ( + + + {t( + "ClusterConnectionDetails.developing_kafka_client_applications", + )} + + + )} + + + {t("ClusterConnectionDetails.streams_portal")} + + + + + )} ); } diff --git a/ui/app/[locale]/ClusterDrawer.tsx b/ui/app/[locale]/ClusterDrawer.tsx index 366f60230..ec36c8869 100644 --- a/ui/app/[locale]/ClusterDrawer.tsx +++ b/ui/app/[locale]/ClusterDrawer.tsx @@ -16,7 +16,10 @@ import { Skeleton } from "@patternfly/react-core"; import { useTranslations } from "next-intl"; import { PropsWithChildren, Suspense } from "react"; -export function ClusterDrawer({ children }: PropsWithChildren) { +export function ClusterDrawer({ + children, + showLearningLinks, +}: PropsWithChildren<{ showLearningLinks: boolean }>) { const t = useTranslations(); const { expanded, clusterId, close } = useClusterDrawerContext(); return ( @@ -48,7 +51,12 @@ export function ClusterDrawer({ children }: PropsWithChildren) { } > - {clusterId && } + {clusterId && ( + + )} } diff --git a/ui/app/[locale]/home/page.tsx b/ui/app/[locale]/home/page.tsx index 4b45b8f87..af34782e7 100644 --- a/ui/app/[locale]/home/page.tsx +++ b/ui/app/[locale]/home/page.tsx @@ -1,4 +1,3 @@ -import { useTranslations } from "next-intl"; import { getConsumerGroups } from "@/api/consumerGroups/actions"; import { getKafkaCluster, getKafkaClusters } from "@/api/kafka/actions"; import { ClusterList } from "@/api/kafka/schema"; @@ -37,9 +36,10 @@ import { Tooltip, } from "@/libs/patternfly/react-core"; import { HelpIcon } from "@/libs/patternfly/react-icons"; +import { isProductizedBuild } from "@/utils/env"; +import { useTranslations } from "next-intl"; import { Suspense } from "react"; import styles from "./home.module.css"; -import { enabled as learningLinksEnabled } from "@/utils/learningLinks"; export default function Home() { const t = useTranslations(); @@ -57,7 +57,10 @@ export default function Home() { {t.rich("homepage.page_header", { product: productName })} - {t("homepage.page_subtitle", { brand: brand, product: productName })} + {t("homepage.page_subtitle", { + brand: brand, + product: productName, + })} @@ -92,17 +95,20 @@ export default function Home() { title={ - {t("homepage.recently_viewed_topics_header")} {" "} + {t("homepage.recently_viewed_topics_header")}{" "} - {t("homepage.last_accessed_topics", { product: productName })} + {t("homepage.last_accessed_topics", { + product: productName, + })} } @@ -115,143 +121,155 @@ export default function Home() { - { learningLinksEnabled() && - - - - - {t.rich("homepage.recommended_learning_resources")} - - - - } - collapsedTitle={ - - - - - - {t.rich("homepage.recommended_learning_resources")} - - - - - - - - - - - } - isCompact={true} - > - - - - - - - {t("learning.labels.overview")} - - , - - , - - - {t("homepage.view_documentation")} - - , - ]} - /> - - - { t("learning.links.gettingStarted") && - - - - - {t("learning.labels.gettingStarted")} - - , - - - , - - - {t("homepage.view_documentation")} - - , - ]} - /> - - - } - { t("learning.links.connecting") && - - - - - {t("learning.labels.connecting")} - - , - - - , - - - {t("homepage.view_documentation")} - - , - ]} - /> - - - } - - - - - {t("learning.labels.topicOperatorUse")} - - , - - - , - - - {t("homepage.view_documentation")} - - , - ]} - /> - - - - - - - } + + + + + + } + isCompact={true} + > + + + + + + + {t("learning.labels.overview")} + + , + + + , + + + {t("homepage.view_documentation")} + + , + ]} + /> + + + {t("learning.links.gettingStarted") && ( + + + + + {t("learning.labels.gettingStarted")} + + , + + + , + + + {t("homepage.view_documentation")} + + , + ]} + /> + + + )} + {t("learning.links.connecting") && ( + + + + + {t("learning.labels.connecting")} + + , + + + , + + + {t("homepage.view_documentation")} + + , + ]} + /> + + + )} + + + + + {t("learning.labels.topicOperatorUse")} + + , + + + , + + + {t("homepage.view_documentation")} + + , + ]} + /> + + + + + + + )} @@ -331,15 +349,18 @@ async function RecentTopics() { {t("homepage.empty_topics_description", { product: productName })} - { learningLinksEnabled() && - - - - {t("learning.labels.topicOperatorUse")} - - - - } + {isProductizedBuild && ( + + + + {t("learning.labels.topicOperatorUse")} + + + + )} ); } diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/(page)/ConnectedTopicsTable.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/(page)/ConnectedTopicsTable.tsx index 380f3aeec..bc4952f5d 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/(page)/ConnectedTopicsTable.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/(page)/ConnectedTopicsTable.tsx @@ -7,7 +7,7 @@ import { TopicsTableColumns, } from "@/components/TopicsTable/TopicsTable"; import { useRouter } from "@/navigation"; -import { readonly } from "@/utils/runmode"; +import { isProductizedBuild, isReadonly } from "@/utils/env"; import { useFilterParams } from "@/utils/useFilterParams"; import { useOptimistic, useTransition } from "react"; @@ -192,7 +192,8 @@ export function ConnectedTopicsTable({ }); }} includeHidden={includeHidden} - isReadOnly={readonly()} + isReadOnly={isReadonly} + showLearningLinks={isProductizedBuild} /> ); } diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/ConfigTable.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/ConfigTable.tsx index 7586a30d9..9943a30e2 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/ConfigTable.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/ConfigTable.tsx @@ -5,7 +5,7 @@ import { topicMutateErrorToFieldError } from "@/app/[locale]/kafka/[kafkaId]/top import { Number } from "@/components/Format/Number"; import { ResponsiveTableProps, TableView } from "@/components/Table"; import { usePathname, useRouter } from "@/navigation"; -import { readonly } from "@/utils/runmode"; +import { isReadonly } from "@/utils/env"; import { Button, FormGroup, @@ -321,7 +321,7 @@ export function ConfigTable({ }} renderCell={renderCell} renderActions={({ row: [name] }) => { - if (readonly()) { + if (isReadonly) { return <>; } diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/page.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/page.tsx index 4a3ef9950..7c6273299 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/page.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/configuration/page.tsx @@ -1,8 +1,8 @@ import { getTopic, updateTopic } from "@/api/topics/actions"; import { KafkaTopicParams } from "@/app/[locale]/kafka/[kafkaId]/topics/kafkaTopic.params"; -import { readonly } from "@/utils/runmode"; import { PageSection } from "@/libs/patternfly/react-core"; import { redirect } from "@/navigation"; +import { isReadonly } from "@/utils/env"; import { Suspense } from "react"; import { ConfigTable } from "./ConfigTable"; @@ -36,7 +36,7 @@ async function ConnectedTopicConfiguration({ async function onSaveProperty(name: string, value: string) { "use server"; - if (readonly()) { + if (isReadonly) { // silently ignore attempt to change a property value in read-only mode return true; } diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/create/page.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/create/page.tsx index b5291b8ab..947e999a8 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/create/page.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/create/page.tsx @@ -3,8 +3,8 @@ import { createTopic } from "@/api/topics/actions"; import { NewConfigMap } from "@/api/topics/schema"; import { KafkaParams } from "@/app/[locale]/kafka/[kafkaId]/kafka.params"; import { CreateTopic } from "@/app/[locale]/kafka/[kafkaId]/topics/create/CreateTopic"; -import { readonly } from "@/utils/runmode"; import { redirect } from "@/navigation"; +import { isReadonly } from "@/utils/env"; import { notFound } from "next/navigation"; export default async function CreateTopicPage({ @@ -12,7 +12,7 @@ export default async function CreateTopicPage({ }: { params: KafkaParams; }) { - if (readonly()) { + if (isReadonly) { redirect(`/kafka/${kafkaId}`); return; } diff --git a/ui/app/[locale]/layout.tsx b/ui/app/[locale]/layout.tsx index 9abec1609..8fe99ae4b 100644 --- a/ui/app/[locale]/layout.tsx +++ b/ui/app/[locale]/layout.tsx @@ -5,6 +5,7 @@ import NextIntlProvider from "@/app/[locale]/NextIntlProvider"; import { SessionRefresher } from "@/app/[locale]/SessionRefresher"; import { authOptions } from "@/utils/authOptions"; +import { isProductizedBuild } from "@/utils/env"; import { getServerSession } from "next-auth"; import { getTranslations } from "next-intl/server"; import { notFound } from "next/navigation"; @@ -30,7 +31,9 @@ export default async function Layout({ children, params: { locale } }: Props) { - {children} + + {children} + diff --git a/ui/components/TopicsTable/TopicsTable.stories.tsx b/ui/components/TopicsTable/TopicsTable.stories.tsx index 3fff32fdf..be3b9bb07 100644 --- a/ui/components/TopicsTable/TopicsTable.stories.tsx +++ b/ui/components/TopicsTable/TopicsTable.stories.tsx @@ -11,6 +11,7 @@ export default { topics: [], includeHidden: false, topicsCount: 0, + showLearningLinks: true, }, } as Meta; type Story = StoryObj; diff --git a/ui/components/TopicsTable/TopicsTable.tsx b/ui/components/TopicsTable/TopicsTable.tsx index d0e3969d6..656154a9f 100644 --- a/ui/components/TopicsTable/TopicsTable.tsx +++ b/ui/components/TopicsTable/TopicsTable.tsx @@ -78,6 +78,7 @@ export type TopicsTableProps = { filterId: string | undefined; filterName: string | undefined; filterStatus: TopicStatus[] | undefined; + showLearningLinks: boolean; onEditTopic: (topic: TopicList) => void; onDeleteTopic: (topic: TopicList) => void; onCreateTopic: () => void; @@ -111,6 +112,7 @@ export function TopicsTable({ onFilterIdChange, onFilterNameChange, onFilterStatusChange, + showLearningLinks, }: TopicsTableProps) { const t = useTranslations("topics"); return ( @@ -125,6 +127,7 @@ export function TopicsTable({ canCreate={isReadOnly === false} createHref={`${baseurl}/create`} onShowHiddenTopics={() => onInternalTopicsChange(true)} + showLearningLinks={showLearningLinks} /> } emptyStateNoResults={ @@ -156,7 +159,7 @@ export function TopicsTable({ > - + ); case "consumerGroups": return ( @@ -298,12 +301,12 @@ export function TopicsTable({ actions={ isReadOnly === false ? [ - { - label: t("create_topic"), - onClick: onCreateTopic, - isPrimary: true, - }, - ] + { + label: t("create_topic"), + onClick: onCreateTopic, + isPrimary: true, + }, + ] : undefined } tools={[ @@ -312,9 +315,7 @@ export function TopicsTable({ label={ <> {t("hide_internal_topics")}  - + diff --git a/ui/components/TopicsTable/components/EmptyStateNoTopics.stories.tsx b/ui/components/TopicsTable/components/EmptyStateNoTopics.stories.tsx index a720a94e3..99b3d5db9 100644 --- a/ui/components/TopicsTable/components/EmptyStateNoTopics.stories.tsx +++ b/ui/components/TopicsTable/components/EmptyStateNoTopics.stories.tsx @@ -13,5 +13,6 @@ export const CanCreate: Story = { args: { canCreate: true, createHref: "#/sample", + showLearningLinks: true, }, }; diff --git a/ui/components/TopicsTable/components/EmptyStateNoTopics.tsx b/ui/components/TopicsTable/components/EmptyStateNoTopics.tsx index fb20e808a..ac4281b6b 100644 --- a/ui/components/TopicsTable/components/EmptyStateNoTopics.tsx +++ b/ui/components/TopicsTable/components/EmptyStateNoTopics.tsx @@ -11,19 +11,19 @@ import { } from "@/libs/patternfly/react-core"; import { PlusCircleIcon } from "@/libs/patternfly/react-icons"; import { useTranslations } from "next-intl"; -import { enabled as learningLinksEnabled } from "@/utils/learningLinks"; export function EmptyStateNoTopics({ canCreate, createHref, onShowHiddenTopics, + showLearningLinks, }: { canCreate: boolean; createHref: string; onShowHiddenTopics: () => void; + showLearningLinks: boolean; }) { const t = useTranslations(); - const showLearningLinks = learningLinksEnabled(); return ( {showLearningLinks && ( - + {t("EmptyStateNoTopics.view_documentation")} diff --git a/ui/environment.d.ts b/ui/environment.d.ts index 888c6b207..b4832d4de 100644 --- a/ui/environment.d.ts +++ b/ui/environment.d.ts @@ -6,7 +6,7 @@ namespace NodeJS { KEYCLOAK_CLIENTID?: string; KEYCLOAK_CLIENTSECRET?: string; NEXT_PUBLIC_KEYCLOAK_URL?: string; - CONSOLE_LEARNING_LINKS_ENABLED?: "true" | "false"; + PRODUCTIZED_BUILD?: "true" | "false"; CONSOLE_METRICS_PROMETHEUS_URL?: string; LOG_LEVEL?: "fatal" | "error" | "warn" | "info" | "debug" | "trace"; CONSOLE_MODE?: "read-only" | "read-write"; diff --git a/ui/utils/env.ts b/ui/utils/env.ts index d278562d6..912877cba 100644 --- a/ui/utils/env.ts +++ b/ui/utils/env.ts @@ -1,12 +1,17 @@ - -export function readonly() { +export const isReadonly = (() => { if (process.env.CONSOLE_MODE !== "read-write") { return true; } - if (process.env.NEXT_PUBLIC_KEYCLOAK_URL && process.env.KEYCLOAK_CLIENTID && process.env.KEYCLOAK_CLIENTSECRET) { + if ( + process.env.NEXT_PUBLIC_KEYCLOAK_URL && + process.env.KEYCLOAK_CLIENTID && + process.env.KEYCLOAK_CLIENTSECRET + ) { return false; } return true; -} +})(); + +export const isProductizedBuild = process.env.PRODUCTIZED_BUILD === "true";