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 883c9d6593 [ZEPPELIN-6676] Accept both Angular object removal payload 
shapes
883c9d6593 is described below

commit 883c9d65932609d071b4593941ec8cd62aa5fac0
Author: Minho Jang <[email protected]>
AuthorDate: Thu Oct 8 19:00:54 2026 +0900

    [ZEPPELIN-6676] Accept both Angular object removal payload shapes
    
    ### What is this PR for?
    
    The server emits two `ANGULAR_OBJECT_REMOVE` payload shapes. Interpreter 
removal supplies top-level `name`; client unbind supplies `angularObject.name`.
    
    Represent both shapes and export a shared SDK helper using `data.name ?? 
data.angularObject?.name`. Update the existing Angular consumer to use this 
normalized identifier while preserving paragraph isolation and avoiding 
feedback messages.
    
    Server evidence:
    - 
`zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java:1518-1528,2276-2291`
    
    ### What type of PR is it?
    
    Bug Fix
    
    ### Todos
    
    - [x] Represent and normalize both removal payloads.
    - [x] Test both shapes, name precedence and paragraph isolation.
    
    ### What is the Jira issue?
    
    https://issues.apache.org/jira/browse/ZEPPELIN-6676
    
    Slice 3 of 3: Angular object removal compatibility. Slice 1 merged in #5539 
and slice 2 merged in #5544.
    
    ### How should this be tested?
    
    Run from the repository root after installing frontend dependencies:
    
    ```sh
    cd zeppelin-web-angular
    npm run typecheck:sdk-contracts
    npm run test:shell
    npm run check:websocket-contract
    ```
    
    ### Questions:
    
    - Does the license files need to update? No.
    - Is there breaking changes for older versions? Consumers must normalize 
the identifier instead of assuming top-level `name` is always present. Both 
existing server shapes are accepted. No server wire-format change.
    - Does this needs documentation? The SDK helper and regression tests show 
both payload shapes and normalization.
    
    
    Closes #5557 from miinhho/ZEPPELIN-6676-upstream-angular-object-compat.
    
    Signed-off-by: YONGJAE LEE <[email protected]>
---
 .../zeppelin-sdk/src/angular-object.spec.ts        | 41 ++++++++++++++++++++++
 .../src/{public-api.ts => angular-object.ts}       |  8 +++--
 .../src/interfaces/message-paragraph.interface.ts  |  6 ++--
 .../projects/zeppelin-sdk/src/public-api.ts        |  1 +
 .../app/core/paragraph-base/paragraph-base.spec.ts | 24 +++++++++++--
 .../src/app/core/paragraph-base/paragraph-base.ts  |  6 +++-
 6 files changed, 78 insertions(+), 8 deletions(-)

diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.spec.ts 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.spec.ts
new file mode 100644
index 0000000000..299449c456
--- /dev/null
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.spec.ts
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+import { assertType, describe, expect, it } from 'vitest';
+
+import { getAngularObjectRemovalName } from './angular-object';
+import { AngularObjectRemove } from './interfaces/message-paragraph.interface';
+
+describe('Angular object removal compatibility', () => {
+  const context = { noteId: 'note', paragraphId: 'paragraph' };
+
+  it('reads the interpreter removal name', () => {
+    const data = { ...context, name: 'value' } satisfies AngularObjectRemove;
+    expect(getAngularObjectRemovalName(data)).toBe('value');
+  });
+
+  it('reads the client-unbind object name without a top-level name', () => {
+    const data = { ...context, angularObject: { name: 'value', object: 42 }, 
interpreterGroupId: 'group' };
+    assertType<AngularObjectRemove>(data);
+    expect(getAngularObjectRemovalName(data)).toBe('value');
+  });
+
+  it('prefers the top-level name when both are supplied', () => {
+    expect(
+      getAngularObjectRemovalName({
+        ...context,
+        name: 'top',
+        angularObject: { name: 'nested', object: null }
+      })
+    ).toBe('top');
+  });
+});
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.ts
similarity index 64%
copy from zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
copy to zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.ts
index 2db821a15a..de91b0e39b 100644
--- a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/angular-object.ts
@@ -10,6 +10,8 @@
  * limitations under the License.
  */
 
-export * from './interfaces/public-api';
-export * from './message';
-export * from './paragraph-state';
+import { AngularObjectRemove } from './interfaces/message-paragraph.interface';
+
+// NotebookServer emits a name for interpreter removal and an object for 
client unbind.
+export const getAngularObjectRemovalName = (data: AngularObjectRemove): string 
| undefined =>
+  data.name ?? data.angularObject?.name;
diff --git 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
index 90e1312581..9d0d703008 100644
--- 
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
+++ 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-paragraph.interface.ts
@@ -240,10 +240,9 @@ export interface AngularObjectUpdated {
   interpreterGroupId: string;
 }
 
