You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by zh...@apache.org on 2026/02/28 06:13:08 UTC
(flink) 02/02: [FLINK-38977][ui] Expose exceptions for applications
This is an automated email from the ASF dual-hosted git repository.
zhuzh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git
commit 9b7c95c8e0a3f49bd583a2db6d65d64c8175e7e4
Author: Yi Zhang <zh...@163.com>
AuthorDate: Wed Dec 10 15:30:51 2025 +0800
[FLINK-38977][ui] Expose exceptions for applications
---
.../application-exception.ts} | 24 ++---
.../web-dashboard/src/app/interfaces/public-api.ts | 1 +
.../app/pages/application/application.config.ts | 5 +-
.../application-exceptions.component.html | 30 ++++++
.../application-exceptions.component.less} | 64 ++++++++----
.../exceptions/application-exceptions.component.ts | 112 +++++++++++++++++++++
.../modules/completed-application/routes.ts | 10 ++
.../modules/running-application/routes.ts | 10 ++
.../src/app/services/application.service.ts | 7 ++
9 files changed, 231 insertions(+), 32 deletions(-)
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts b/flink-runtime-web/web-dashboard/src/app/interfaces/application-exception.ts
similarity index 60%
copy from flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts
copy to flink-runtime-web/web-dashboard/src/app/interfaces/application-exception.ts
index e5a659e429c..fa181919b3f 100644
--- a/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts
+++ b/flink-runtime-web/web-dashboard/src/app/interfaces/application-exception.ts
@@ -16,17 +16,17 @@
* limitations under the License.
*/
-import { InjectionToken } from '@angular/core';
+export interface ApplicationExceptions {
+ exceptionHistory: ApplicationExceptionHistory;
+}
-import { ModuleConfig } from '@flink-runtime-web/core/module-config';
+export interface ApplicationExceptionHistory {
+ entries: ApplicationExceptionInfo[];
+}
-export type ApplicationModuleConfig = Pick<ModuleConfig, 'routerTabs'>;
-
-export const APPLICATION_MODULE_DEFAULT_CONFIG: Required<ApplicationModuleConfig> = {
- routerTabs: [{ title: 'Overview', path: 'overview' }]
-};
-
-export const APPLICATION_MODULE_CONFIG = new InjectionToken<ApplicationModuleConfig>('application-module-config', {
- providedIn: 'root',
- factory: () => APPLICATION_MODULE_DEFAULT_CONFIG
-});
+export interface ApplicationExceptionInfo {
+ exceptionName: string;
+ stacktrace: string;
+ timestamp: number;
+ jobId?: string;
+}
diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts b/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
index 83451266fe2..38d072557e1 100644
--- a/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
+++ b/flink-runtime-web/web-dashboard/src/app/interfaces/public-api.ts
@@ -35,3 +35,4 @@ export * from './job-manager';
export * from './job-metrics';
export * from './application-overview';
export * from './application-detail';
+export * from './application-exception';
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts b/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts
index e5a659e429c..f24371a48e4 100644
--- a/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/application.config.ts
@@ -23,7 +23,10 @@ import { ModuleConfig } from '@flink-runtime-web/core/module-config';
export type ApplicationModuleConfig = Pick<ModuleConfig, 'routerTabs'>;
export const APPLICATION_MODULE_DEFAULT_CONFIG: Required<ApplicationModuleConfig> = {
- routerTabs: [{ title: 'Overview', path: 'overview' }]
+ routerTabs: [
+ { title: 'Overview', path: 'overview' },
+ { title: 'Exceptions', path: 'exceptions' }
+ ]
};
export const APPLICATION_MODULE_CONFIG = new InjectionToken<ApplicationModuleConfig>('application-module-config', {
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.html b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.html
new file mode 100644
index 00000000000..0cbb8a849bc
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.html
@@ -0,0 +1,30 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you 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.
+ -->
+
+<nz-tabs [nzSize]="'small'" [nzAnimated]="{ inkBar: true, tabPane: false }">
+ <nz-tab nzTitle="Root Exception">
+ <ng-template nz-tab>
+ <nz-code-editor
+ flinkAutoResize
+ [nzLoading]="isLoading"
+ [ngModel]="rootException"
+ [nzEditorOption]="editorOptions"
+ ></nz-code-editor>
+ </ng-template>
+ </nz-tab>
+</nz-tabs>
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.less
similarity index 58%
copy from flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts
copy to flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.less
index 6daac893d51..e7fc8121c5b 100644
--- a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.less
@@ -16,23 +16,49 @@
* limitations under the License.
*/
-import { Routes } from '@angular/router';
-
-import { ApplicationDetailComponent } from '@flink-runtime-web/pages/application/application-detail/application-detail.component';
-
-export const COMPLETED_APPLICATION_ROUES: Routes = [
- {
- path: '',
- component: ApplicationDetailComponent,
- children: [
- {
- path: 'overview',
- loadChildren: () => import('../../overview/routes').then(m => m.APPLICATION_OVERVIEW_ROUTES),
- data: {
- path: 'overview'
- }
- },
- { path: '**', redirectTo: 'overview', pathMatch: 'full' }
- ]
+@import "theme";
+
+:host {
+ display: flex;
+ flex: 1;
+ flex-flow: column nowrap;
+
+ ::ng-deep {
+ .ant-tabs-nav-list {
+ padding: 4px 16px;
+ }
+ }
+
+ nz-tabs {
+ position: relative;
+ flex: 1;
+ }
+
+ nz-code-editor {
+ position: relative;
+ height: calc(~"100vh - 310px");
+
+ &.subtask {
+ position: relative;
+ top: 0;
+ height: 300px;
+ }
+ }
+
+ nz-table {
+ margin-top: -@margin-md;
+ }
+
+ nz-tag {
+ margin-bottom: 5px;
+ }
+
+ .expand-td {
+ display: block;
+ padding: 0 !important;
+ }
+
+ .exception-select {
+ width: 300px;
}
-];
+}
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.ts b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.ts
new file mode 100644
index 00000000000..896203a9c1b
--- /dev/null
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/exceptions/application-exceptions.component.ts
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 { formatDate } from '@angular/common';
+import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { Subject } from 'rxjs';
+import { distinctUntilChanged, mergeMap, takeUntil, tap } from 'rxjs/operators';
+
+import { AutoResizeDirective } from '@flink-runtime-web/components/editor/auto-resize.directive';
+import { flinkEditorOptions } from '@flink-runtime-web/components/editor/editor-config';
+import { ExceptionInfo } from '@flink-runtime-web/interfaces';
+import { ApplicationService } from '@flink-runtime-web/services';
+import { NzButtonModule } from 'ng-zorro-antd/button';
+import { NzCodeEditorModule, EditorOptions } from 'ng-zorro-antd/code-editor';
+import { NzIconModule } from 'ng-zorro-antd/icon';
+import { NzSelectModule } from 'ng-zorro-antd/select';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { NzTabsModule } from 'ng-zorro-antd/tabs';
+import { NzTagModule } from 'ng-zorro-antd/tag';
+import { NzTooltipModule } from 'ng-zorro-antd/tooltip';
+
+import { ApplicationLocalService } from '../application-local.service';
+
+@Component({
+ selector: 'flink-application-exceptions',
+ templateUrl: './application-exceptions.component.html',
+ styleUrls: ['./application-exceptions.component.less'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ NzTabsModule,
+ NzCodeEditorModule,
+ AutoResizeDirective,
+ NzTableModule,
+ NzSelectModule,
+ NzTooltipModule,
+ FormsModule,
+ NzIconModule,
+ NzButtonModule,
+ NzTagModule
+ ]
+})
+export class ApplicationExceptionsComponent implements OnInit, OnDestroy {
+ public readonly trackByTimestamp = (_: number, node: ExceptionInfo): number => node.timestamp;
+
+ public rootException = '';
+ public isLoading = false;
+ public total = 0;
+ public editorOptions: EditorOptions = flinkEditorOptions;
+
+ private readonly destroy$ = new Subject<void>();
+
+ constructor(
+ private readonly applicationService: ApplicationService,
+ private readonly applicationLocalService: ApplicationLocalService,
+ private readonly cdr: ChangeDetectorRef
+ ) {}
+
+ public ngOnInit(): void {
+ this.loadMore();
+ }
+
+ public ngOnDestroy(): void {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+
+ public loadMore(): void {
+ this.isLoading = true;
+ this.applicationLocalService
+ .applicationDetailChanges()
+ .pipe(
+ distinctUntilChanged((pre, next) => pre.id === next.id),
+ mergeMap(application => this.applicationService.loadExceptions(application.id)),
+ tap(() => {
+ this.isLoading = false;
+ this.cdr.markForCheck();
+ }),
+ takeUntil(this.destroy$)
+ )
+ .subscribe(data => {
+ // @ts-ignore
+ const exceptionHistory = data.exceptionHistory;
+ if (exceptionHistory.entries.length > 0) {
+ const exceptionInfo = exceptionHistory.entries[0];
+ let exceptionMessage = `${formatDate(exceptionInfo.timestamp, 'yyyy-MM-dd HH:mm:ss', 'en')}\n`;
+ if (exceptionInfo.jobId) {
+ exceptionMessage += `Related Job: ${exceptionInfo.jobId}\n`;
+ }
+ exceptionMessage += exceptionInfo.stacktrace;
+ this.rootException = exceptionMessage;
+ } else {
+ this.rootException = 'No Root Exception';
+ }
+ });
+ }
+}
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts b/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts
index 6daac893d51..3824d1ba3c0 100644
--- a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/modules/completed-application/routes.ts
@@ -32,6 +32,16 @@ export const COMPLETED_APPLICATION_ROUES: Routes = [
path: 'overview'
}
},
+ {
+ path: 'exceptions',
+ loadComponent: () =>
+ import('@flink-runtime-web/pages/application/exceptions/application-exceptions.component').then(
+ m => m.ApplicationExceptionsComponent
+ ),
+ data: {
+ path: 'exceptions'
+ }
+ },
{ path: '**', redirectTo: 'overview', pathMatch: 'full' }
]
}
diff --git a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/running-application/routes.ts b/flink-runtime-web/web-dashboard/src/app/pages/application/modules/running-application/routes.ts
index e070a02b524..d1675386d5e 100644
--- a/flink-runtime-web/web-dashboard/src/app/pages/application/modules/running-application/routes.ts
+++ b/flink-runtime-web/web-dashboard/src/app/pages/application/modules/running-application/routes.ts
@@ -34,6 +34,16 @@ export const RUNNING_APPLICATION_ROUTES: Routes = [
path: 'overview'
}
},
+ {
+ path: 'exceptions',
+ loadComponent: () =>
+ import('@flink-runtime-web/pages/application/exceptions/application-exceptions.component').then(
+ m => m.ApplicationExceptionsComponent
+ ),
+ data: {
+ path: 'exceptions'
+ }
+ },
{ path: '**', redirectTo: 'overview', pathMatch: 'full' }
]
}
diff --git a/flink-runtime-web/web-dashboard/src/app/services/application.service.ts b/flink-runtime-web/web-dashboard/src/app/services/application.service.ts
index d9b7855556b..9a2394f3835 100644
--- a/flink-runtime-web/web-dashboard/src/app/services/application.service.ts
+++ b/flink-runtime-web/web-dashboard/src/app/services/application.service.ts
@@ -28,6 +28,7 @@ import {
JobStatus,
TaskStatus
} from '@flink-runtime-web/interfaces';
+import { ApplicationExceptions } from '@flink-runtime-web/interfaces/application-exception';
import { ConfigService } from './config.service';
@@ -92,4 +93,10 @@ export class ApplicationService {
catchError(() => EMPTY)
);
}
+
+ public loadExceptions(applicationId: string): Observable<ApplicationExceptions> {
+ return this.httpClient.get<ApplicationExceptions>(
+ `${this.configService.BASE_URL}/applications/${applicationId}/exceptions`
+ );
+ }
}