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/dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<html lang="en" style="height: 100%">
<head>
<meta charset="UTF-8" />
<base href="{{ backend_server_base_url }}" />
<base href="/" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would break a lot of things for instance.

<link rel="icon" type="image/png" href="http://localhost:5173/pin_32.png" />
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module">
Expand Down
8 changes: 4 additions & 4 deletions airflow-core/src/airflow/ui/index.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<!doctype html>
<html style="height: 100%">
<html lang="en" style="height: 100%">
<head>
<meta charset="UTF-8" />
<base href="{{ backend_server_base_url }}" />
<link rel="icon" type="image/png" href="/static/pin_32.png" />
<base href="./" />
<link rel="icon" type="image/png" href="./static/pin_32.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Airflow</title>
<script type="module" crossorigin src="./static/assets/index-mNLOr9BH.js"></script>
</head>
<body style="height: 100%">
<div id="root" style="height: 100%"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
3 changes: 2 additions & 1 deletion airflow-core/src/airflow/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,6 @@
"esbuild",
"msw"
]
}
},
"packageManager": "pnpm@10.16.1+sha512.0e155aa2629db8672b49e8475da6226aa4bdea85fdcdfdc15350874946d4f3c91faaf64cbdc4a5d1ab8002f473d5c3fcedcd197989cf0390f9badd3c04678706"
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { ConnectionResponse, ConnectionBody } from "openapi/requests/types.
import ActionButton from "src/components/ui/ActionButton";
import { useConfig } from "src/queries/useConfig";
import { useTestConnection } from "src/queries/useTestConnection";
import { Tooltip } from "src/components/ui";

