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 1932768274 [ZEPPELIN-6445] Type AngularJS drag-and-drop callback bridge
1932768274 is described below

commit 193276827499707ac6628ae0d8d1a4f7a19b34a8
Author: chaeyoung kim <[email protected]>
AuthorDate: Mon Oct 5 22:09:40 2026 +0900

    [ZEPPELIN-6445] Type AngularJS drag-and-drop callback bridge
    
    ### What is this PR for?
    
    This PR adds explicit types to the AngularJS drag-and-drop callback bridge 
and removes the existing `<at>ts-ignore` directives.
    
    It preserves callback arguments and scope binding while safely ignoring 
callbacks that are not defined.
    
    ### What type of PR is it?
    
    Improvement
    
    ### Todos
    
    * [x] Remove `<at>ts-ignore` directives
    * [x] Preserve existing callback behavior
    * [x] Add regression tests for callback invocation and missing callbacks
    
    ### What is the Jira issue?
    
    https://issues.apache.org/jira/browse/ZEPPELIN-6445
    
    ### How should this be tested?
    
    Automated tests cover callback argument forwarding, scope binding, and 
missing callback handling.
    
    * `npm run test:shell`
    * `npm run lint`
    * `npm run build:angular`
    * `./mvnw -pl zeppelin-web-angular org.apache.rat:apache-rat-plugin:check 
-Prat`
    
    ### Questions:
    
    * Does the license files need to update? No.
    * Is there breaking changes for older versions? No.
    * Does this needs documentation? No. This is an internal type-safety 
improvement with no user-facing changes.
    
    Closes #5534 from chelsseeey/ZEPPELIN-6445-type-angular-drag-drop-callbacks.
    
    Signed-off-by: YONGJAE LEE <[email protected]>
---
 .../app/services/angular-drag-drop.service.spec.ts | 71 ++++++++++++++++++++++
 .../src/app/services/angular-drag-drop.service.ts  | 35 +++++++----
 2 files changed, 95 insertions(+), 11 deletions(-)

diff --git 
a/zeppelin-web-angular/src/app/services/angular-drag-drop.service.spec.ts 
b/zeppelin-web-angular/src/app/services/angular-drag-drop.service.spec.ts
new file mode 100644
index 0000000000..98b0c8392b
--- /dev/null
+++ b/zeppelin-web-angular/src/app/services/angular-drag-drop.service.spec.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 * as angular from 'angular';
+import { describe, expect, it, vi } from 'vitest';
+
+import { AngularDragDropService } from './angular-drag-drop.service';
+
+type TestScope = angular.IScope & Record<string, unknown>;
+
+interface CallbackService {
+  callEventCallback(scope: angular.IScope, callbackName: string, event: 
DragEvent): void;
+}
+
+type DragDropFactory = ($parse: angular.IParseService) => CallbackService;
+type AnnotatedDragDropFactory = ['$parse', DragDropFactory];
+
+const createCallbackService = (): CallbackService => {
+  let registeredFactory: DragDropFactory | undefined;
+  const moduleStub = {
+    factory: (_name: string, definition: AnnotatedDragDropFactory) => {
+      registeredFactory = definition[1];
+      return moduleStub;
+    },
+    directive: () => {
+      return moduleStub;
+    }
+  };
+
+  new AngularDragDropService().addDragDropDirectives(moduleStub as unknown as 
angular.IModule);
+
+  const $parse = ((expression: string) => (scope: angular.IScope) => {
+    return (scope as TestScope)[expression];
+  }) as angular.IParseService;
+
+  if (!registeredFactory) {
+    throw new Error('customDragDropService factory was not registered');
+  }
+  return registeredFactory($parse);
+};
+
+describe('AngularDragDropService', () => {
+  it('invokes a scope callback with the drag event, UI value, and parsed 
arguments', () => {
+    const callbackService = createCallbackService();
+    const callback = vi.fn();
+    const argument = { name: 'axis' };
+    const scope = { save: callback, axisSpec: argument } as unknown as 
TestScope;
+    const event = {} as DragEvent;
+
+    callbackService.callEventCallback(scope, 'save(axisSpec)', event);
+
+    expect(callback).toHaveBeenCalledWith(event, undefined, argument);
+    expect(callback.mock.instances[0]).toBe(scope);
+  });
+
+  it('ignores a missing callback', () => {
+    const callbackService = createCallbackService();
+    const scope = {} as TestScope;
+
+    expect(() => callbackService.callEventCallback(scope, 'missing()', {} as 
DragEvent)).not.toThrow();
+  });
+});
diff --git a/zeppelin-web-angular/src/app/services/angular-drag-drop.service.ts 
b/zeppelin-web-angular/src/app/services/angular-drag-drop.service.ts
index 4683997d89..dcb63fc232 100644
--- a/zeppelin-web-angular/src/app/services/angular-drag-drop.service.ts
+++ b/zeppelin-web-angular/src/app/services/angular-drag-drop.service.ts
@@ -34,6 +34,20 @@ interface UI {
   offset: { top: number; left: number };
 }
 