-export interface AngularObjectRemove {
+interface AngularObjectRemoveContext {
   noteId: string;
   paragraphId: string;
-  name: string;
   angularObject?: {
     name: string;
     object: unknown;
@@ -254,6 +253,9 @@ export interface AngularObjectRemove {
   interpreterGroupId?: string;
 }
 
+export type AngularObjectRemove = AngularObjectRemoveContext &
+  ({ name: string } | { name?: string; angularObject: 
NonNullable<AngularObjectRemoveContext['angularObject']> });
+
 export interface AngularObjectUpdate {
   noteId: string;
   paragraphId: string;
diff --git a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts 
b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
index 2db821a15a..ba14a13c7e 100644
--- a/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
+++ b/zeppelin-web-angular/projects/zeppelin-sdk/src/public-api.ts
@@ -10,6 +10,7 @@
  * limitations under the License.
  */
 
+export * from './angular-object';
 export * from './interfaces/public-api';
 export * from './message';
 export * from './paragraph-state';
diff --git 
a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.spec.ts 
b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.spec.ts
index fbfc695f8a..ca3ec2c922 100644
--- a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.spec.ts
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.spec.ts
@@ -11,7 +11,7 @@
  */
 
 import { ChangeDetectorRef } from '@angular/core';
-import { DatasetType, Message, OP, ParagraphItem, ParagraphState } from 
'@zeppelin/sdk';
+import { AngularObjectRemove, DatasetType, Message, OP, ParagraphItem, 
ParagraphState } from '@zeppelin/sdk';
 import { EMPTY } from 'rxjs';
 import { afterEach, describe, expect, it, vi } from 'vitest';
 
@@ -22,6 +22,7 @@ class TestParagraph extends ParagraphBase {
   changeColWidth = vi.fn();
   updateParagraphResult = vi.fn();
   protected currentNoteId = 'note';
+  unsetContextValue = vi.fn();
 
   hydrate(snapshot: ParagraphItem) {
     this.setParagraphSnapshot(snapshot);
@@ -31,7 +32,7 @@ class TestParagraph extends ParagraphBase {
     super(
       { receive: () => EMPTY, receiveEnvelope: () => EMPTY } as unknown as 
Message,
       { isParagraphRunning: item => item.status === 'RUNNING', 
isEntireNoteRunning: () => false },
-      {} as AngularContextManager,
+      { unsetContextValue: (...args) => this.unsetContextValue(...args) } as 
AngularContextManager,
       { markForCheck: vi.fn() } as unknown as ChangeDetectorRef
     );
     this.paragraph = paragraph;
@@ -73,6 +74,25 @@ const beginOutput = (component: TestParagraph) =>
 const appendOutput = (component: TestParagraph) =>
   component.onParagraphAppendOutput({ noteId: 'note', paragraphId: 'A', index: 
0, data: 'second\n' });
 
+describe('ParagraphBase Angular object removal', () => {
+  const payloads: AngularObjectRemove[] = [
+    { noteId: 'note', paragraphId: 'A', name: 'bound' },
+    { noteId: 'note', paragraphId: 'A', angularObject: { name: 'bound', 
object: 42 }, interpreterGroupId: 'group' }
+  ];
+
+  it.each(payloads)('removes the binding for %j', data => {
+    const component = new TestParagraph(paragraph('A'));
+    component.angularObjectRemove(data);
+    expect(component.unsetContextValue).toHaveBeenCalledWith('bound', 'A', 
false);
+  });
+
+  it('does not remove a binding belonging to another paragraph', () => {
+    const component = new TestParagraph(paragraph('A'));
+    component.angularObjectRemove({ noteId: 'note', paragraphId: 'other', 
name: 'bound' });
+    expect(component.unsetContextValue).not.toHaveBeenCalled();
+  });
+});
+
 describe('ParagraphBase streaming state isolation', () => {
   it.each(['append', 'update'])('ignores %s from another note with the same 
paragraph ID', kind => {
     const component = new TestParagraph(paragraph('A'));
diff --git a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts 
b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts
index 5645b6b743..7b87aea983 100644
--- a/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts
+++ b/zeppelin-web-angular/src/app/core/paragraph-base/paragraph-base.ts
@@ -24,6 +24,7 @@ import {
   ParagraphEditorSetting,
   ParagraphSettings,
   ParagraphStates,
+  getAngularObjectRemovalName,
   isTerminalParagraphState,
   ParagraphItem,
   ParagraphIResultsMsgItem,
@@ -228,7 +229,10 @@ export abstract class ParagraphBase extends 
MessageListenersManager {
       throw new Error('paragraph is not defined');
     }
     if (data.paragraphId === this.paragraph.id) {
-      this.angularContextManager.unsetContextValue(data.name, 
data.paragraphId, false);
+      const name = getAngularObjectRemovalName(data);
+      if (name !== undefined) {
+        this.angularContextManager.unsetContextValue(name, data.paragraphId, 
false);
+      }
     }
   }
 

Reply via email to