Skip to content
Closed
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
2 changes: 1 addition & 1 deletion airflow-core/src/airflow/ui/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { client } from "./queryClient";

const pluginRoute = {
element: <ExternalView />,
path: "plugin/:page",
path: "plugin/:page/*",
};

export const taskInstanceRoutes = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from airflow.configuration import conf
from airflow.exceptions import AirflowConfigException
from airflow.plugins_manager import AirflowPlugin
from airflow.providers.edge3.version_compat import AIRFLOW_V_3_0_PLUS, AIRFLOW_V_3_1_PLUS
from airflow.providers.edge3.version_compat import AIRFLOW_V_3_0_PLUS, AIRFLOW_V_3_1_0, AIRFLOW_V_3_1_PLUS
from airflow.utils.session import NEW_SESSION, provide_session

if TYPE_CHECKING:
Expand Down Expand Up @@ -234,25 +234,40 @@ class EdgeExecutorPlugin(AirflowPlugin):
if EDGE_EXECUTOR_ACTIVE and RUNNING_ON_APISERVER:
if AIRFLOW_V_3_1_PLUS:
fastapi_apps = [_get_api_endpoint()]
react_apps = [
{
"name": "Edge Worker",
"bundle_url": "/edge_worker/static/main.umd.cjs",
"destination": "nav",
"url_route": "edge_worker",
"category": "admin",
"icon": "/edge_worker/res/cloud-computer.svg",
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
},
{
"name": "Edge Worker Jobs",
"bundle_url": "/edge_worker/static/main.umd.cjs",
"url_route": "edge_jobs",
"category": "admin",
"icon": "/edge_worker/res/cloud-computer.svg",
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
},
]
if AIRFLOW_V_3_1_0:
# Airflow 3.1.0 does not route sub-pages, need to register both pages separately
react_apps = [
{
"name": "Edge Worker",
"bundle_url": "/edge_worker/static/main.umd.cjs",
"destination": "nav",
"url_route": "edge_worker",
"category": "admin",
"icon": "/edge_worker/res/cloud-computer.svg",
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
},
{
"name": "Edge Worker Jobs",
"bundle_url": "/edge_worker/static/main.umd.cjs",
"destination": "nav",
"url_route": "edge_jobs",
"category": "admin",
"icon": "/edge_worker/res/cloud-computer.svg",
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
},
]
else:
react_apps = [
{
"name": "Edge Executor",
"bundle_url": "/edge_worker/static/main.umd.cjs",
"destination": "nav",
"url_route": "edge_executor",
"category": "admin",
"icon": "/edge_worker/res/cloud-computer.svg",
"icon_dark_mode": "/edge_worker/res/cloud-computer-dark.svg",
},
]
external_views = [
{
"name": "Edge Worker API docs",
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*!
* 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 as ExternalLink } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";

type Props = {
readonly children: React.ReactNode;
readonly inPlugin?: boolean;
readonly to: string;
};

export const Link = ({ children, inPlugin, to }: Props) => {
// Need to check whether ReactRouterDOM is available globally
// because in Airflow 3.1.0, the plugin system was missing this.
if (inPlugin || (globalThis as Record<string, unknown>).ReactRouterDOM) {
return <RouterLink to={to}>{children}</RouterLink>;
// TODO need to fix internal URL in plugin... in 3.1.0
} else {
// Fallback in 3.1.0, can be removed if we drop support for it
return <ExternalLink href={`..${to}`}>{children}</ExternalLink>;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,51 @@
* under the License.
*/
import { Box } from "@chakra-ui/react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";

import { JobsPage } from "src/pages/JobsPage";
import { WorkerPage } from "src/pages/WorkerPage";

import { NavTabs } from "./NavTabs";

export const EdgeLayout = () => {
const tabs = [
{ label: "Edge Worker", value: "plugin/edge_worker" },
{ label: "Edge Jobs", value: "plugin/edge_jobs" },
];
// Need to check whether ReactRouterDOM is available globally
// because in Airflow 3.1.0, the plugin system was missing this.
if ((globalThis as Record<string, unknown>).ReactRouterDOM) {
const tabs = [
{ label: "Edge Worker", value: "worker" },
{ label: "Edge Jobs", value: "jobs" },
];

return (
<Box p={2} /* Compensate for parent padding from ExternalView */>
<BrowserRouter>
return (
<Box p={2} /* Compensate for parent padding from ExternalView */>
<NavTabs tabs={tabs} />
<Routes>
<Route path="plugin/edge_worker" element={<WorkerPage />} />
<Route path="plugin/edge_jobs" element={<JobsPage />} />
<Route index element={<Navigate to="worker" replace />} />
<Route path="worker" element={<WorkerPage />} />
<Route path="jobs" element={<JobsPage />} />
</Routes>
</BrowserRouter>
</Box>
);
</Box>
);
} else {
// Fallback in 3.1.0, can be removed if we drop support for it
console.warn("Location Pathname:", globalThis.location.pathname);

const tabs = [
{ label: "Edge Worker", value: "plugin/edge_worker" },
{ label: "Edge Jobs", value: "plugin/edge_jobs" },
];

return (
<Box p={2} /* Compensate for parent padding from ExternalView */>
<BrowserRouter>
<NavTabs tabs={tabs} />
<Routes>
<Route path="plugin/edge_worker" element={<WorkerPage />} />
<Route path="plugin/edge_jobs" element={<JobsPage />} />
</Routes>
</BrowserRouter>
</Box>
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,32 +32,31 @@ export const NavTabs = ({ tabs }: Props) => {

return (
<Flex alignItems="center" borderBottomWidth={1} mb={2} ref={containerRef}>
{tabs.map(({ icon, label, value }) => (
<NavLink
end
key={value}
title={label}
to={{
pathname: value,
}}
>
{({ isActive }) => (
<Center
borderBottomColor="border.info"
borderBottomWidth={isActive ? 3 : 0}
color={isActive ? "fg" : "fg.muted"}
fontWeight="bold"
height="40px"
mb="-2px" // Show the border on top of its parent's border
pb={isActive ? 0 : "3px"}
px={4}
transition="all 0.2s ease"
>
{containerWidth > 600 || !icon ? label : icon}
</Center>
)}
</NavLink>
))}
{tabs.map(({ icon, label, value }) => {
// Need to check whether ReactRouterDOM is available globally
// because in Airflow 3.1.0, the plugin system was missing this.
const navTo = (globalThis as Record<string, unknown>).ReactRouterDOM ? value : { pathname: value };

return (
<NavLink end key={value} title={label} to={navTo}>
{({ isActive }) => (
<Center
borderBottomColor="border.info"
borderBottomWidth={isActive ? 3 : 0}
color={isActive ? "fg" : "fg.muted"}
fontWeight="bold"
height="40px"
mb="-2px" // Show the border on top of its parent's border
pb={isActive ? 0 : "3px"}
px={4}
transition="all 0.2s ease"
>
{containerWidth > 600 || !icon ? label : icon}
</Center>
)}
</NavLink>
);
})}
</Flex>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Box, Link, Table, Text } from "@chakra-ui/react";
import { Box, Table, Text } from "@chakra-ui/react";
import { useUiServiceJobs } from "openapi/queries";
import { Link as RouterLink } from "react-router-dom";
import TimeAgo from "react-timeago";

import { ErrorAlert } from "src/components/ErrorAlert";
import { Link } from "src/components/InternalLink";
import { StateBadge } from "src/components/StateBadge";
import { autoRefreshInterval } from "src/utils";

Expand Down Expand Up @@ -60,22 +60,21 @@ export const JobsPage = () => {
key={`${job.dag_id}.${job.run_id}.${job.task_id}.${job.map_index}.${job.try_number}`}
>
<Table.Cell>
{/* TODO Check why <Link to={`/dags/${job.dag_id}`}> is not working via react-router-dom! */}
<Link href={`../dags/${job.dag_id}`}>{job.dag_id}</Link>
<Link to={`/dags/${job.dag_id}`}>{job.dag_id}</Link>
</Table.Cell>
<Table.Cell>
<Link href={`../dags/${job.dag_id}/runs/${job.run_id}`}>{job.run_id}</Link>
<Link to={`/dags/${job.dag_id}/runs/${job.run_id}`}>{job.run_id}</Link>
</Table.Cell>
<Table.Cell>
{job.map_index >= 0 ? (
<Link
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}/mapped/${job.map_index}?try_number=${job.try_number}`}
to={`/dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}/mapped/${job.map_index}?try_number=${job.try_number}`}
>
{job.task_id}
</Link>
) : (
<Link
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}?try_number=${job.try_number}`}
to={`/dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}?try_number=${job.try_number}`}
>
{job.task_id}
</Link>
Expand All @@ -91,7 +90,9 @@ export const JobsPage = () => {
{job.queued_dttm ? <TimeAgo date={job.queued_dttm} live={false} /> : undefined}
</Table.Cell>
<Table.Cell>
<RouterLink to={`/plugin/edge_worker#${job.edge_worker}`}>{job.edge_worker}</RouterLink>
<Link inPlugin={true} to={`../worker#${job.edge_worker}`}>
{job.edge_worker}
</Link>
</Table.Cell>
<Table.Cell>
{job.last_update ? <TimeAgo date={job.last_update} live={false} /> : undefined}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export default defineConfig(({ command }) => {
globals: {
react: "React",
"react-dom": "ReactDOM",
"react-router-dom": "ReactRouterDOM",
"react/jsx-runtime": "ReactJSXRuntime",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ def get_base_airflow_version_tuple() -> tuple[int, int, int]:


AIRFLOW_V_3_0_PLUS = get_base_airflow_version_tuple() >= (3, 0, 0)
AIRFLOW_V_3_1_0 = get_base_airflow_version_tuple() == (3, 1, 0)
AIRFLOW_V_3_1_PLUS = get_base_airflow_version_tuple() >= (3, 1, 0)
2 changes: 1 addition & 1 deletion providers/edge3/www-hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
719ccadaebac924011168dd33e1a46f7128362bfdd487ac43cefba7c365d717b
f4a68b4fd7f7895da4051990101a169b98bd404ff06048adb1675846381dacf0
Loading