+type LegacyScope = angular.IScope & Record<string, unknown>;
+
+interface LegacyCallbackOwner extends Record<string, unknown> {
+  length?: number;
+}
+
+type LegacyCallback = (...args: unknown[]) => unknown;
+
+const isLegacyCallbackOwner = (value: unknown): value is LegacyCallbackOwner 
=> {
+  return typeof value === 'object' && value !== null && typeof 
value.constructor === 'function';
+};
+
+const isLegacyCallback = (value: unknown): value is LegacyCallback => typeof 
value === 'function';
+
 @Injectable({
   providedIn: 'root'
 })
@@ -61,11 +75,12 @@ export class AngularDragDropService {
             const { targetCallback, targetScope, args: extractedArgs } = 
extract(callbackStr);
             const fullArgs = [event, ui].concat(extractedArgs);
 
-            if (typeof targetCallback === 'function') {
+            if (isLegacyCallback(targetCallback)) {
               return targetCallback.apply(targetScope, fullArgs);
             }
 
             function extract(_callbackStr: string) {
+              const legacyScope = scope as LegacyScope;
               const atStartBracket = _callbackStr.indexOf('(') !== -1 ? 
_callbackStr.indexOf('(') : _callbackStr.length;
               const atEndBracket =
                 _callbackStr.lastIndexOf(')') !== -1 ? 
_callbackStr.lastIndexOf(')') : _callbackStr.length;
@@ -76,17 +91,15 @@ export class AngularDragDropService {
 
               const dotIndex = _callbackStr.indexOf('.');
               const constructorName = dotIndex !== -1 ? _callbackStr.slice(0, 
dotIndex) : null;
-              // @ts-ignore
-              const constructorCandid = constructorName && 
scope[constructorName];
-              const constructor =
-                constructorCandid && typeof constructorCandid.constructor === 
'function' ? constructorCandid : null;
-
-              const callbackName = _callbackStr.substring((constructor && 
constructor.length + 1) || 0, atStartBracket);
-              // @ts-ignore
-              const callbackCandid = scope[callbackName];
+              const constructorCandidate = constructorName ? 
legacyScope[constructorName] : null;
+              const constructor = isLegacyCallbackOwner(constructorCandidate) 
? constructorCandidate : null;
+
+              const callbackOffset = typeof constructor?.length === 'number' ? 
constructor.length + 1 : 0;
+              const callbackName = _callbackStr.substring(callbackOffset, 
atStartBracket);
+              const callbackCandidate = legacyScope[callbackName];
               // If the expression is a method call, then the parsed 
constructor becomes its bound scope.
-              const _scope = callbackCandid ? scope : constructor;
-              const callback = callbackCandid || constructor[callbackName];
+              const _scope = callbackCandidate ? scope : constructor;
+              const callback = callbackCandidate || 
constructor?.[callbackName];
 
               return {
                 args,

Reply via email to