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,