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 <[email protected]> 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` + ); + } }
