Skip to content

Commit 84779ba

Browse files
github-actions[bot]bbovenzipotiuk
authored
[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 ffa19c3) Co-authored-by: Brent Bovenzi <brent@astronomer.io> Closes: #73537 * Apply v3-3-test UI import grouping to the RenderedTemplates test Generated-by: Claude Opus 5 --------- Co-authored-by: Brent Bovenzi <brent@astronomer.io> Co-authored-by: Jarek Potiuk <jarek@potiuk.com>
1 parent 44c4ff2 commit 84779ba

2 files changed

Lines changed: 97 additions & 3 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*!
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import "@testing-library/jest-dom";
20+
import { render, screen } from "@testing-library/react";
21+
import { useParams } from "react-router-dom";
22+
import { beforeEach, describe, expect, it, vi } from "vitest";
23+
24+
import * as queries from "openapi/queries";
25+
import { Wrapper } from "src/utils/Wrapper";
26+
27+
import { RenderedTemplates } from "./RenderedTemplates";
28+
29+
vi.mock("openapi/queries");
30+
vi.mock("react-router-dom", async () => {
31+
const actual = await vi.importActual("react-router-dom");
32+
33+
return { ...actual, useParams: vi.fn() };
34+
});
35+
36+
const SQL = `select
37+
count(*) as total_rows,
38+
nvl(count_if(m.a is null), 0) as nulls_count
39+
from my_table as m`;
40+
41+
describe("RenderedTemplates", () => {
42+
beforeEach(() => {
43+
vi.clearAllMocks();
44+
vi.mocked(useParams).mockReturnValue({
45+
dagId: "test-dag",
46+
mapIndex: "-1",
47+
runId: "test-run",
48+
taskId: "test-task",
49+
});
50+
vi.mocked(queries.useTaskInstanceServiceGetMappedTaskInstance).mockReturnValue({
51+
data: { rendered_fields: { sql: SQL } },
52+
} as unknown as ReturnType<typeof queries.useTaskInstanceServiceGetMappedTaskInstance>);
53+
});
54+
55+
it("lays out highlighted lines as blocks so copied text keeps its tokens on one line", async () => {
56+
const { container } = render(
57+
<Wrapper>
58+
<RenderedTemplates />
59+
</Wrapper>,
60+
);
61+
62+
await screen.findByText("sql");
63+
64+
const lines = container.querySelectorAll("code > span");
65+
66+
expect(lines.length).toBeGreaterThan(0);
67+
lines.forEach((line) => {
68+
expect(line).toHaveStyle({ display: "block" });
69+
});
70+
});
71+
72+
it("overrides the theme's white-space so long lines actually wrap", async () => {
73+
const { container } = render(
74+
<Wrapper>
75+
<RenderedTemplates />
76+
</Wrapper>,
77+
);
78+
79+
await screen.findByText("sql");
80+
81+
expect(container.querySelector("code")).toHaveStyle({ whiteSpace: "pre-wrap" });
82+
});
83+
});

‎airflow-core/src/airflow/ui/src/pages/TaskInstance/RenderedTemplates.tsx‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,22 @@ const RenderedTemplatesContent = () => {
6161
opacity: 1,
6262
},
6363
}}
64+
position="relative"
6465
>
6566
<Box borderRadius="md" fontSize="sm" m={0} overflowX="auto" p={2}>
6667
<SyntaxHighlighter
68+
// The prism theme hard-codes `white-space: pre` on the code tag, which
69+
// silently defeats `wrapLongLines` unless we override it here.
70+
codeTagProps={{ style: { whiteSpace: "pre-wrap", wordBreak: "break-word" } }}
6771
language={language}
72+
lineNumberStyle={{ minWidth: "2.5em" }}
73+
// Combining `wrapLongLines` with `showLineNumbers` makes the library lay
74+
// each line out as a flex container, so every highlighted token becomes a
75+
// block-level flex item and copied text breaks after each one. The hanging
76+
// indent keeps wrapped lines clear of the gutter (2.5em number + 1em pad).
77+
lineProps={{
78+
style: { display: "block", paddingLeft: "3.5em", textIndent: "-3.5em" },
79+
}}
6880
PreTag="pre"
6981
showLineNumbers
7082
style={style}
@@ -75,11 +87,10 @@ const RenderedTemplatesContent = () => {
7587
</Box>
7688
<ClipboardRoot
7789
className="copy-button"
78-
float="right"
79-
marginTop="-3.5rem"
8090
opacity={0}
81-
position="sticky"
91+
position="absolute"
8292
right={4}
93+
top={6}
8394
transition="opacity 0.2s ease-in-out"
8495
value={renderedValue}
8596
>

0 commit comments

Comments
 (0)