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

voidmatcha pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 9799b263cb [ZEPPELIN-6762] Fix truncated output alerts in the new UI
9799b263cb is described below

commit 9799b263cb1b9ca5aa2bd0998739e284ec9d5daf
Author: Minho Jang <[email protected]>
AuthorDate: Tue Oct 27 20:40:06 2026 +0900

    [ZEPPELIN-6762] Fix truncated output alerts in the new UI
    
    ### What is this PR for?
    Fix the unstyled “Output is truncated” alert and its non-functional close 
button in the new UI.
    
    The alert HTML generated by the server relies on Bootstrap styles and 
dismissal behavior, which the new UI does not provide. This PR adds light/dark 
theme styling and close-button handling for both Angular and React.
    
    ### What type of PR is it?
    Bug Fix
    
    ### What is the Jira issue?
    * https://issues.apache.org/jira/browse/ZEPPELIN-6762
    
    ### How should this be tested?
    
    Automated tests
    - Run the following commands from the repository root:
    
    ```bash
    cd zeppelin-web-angular
    npm run test:shell -- 
src/app/pages/workspace/share/result/result.component.spec.ts --maxWorkers=1
    npm --prefix projects/zeppelin-react test -- 
src/components/renderers/HTMLRenderer.spec.tsx --maxWorkers=1
    ```
    
    Manual verification
    
    1. Create a notebook using the sh interpreter and run:
      ```sh
    %sh
    for i in $(seq 1 20000); do echo "line_$i"; done
      ```
    2. Verify that the “Output is truncated” alert below the output has a 
background, border, and close button.
    3. Click × and verify that only the alert disappears; the output text 
should remain.
    4. Run the paragraph again. Verify that the alert reappears and can also be 
dismissed by focusing its close button and pressing Enter.
    5. Repeat in both light and dark themes.
    6. Open the same paragraph in the React Published view and repeat:
      ```text
    /#/notebook/<noteId>/paragraph/<paragraphId>?react=true
      ```
    
    To verify the row-limit alert with a PostgreSQL JDBC interpreter, set 
zeppelin.jdbc.maxResult to 1000 and run:
    
    ```sql
    %jdbc
    SELECT generate_series(1, 2000);
    ```
    
    Verify that the row-limit alert following the table is styled correctly and 
can be dismissed.
    
    ### Screenshots (if appropriate)
    **Angular dark theme**
    
    <img width="1280" height="420" alt="zeppelin-6762-angular-dark" 
src="https://github.com/user-attachments/assets/22343a9e-9aa1-47d8-86a6-6320b3f1d675";
 />
    
    **Angular light theme**
    
    <img width="1280" height="420" alt="zeppelin-6762-angular-light" 
src="https://github.com/user-attachments/assets/3498ac84-d76f-4ffe-a831-12d9c7e6e5e6";
 />
    
    **React dark theme**
    
    <img width="1600" height="525" alt="zeppelin-6762-react-dark" 
src="https://github.com/user-attachments/assets/d7f85139-e1bf-425e-b189-ffba5de510c5";
 />
    
    **React light theme**
    
    <img width="1280" height="420" alt="zeppelin-6762-react-light" 
src="https://github.com/user-attachments/assets/243408ef-3ae0-411f-83e0-5839d123deff";
 />
    
    ### Questions:
    * Does the license files need to update? No.
    * Is there breaking changes for older versions? No.
    * Does this needs documentation? No.
    
    
    Closes #5591 from miinhho/ZEPPELIN-6762-truncated-output-alert.
    
    Signed-off-by: YONGJAE LEE <[email protected]>
---
 .../src/components/renderers/HTMLRenderer.spec.tsx | 20 +++++++-
 .../src/components/renderers/HTMLRenderer.tsx      |  8 +++
 .../workspace/share/result/result.component.html   |  1 +
 .../share/result/result.component.spec.ts          | 23 +++++++++
 .../workspace/share/result/result.component.ts     | 14 ++++++
 zeppelin-web-angular/src/styles.less               |  1 +
 zeppelin-web-angular/src/styles/result-alert.less  | 57 ++++++++++++++++++++++
 7 files changed, 123 insertions(+), 1 deletion(-)

diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.spec.tsx
 
b/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.spec.tsx
index 0911a9f15b..71ce1fb4f8 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.spec.tsx
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.spec.tsx
@@ -10,7 +10,7 @@
  * limitations under the License.
  */
 
-import { render, screen, waitFor } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
 import { describe, expect, it } from 'vitest';
 import { HTMLRenderer } from './HTMLRenderer';
 
@@ -76,6 +76,24 @@ describe('HTMLRenderer', () => {
     expect(screen.getByText('second')).toBeTruthy();
   });
 
+  it('dismisses only result warnings, including after the HTML is replaced', 
() => {
+    const html =
+      '<div class="result-alert alert-warning" role="alert"><button 
class="close" data-dismiss="alert" 
aria-label="Close"><span>&times;</span></button>102400 bytes</div><button 
class="close" data-dismiss="alert">Ordinary output</button>';
+    const { rerender } = render(<HTMLRenderer html={html} />);
+
+    fireEvent.click(screen.getByRole('button', { name: 'Ordinary output' }));
+    expect(screen.getByRole('alert').textContent).toContain('102400 bytes');
+    fireEvent.click(screen.getByRole('button', { name: 'Close' }).firstChild!);
+    rerender(<HTMLRenderer html={html} />);
+    expect(screen.queryByRole('alert')).toBeNull();
+    expect(screen.getByRole('button', { name: 'Ordinary output' 
})).toBeTruthy();
+
+    rerender(<HTMLRenderer html={html.replace('102400 bytes', '1000 rows')} 
/>);
+    expect(screen.getByRole('alert').textContent).toContain('1000 rows');
+    fireEvent.click(screen.getByRole('button', { name: 'Close' }));
+    expect(screen.queryByRole('alert')).toBeNull();
+  });
+
   it('renders nothing visible for empty html', () => {
     const { container } = render(<HTMLRenderer html="" />);
 
diff --git 
a/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.tsx
 
b/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.tsx
index b01aeb12f9..50a7cac7b8 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.tsx
+++ 
b/zeppelin-web-angular/projects/zeppelin-react/src/components/renderers/HTMLRenderer.tsx
@@ -28,6 +28,14 @@ export const HTMLRenderer = ({ html }: HTMLRendererProps) => 
{
       // to the DOM to execute them.
       container.innerHTML = html;
 
+      // ResultMessages HTML needs dismissal without loading Bootstrap's alert 
plugin.
+      container
+        .querySelectorAll<HTMLButtonElement>('.result-alert > 
button.close[data-dismiss="alert"]')
+        .forEach(button => {
+          const alert = button.parentElement!;
+          button.addEventListener('click', () => alert.remove(), { once: true 
});
+        });
+
       // Highlight code blocks (matches Angular: result.component.ts 
renderHTML)
       const codeEle = container.querySelector<HTMLElement>('pre code');
       if (codeEle) {
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
index 0d0804e814..8b166d3b75 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.html
@@ -95,6 +95,7 @@
         zeppelin-run-scripts
         [scriptsContent]="innerHTML"
         [innerHTML]="innerHTML"
+        (click)="dismissResultAlert($event)"
       ></div>
     }
     @case (datasetType.TEXT) {
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.spec.ts
 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.spec.ts
index 67da4755a4..a15a772476 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.spec.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.spec.ts
@@ -363,3 +363,26 @@ describe('NotebookParagraphResultComponent HTML 
highlighting', () => {
     expect(output.querySelector('.hljs-keyword')?.textContent).toBe('const');
   });
 });
+
+describe('HTML result alert dismissal', () => {
+  it('closes only the selected warning without changing the result data', () 
=> {
+    const result = component();
+    const html = ['102400 bytes', '1000 rows']
+      .map(
+        limit =>
+          `<div class="result-alert alert-warning" role="alert"><button 
class="close" data-dismiss="alert"><span>&times;</span></button>${limit}</div>`
+      )
+      .join('');
+    result.result = { type: DatasetType.HTML, data: html };
+    const container = document.createElement('div');
+    container.innerHTML = `${html}<button class="close" 
data-dismiss="alert">Ordinary output</button>`;
+    container.addEventListener('click', event => 
result.dismissResultAlert(event));
+
+    container.querySelector<HTMLElement>('button span')!.click();
+    container.querySelector<HTMLButtonElement>('div + button')!.click();
+
+    expect(container.querySelector('[role="alert"]')?.textContent).toBe('×1000 
rows');
+    expect(container.textContent).toContain('Ordinary output');
+    expect(result.result.data).toBe(html);
+  });
+});
diff --git 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts
index e2a09068c2..e1721472f7 100644
--- 
a/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts
+++ 
b/zeppelin-web-angular/src/app/pages/workspace/share/result/result.component.ts
@@ -326,6 +326,20 @@ export class NotebookParagraphResultComponent implements 
OnInit, AfterViewInit,
     this.configChange.emit(this.config);
   }
 
+  dismissResultAlert(event: MouseEvent): void {
+    const container = event.currentTarget;
+    const target = event.target;
+    if (!(container instanceof Element) || !(target instanceof Element)) {
+      return;
+    }
+    const button = target.closest('button.close[data-dismiss="alert"]');
+    const alert = button?.parentElement;
+    // ResultMessages warnings are dismissed locally, without changing the 
saved output.
+    if (alert?.classList.contains('result-alert') && alert !== container && 
container.contains(alert)) {
+      alert.remove();
+    }
+  }
+
   updateResult(config: ParagraphConfigResult, result: 
ParagraphIResultsMsgItem) {
     this.config = config;
     this.result = result;
diff --git a/zeppelin-web-angular/src/styles.less 
b/zeppelin-web-angular/src/styles.less
index 72bf531420..0452d43daa 100644
--- a/zeppelin-web-angular/src/styles.less
+++ b/zeppelin-web-angular/src/styles.less
@@ -17,5 +17,6 @@
 @import './styles/font';
 @import './styles/global';
 @import './styles/rewrite';
+@import './styles/result-alert';
 @import "../node_modules/ng-zorro-antd/resizable/style/entry.less";
 @import "../node_modules/ng-zorro-antd/code-editor/style/entry.less";
diff --git a/zeppelin-web-angular/src/styles/result-alert.less 
b/zeppelin-web-angular/src/styles/result-alert.less
new file mode 100644
index 0000000000..f5bec5a5a8
--- /dev/null
+++ b/zeppelin-web-angular/src/styles/result-alert.less
@@ -0,0 +1,57 @@
+/*
+ * Licensed 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.
+ */
+
+// Shell-owned styles also cover the React remote and dynamically inserted 
HTML.
+.inner-html .result-alert.alert-warning {
+  @import (reference, multiple) './theme/light/theme-light';
+  position: relative;
+  padding: 12px 44px 12px 16px;
+  color: @text-color;
+  background: @alert-warning-bg-color;
+  border: 1px solid @alert-warning-border-color;
+  border-radius: @border-radius-base;
+  overflow-wrap: anywhere;
+
+  > button.close[data-dismiss='alert'] {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    width: 24px;
+    height: 24px;
+    padding: 0;
+    border: 0;
+    background: transparent;
+    color: inherit;
+    font: inherit;
+    font-size: 20px;
+    line-height: 24px;
+    cursor: pointer;
+    opacity: 0.65;
+
+    &:hover,
+    &:focus-visible {
+      opacity: 1;
+    }
+
+    &:focus-visible {
+      outline: 2px solid currentColor;
+      outline-offset: 2px;
+    }
+  }
+}
+
+html.dark .inner-html .result-alert.alert-warning {
+  @import (reference, multiple) './theme/dark/theme-dark';
+  color: @text-color;
+  background: mix(@warning-color, @component-background, 10%);
+  border-color: mix(@warning-color, @component-background, 30%);
+}

Reply via email to