type TestConnectionOption = "Disabled" | "Enabled" | "Hidden";
type Props = {
Expand All @@ -37,6 +38,7 @@ const disconnectedIcon = <FiWifiOff color="red" />;
const TestConnectionButton = ({ connection }: Props) => {
const { t: translate } = useTranslation("admin");
const [icon, setIcon] = useState(defaultIcon);
const [message, setMessage] = useState<string | undefined>(undefined);
const testConnection = useConfig("test_connection");
let option: TestConnectionOption;

Expand All @@ -63,28 +65,59 @@ const TestConnectionButton = ({ connection }: Props) => {
const { isPending, mutate } = useTestConnection((result) => {
if (result === undefined) {
setIcon(defaultIcon);
setMessage(undefined);
} else if (result === true) {
setIcon(connectedIcon);
// Message will be set by the hook's onSuccess callback
} else {
setIcon(disconnectedIcon);
// Message will be set by the hook's onSuccess callback
}
}, (newMessage) => {
setMessage(newMessage);
});

const tooltipContent = message ? message : translate("connections.test");

return (
<ActionButton
actionName={
option === "Enabled" ? translate("connections.test") : translate("connections.testDisabled")
}
disabled={option === "Disabled"}
display={option === "Hidden" ? "none" : "flex"}
icon={icon}
loading={isPending}
onClick={() => {
mutate({ requestBody: connectionBody });
}}
text={translate("connections.test")}
withText={false}
/>
<div style={{ position: 'relative' }}>
<Tooltip content={tooltipContent}>
<ActionButton
actionName={
option === "Enabled" ? translate("connections.test") : translate("connections.testDisabled")
}
disabled={option === "Disabled"}
display={option === "Hidden" ? "none" : "flex"}
icon={icon}
loading={isPending}
onClick={() => {
// Reset message when starting a new test
setMessage(undefined);
mutate({ requestBody: connectionBody });
}}
text={translate("connections.test")}
withText={false}
/>
</Tooltip>
{message && (
<div style={{
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(0, 0, 0, 0.8)',
color: 'white',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
whiteSpace: 'nowrap',
zIndex: 1000,
marginTop: '4px',
}}>
{message}
</div>
)}
</div>
);
};

Expand Down
40 changes: 38 additions & 2 deletions airflow-core/src/airflow/ui/src/queries/useTestConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,54 @@ import type { Dispatch, SetStateAction } from "react";
import { useConnectionServiceTestConnection, useConnectionServiceGetConnectionsKey } from "openapi/queries";
import type { ConnectionTestResponse } from "openapi/requests/types.gen";

export const useTestConnection = (setConnected: Dispatch<SetStateAction<boolean | undefined>>) => {
export const useTestConnection = (
setConnected: Dispatch<SetStateAction<boolean | undefined>>,
setMessage: Dispatch<SetStateAction<string | undefined>>
) => {
const queryClient = useQueryClient();

const onSuccess = async (res: ConnectionTestResponse) => {
await queryClient.invalidateQueries({
queryKey: [useConnectionServiceGetConnectionsKey],
});
setConnected(res.status);
setMessage(res.message);
};

const onError = () => {
const onError = (error: any) => {
setConnected(false);

// Extract error message from different possible error structures
let errorMessage = "Connection test failed";

// Try different error message extraction strategies
if (error?.body?.detail) {
errorMessage = error.body.detail;
} else if (error?.body?.message) {
errorMessage = error.body.message;
} else if (error?.response?.data?.detail) {
errorMessage = error.response.data.detail;
} else if (error?.response?.data?.message) {
errorMessage = error.response.data.message;
} else if (error?.message) {
errorMessage = error.message;
} else if (typeof error?.body === 'string') {
errorMessage = error.body;
} else if (error?.body && typeof error.body === 'object') {
// Try to extract message from nested error object
const bodyStr = JSON.stringify(error.body);
if (bodyStr.includes('detail')) {
try {
const parsed = JSON.parse(bodyStr);
errorMessage = parsed.detail || parsed.message || errorMessage;
} catch (e) {
// If parsing fails, use the string representation
errorMessage = bodyStr;
}
}
}

setMessage(errorMessage);
};

return useConnectionServiceTestConnection({
Expand Down
27 changes: 27 additions & 0 deletions airflow-core/src/airflow/ui/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,33 @@ export default defineConfig({
resolve: { alias: { openapi: "/openapi-gen", src: "/src" } },
server: {
cors: true, // Only used by the dev server.
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
'/ui': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
'/config': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
'/auth': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
'/login': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
},
},
test: {
coverage: {
Expand Down
97 changes: 97 additions & 0 deletions setup_complete_airflow.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/bin/bash

echo "🚀 Setting up complete Airflow environment for PR #55680..."

# Set environment variables
export PATH="/Users/alphaskynet/Library/Python/3.9/bin:$PATH"
Comment on lines +1 to +6
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that script for instance? I guess a local helper script, generated with LLM tools?

We shouldn't commit this, please double check before submitting a PR.

export AIRFLOW_HOME=/tmp/airflow_test
export AIRFLOW__CORE__TEST_CONNECTION=Enabled
export AIRFLOW__CORE__LOAD_EXAMPLES=False
export AIRFLOW__CORE__EXECUTOR=SequentialExecutor
export AIRFLOW__CORE__DAGS_FOLDER=/tmp/airflow_test/dags
export AIRFLOW__CORE__SQL_ALCHEMY_CONN=sqlite:////tmp/airflow_test/airflow.db

# Clean up previous runs
echo "🧹 Cleaning up previous runs..."
rm -rf /tmp/airflow_test
mkdir -p /tmp/airflow_test/dags

# Install HTTP provider
echo "📦 Installing HTTP provider..."
pip install -U apache-airflow-providers-http

# Initialize Airflow database
echo "🗄️ Initializing Airflow database..."
cd /Users/alphaskynet/Downloads/Github\ Contributions/airflow
airflow db init

# Create test connections
echo "🔗 Creating test connections..."
python3 -c "
import os
import sys
from airflow.models import Connection
from airflow.utils.session import create_session

def create_test_connections():
with create_session() as session:
# Delete existing test connections
session.query(Connection).filter(Connection.conn_id.in_(['test_connection_success', 'test_connection_failure'])).delete()

# Create success connection
success_conn = Connection(
conn_id='test_connection_success',
conn_type='http',
host='https://httpbin.org/anything',
port=443,
schema='https',
)

# Create failure connection
failure_conn = Connection(
conn_id='test_connection_failure',
conn_type='http',
host='https://invalid.invalid',
port=443,
schema='https',
)

session.add_all([success_conn, failure_conn])
session.commit()
print('✅ Test connections created successfully!')
print(f' - {success_conn.conn_id}: {success_conn.host} (should work)')
print(f' - {failure_conn.conn_id}: {failure_conn.host} (should fail)')

create_test_connections()
"

# Start Airflow backend
echo "🚀 Starting Airflow backend..."
airflow standalone &
AIRFLOW_PID=$!

# Wait for Airflow to start
echo "⏳ Waiting for Airflow to start..."
sleep 30

# Get the generated password
echo "🔑 Getting login credentials..."
if [ -f "/tmp/airflow_test/simple_auth_manager_passwords.json.generated" ]; then
PASSWORD=$(cat /tmp/airflow_test/simple_auth_manager_passwords.json.generated | grep -o '"admin": "[^"]*"' | cut -d'"' -f4)
echo "✅ Airflow is ready!"
echo "🌐 Backend UI: http://localhost:8080/"
echo "👤 Username: admin"
echo "🔑 Password: $PASSWORD"
echo ""
echo "📸 Ready to test your PR #55680!"
echo "1. Go to http://localhost:8080/"
echo "2. Login with admin / $PASSWORD"
echo "3. Navigate to Admin → Connections"
echo "4. Test the connections and take screenshots"
else
echo "❌ Failed to get password. Check Airflow logs."
fi

echo "✅ Complete setup finished!"
echo "Backend PID: $AIRFLOW_PID"

Loading