This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-3-test by this push:
new 84779bacd89 [v3-3-test] UI: Fix copied SQL from Rendered Templates
breaking at every token (#73633) (#73640)
84779bacd89 is described below
commit 84779bacd895edf1e1bcff2697f34d9954433763
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Sep 28 16:58:13 2026 +0200
[v3-3-test] UI: Fix copied SQL from Rendered Templates breaking at every
token (#73633) (#73640)
* [v3-3-test] UI: Fix copied SQL from Rendered Templates breaking at every
token (#73633)
Combining `wrapLongLines` with `showLineNumbers` makes
react-syntax-highlighter
lay each line out as a flex container, so every highlighted token becomes a
block-level box and the browser emits a newline after each one when it
serializes a selection. Selecting a rendered SQL field and copying it
therefore
produced something that no longer parsed. The copy button kept working
because
it copies the raw string rather than the DOM, which is why this went
unnoticed.
The prism theme also hard-codes `white-space: pre` on the code tag and the
library merges that over its own `pre-wrap`, so `wrapLongLines` has never
actually wrapped anything.
The copy button was pulled back over the code block with a fixed negative
margin, so where it landed depended on the block's height.
(cherry picked from commit ffa19c31a67f86dadf23709eb10cfaacd2669e63)
Co-authored-by: Brent Bovenzi <[email protected]>
Closes: #73537
* Apply v3-3-test UI import grouping to the RenderedTemplates test
Generated-by: Claude Opus 5
---------
Co-authored-by: Brent Bovenzi <[email protected]>
Co-authored-by: Jarek Potiuk <[email protected]>
---
.../pages/TaskInstance/RenderedTemplates.test.tsx | 83 ++++++++++++++++++++++
.../src/pages/TaskInstance/RenderedTemplates.tsx | 17 ++++-
2 files changed, 97 insertions(+), 3 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.test.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.test.tsx
new file mode 100644
index 00000000000..3bb6b141d25
--- /dev/null
+++
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.test.tsx
@@ -0,0 +1,83 @@
+/*!
+ * 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 "@testing-library/jest-dom";
+import { render, screen } from "@testing-library/react";
+import { useParams } from "react-router-dom";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+import * as queries from "openapi/queries";
+import { Wrapper } from "src/utils/Wrapper";
+
+import { RenderedTemplates } from "./RenderedTemplates";
+
+vi.mock("openapi/queries");
+vi.mock("react-router-dom", async () => {
+ const actual = await vi.importActual("react-router-dom");
+
+ return { ...actual, useParams: vi.fn() };
+});
+
+const SQL = `select
+ count(*) as total_rows,
+ nvl(count_if(m.a is null), 0) as nulls_count
+from my_table as m`;
+
+describe("RenderedTemplates", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(useParams).mockReturnValue({
+ dagId: "test-dag",
+ mapIndex: "-1",
+ runId: "test-run",
+ taskId: "test-task",
+ });
+
vi.mocked(queries.useTaskInstanceServiceGetMappedTaskInstance).mockReturnValue({
+ data: { rendered_fields: { sql: SQL } },
+ } as unknown as ReturnType<typeof
queries.useTaskInstanceServiceGetMappedTaskInstance>);
+ });
+
+ it("lays out highlighted lines as blocks so copied text keeps its tokens on
one line", async () => {
+ const { container } = render(
+ <Wrapper>
+ <RenderedTemplates />
+ </Wrapper>,
+ );
+
+ await screen.findByText("sql");
+
+ const lines = container.querySelectorAll("code > span");
+
+ expect(lines.length).toBeGreaterThan(0);
+ lines.forEach((line) => {
+ expect(line).toHaveStyle({ display: "block" });
+ });
+ });
+
+ it("overrides the theme's white-space so long lines actually wrap", async ()
=> {
+ const { container } = render(
+ <Wrapper>
+ <RenderedTemplates />
+ </Wrapper>,
+ );
+
+ await screen.findByText("sql");
+
+ expect(container.querySelector("code")).toHaveStyle({ whiteSpace:
"pre-wrap" });
+ });
+});
diff --git
a/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
index de2c6313699..d9a6720e495 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx
@@ -61,10 +61,22 @@ const RenderedTemplatesContent = () => {
opacity: 1,
},
}}
+ position="relative"
>
<Box borderRadius="md" fontSize="sm" m={0}
overflowX="auto" p={2}>
<SyntaxHighlighter
+ // The prism theme hard-codes `white-space: pre` on
the code tag, which
+ // silently defeats `wrapLongLines` unless we
override it here.
+ codeTagProps={{ style: { whiteSpace: "pre-wrap",
wordBreak: "break-word" } }}
language={language}
+ lineNumberStyle={{ minWidth: "2.5em" }}
+ // Combining `wrapLongLines` with `showLineNumbers`
makes the library lay
+ // each line out as a flex container, so every
highlighted token becomes a
+ // block-level flex item and copied text breaks
after each one. The hanging
+ // indent keeps wrapped lines clear of the gutter
(2.5em number + 1em pad).
+ lineProps={{
+ style: { display: "block", paddingLeft: "3.5em",
textIndent: "-3.5em" },
+ }}
PreTag="pre"
showLineNumbers
style={style}
@@ -75,11 +87,10 @@ const RenderedTemplatesContent = () => {
</Box>
<ClipboardRoot
className="copy-button"
- float="right"
- marginTop="-3.5rem"
opacity={0}
- position="sticky"
+ position="absolute"
right={4}
+ top={6}
transition="opacity 0.2s ease-in-out"
value={renderedValue}
>