This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun 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 f01c69a15ab fix(UI): show 403 error when unauthorized user reparses 
DAG (#61560)
f01c69a15ab is described below

commit f01c69a15ab1a5d32d0917a793d438857c6b200b
Author: Liam <[email protected]>
AuthorDate: Wed Mar 11 06:27:35 2026 -0400

    fix(UI): show 403 error when unauthorized user reparses DAG (#61560)
    
    * fix(UI): show permission error when unauthorized user tries to reparse DAG
    
    When a Viewer or Author without edit permission tries to reparse a DAG,
    the API returns 403 but the UI shows a generic "Dag parsing request
    failed" message. This checks the error status and shows an appropriate
    "Access Denied" toast instead.
    
    Closes #61459
    
    Co-authored-by: Cursor <[email protected]>
    
    * Generalize 403 permission toast handling in UI
    
    Move the forbidden toast copy to common translations and route DAG reparse 
mutation errors through a shared error toaster helper so 403 responses 
consistently show a reusable access-denied message.
    
    Made-with: Cursor
    
    * Small adjustments
    
    ---------
    
    Co-authored-by: Cursor <[email protected]>
    Co-authored-by: pierrejeambrun <[email protected]>
---
 .../airflow/ui/public/i18n/locales/en/common.json  |  4 +++
 .../ui/src/components/ui/createErrorToaster.ts     | 40 ++++++++++++++++++++++
 .../src/airflow/ui/src/queries/useDagParsing.ts    | 15 ++++----
 3 files changed, 50 insertions(+), 9 deletions(-)

diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json 
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index 745193fd071..89c5d48e816 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -313,6 +313,10 @@
         "title": "Delete {{resourceName}} Request Submitted"
       }
     },
+    "forbidden": {
+      "description": "You do not have permission to perform this action.",
+      "title": "Access Denied"
+    },
     "import": {
       "error": "Import {{resourceName}} Request Failed",
       "success": {
diff --git 
a/airflow-core/src/airflow/ui/src/components/ui/createErrorToaster.ts 
b/airflow-core/src/airflow/ui/src/components/ui/createErrorToaster.ts
new file mode 100644
index 00000000000..c59cabbf3b6
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/ui/createErrorToaster.ts
@@ -0,0 +1,40 @@
+/*!
+ * 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 type { TFunction } from "i18next";
+
+import type { ExpandedApiError } from "src/components/ErrorAlert";
+import { toaster } from "src/components/ui";
+
+type ErrorToastMessage = {
+  readonly description: string;
+  readonly title: string;
+};
+
+export const createErrorToaster =
+  (translate: TFunction, fallbackMessage: ErrorToastMessage) => (error: 
unknown) => {
+    const isForbidden = (error as ExpandedApiError).status === 403;
+
+    toaster.create({
+      description: isForbidden
+        ? translate("toaster.forbidden.description", { ns: "common" })
+        : fallbackMessage.description,
+      title: isForbidden ? translate("toaster.forbidden.title", { ns: "common" 
}) : fallbackMessage.title,
+      type: "error",
+    });
+  };
diff --git a/airflow-core/src/airflow/ui/src/queries/useDagParsing.ts 
b/airflow-core/src/airflow/ui/src/queries/useDagParsing.ts
index 914799dfc54..2fc8c43c0e3 100644
--- a/airflow-core/src/airflow/ui/src/queries/useDagParsing.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useDagParsing.ts
@@ -25,18 +25,15 @@ import {
   UseDagSourceServiceGetDagSourceKeyFn,
 } from "openapi/queries";
 import { toaster } from "src/components/ui";
+import { createErrorToaster } from "src/components/ui/createErrorToaster";
 
 export const useDagParsing = ({ dagId }: { readonly dagId: string }) => {
   const queryClient = useQueryClient();
-  const { t: translate } = useTranslation("dag");
-
-  const onError = () => {
-    toaster.create({
-      description: translate("parse.toaster.error.description"),
-      title: translate("parse.toaster.error.title"),
-      type: "error",
-    });
-  };
+  const { t: translate } = useTranslation(["dag", "common"]);
+  const onError = createErrorToaster(translate, {
+    description: translate("parse.toaster.error.description"),
+    title: translate("parse.toaster.error.title"),
+  });
 
   const onSuccess = async () => {
     await queryClient.invalidateQueries({

Reply via email to