This is an automated email from the ASF dual-hosted git repository.
jedcunningham pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 8c5e1b2ae18 Add plugin menu items to navbar (#47749)
8c5e1b2ae18 is described below
commit 8c5e1b2ae18ef43800a822c1ae88dc0eddbd8ffe
Author: Brent Bovenzi <[email protected]>
AuthorDate: Fri Mar 14 11:31:28 2025 -0400
Add plugin menu items to navbar (#47749)
---
airflow/ui/src/layouts/Nav/Nav.tsx | 2 +
airflow/ui/src/layouts/Nav/PluginMenus.tsx | 92 ++++++++++++++++++++++++++++++
2 files changed, 94 insertions(+)
diff --git a/airflow/ui/src/layouts/Nav/Nav.tsx
b/airflow/ui/src/layouts/Nav/Nav.tsx
index 44315743404..0132a76576c 100644
--- a/airflow/ui/src/layouts/Nav/Nav.tsx
+++ b/airflow/ui/src/layouts/Nav/Nav.tsx
@@ -27,6 +27,7 @@ import { AdminButton } from "./AdminButton";
import { BrowseButton } from "./BrowseButton";
import { DocsButton } from "./DocsButton";
import { NavButton } from "./NavButton";
+import { PluginMenus } from "./PluginMenus";
import { SecurityButton } from "./SecurityButton";
import { UserSettingsButton } from "./UserSettingsButton";
@@ -56,6 +57,7 @@ export const Nav = () => {
<BrowseButton />
<AdminButton />
<SecurityButton />
+ <PluginMenus />
</Flex>
<Flex flexDir="column">
<DocsButton />
diff --git a/airflow/ui/src/layouts/Nav/PluginMenus.tsx
b/airflow/ui/src/layouts/Nav/PluginMenus.tsx
new file mode 100644
index 00000000000..c3234336fba
--- /dev/null
+++ b/airflow/ui/src/layouts/Nav/PluginMenus.tsx
@@ -0,0 +1,92 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Link } from "@chakra-ui/react";
+import { FiChevronRight } from "react-icons/fi";
+import { LuPlug } from "react-icons/lu";
+
+import { usePluginServiceGetPlugins } from "openapi/queries";
+import type { AppBuilderMenuItemResponse } from "openapi/requests/types.gen";
+import { Menu } from "src/components/ui";
+
+import { NavButton } from "./NavButton";
+
+export const PluginMenus = () => {
+ const { data } = usePluginServiceGetPlugins();
+
+ const menuPlugins = data?.plugins.filter((plugin) =>
plugin.appbuilder_menu_items.length > 0);
+
+ if (data === undefined || menuPlugins === undefined) {
+ return undefined;
+ }
+
+ const categories: Record<string, Array<AppBuilderMenuItemResponse>> = {};
+ const buttons: Array<AppBuilderMenuItemResponse> = [];
+
+ menuPlugins.forEach((plugin) => {
+ plugin.appbuilder_menu_items.forEach((mi) => {
+ if (mi.category !== null && mi.category !== undefined) {
+ categories[mi.category] = [...(categories[mi.category] ?? []), mi];
+ } else {
+ buttons.push(mi);
+ }
+ });
+ });
+
+ if (!buttons.length && !Object.keys(categories).length) {
+ return undefined;
+ }
+
+ return (
+ <Menu.Root positioning={{ placement: "right" }}>
+ <Menu.Trigger>
+ <NavButton icon={<LuPlug />} title="Plugins" />
+ </Menu.Trigger>
+ <Menu.Content>
+ {buttons.map(({ href, name }) =>
+ href !== null && href !== undefined ? (
+ <Menu.Item asChild key={name} value={name}>
+ <Link aria-label={name} href={href} rel="noopener noreferrer"
target="_blank">
+ {name}
+ </Link>
+ </Menu.Item>
+ ) : undefined,
+ )}
+ {Object.entries(categories).map(([key, menuButtons]) => (
+ <Menu.Root key={key} positioning={{ placement: "right" }}>
+ <Menu.TriggerItem display="flex" justifyContent="space-between">
+ {key}
+ <FiChevronRight />
+ </Menu.TriggerItem>
+ <Menu.Content>
+ {menuButtons.map(({ href, name }) =>
+ href !== undefined && href !== null ? (
+ <Menu.Item asChild key={name} value={name}>
+ <Link aria-label={name} href={href} rel="noopener
noreferrer" target="_blank">
+ {name}
+ </Link>
+ </Menu.Item>
+ ) : undefined,
+ )}
+ </Menu.Content>
+ </Menu.Root>
+ ))}
+ </Menu.Content>
+ </Menu.Root>
+ );
+};