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 bad18ae394f6085f0af4eac30041f58c36f5a6b8 Author: Yi Zhang <[email protected]> AuthorDate: Thu Dec 11 10:48:36 2025 +0800 [FLINK-38778][ui] Return to application from job --- .../client/program/rest/RestClusterClientTest.java | 4 ++++ .../src/test/resources/rest_api_v1.snapshot | 3 +++ .../web-dashboard/src/app/interfaces/job-detail.ts | 1 + .../job/job-detail/status/job-status.component.html | 11 ++++++++++- .../job/job-detail/status/job-status.component.less | 18 ++++++++++++++++++ .../job/job-detail/status/job-status.component.ts | 9 ++++++++- .../runtime/rest/handler/job/JobDetailsHandler.java | 1 + .../runtime/rest/messages/job/JobDetailsInfo.java | 20 ++++++++++++++++++++ .../rest/messages/job/JobDetailsInfoTest.java | 2 ++ 9 files changed, 67 insertions(+), 2 deletions(-) diff --git a/flink-clients/src/test/java/org/apache/flink/client/program/rest/RestClusterClientTest.java b/flink-clients/src/test/java/org/apache/flink/client/program/rest/RestClusterClientTest.java index 71e00f38d55..0c4fc83384e 100644 --- a/flink-clients/src/test/java/org/apache/flink/client/program/rest/RestClusterClientTest.java +++ b/flink-clients/src/test/java/org/apache/flink/client/program/rest/RestClusterClientTest.java @@ -18,6 +18,7 @@ package org.apache.flink.client.program.rest; +import org.apache.flink.api.common.ApplicationID; import org.apache.flink.api.common.JobExecutionResult; import org.apache.flink.api.common.JobID; import org.apache.flink.api.common.JobStatus; @@ -190,6 +191,7 @@ class RestClusterClientTest { private JobGraph jobGraph; private JobID jobId; + private ApplicationID applicationId; private static final Configuration restConfig; @@ -213,6 +215,7 @@ class RestClusterClientTest { jobGraph = JobGraphTestUtils.emptyJobGraph(); jobId = jobGraph.getJobID(); + applicationId = ApplicationID.generate(); } @AfterEach @@ -1264,6 +1267,7 @@ class RestClusterClientTest { final JobDetailsInfo jobDetailsInfo = new JobDetailsInfo( jobId, + applicationId, "foobar", false, JobStatus.RUNNING, diff --git a/flink-runtime-web/src/test/resources/rest_api_v1.snapshot b/flink-runtime-web/src/test/resources/rest_api_v1.snapshot index 87a995c854b..d9ec78270e2 100644 --- a/flink-runtime-web/src/test/resources/rest_api_v1.snapshot +++ b/flink-runtime-web/src/test/resources/rest_api_v1.snapshot @@ -1218,6 +1218,9 @@ "jid" : { "type" : "any" }, + "application-id" : { + "type" : "any" + }, "name" : { "type" : "string" }, diff --git a/flink-runtime-web/web-dashboard/src/app/interfaces/job-detail.ts b/flink-runtime-web/web-dashboard/src/app/interfaces/job-detail.ts index b830e5d92d0..21724a6b410 100644 --- a/flink-runtime-web/web-dashboard/src/app/interfaces/job-detail.ts +++ b/flink-runtime-web/web-dashboard/src/app/interfaces/job-detail.ts @@ -60,6 +60,7 @@ export interface JobDetail { plan: Plan; 'stream-graph': StreamGraph; 'pending-operators': number; + 'application-id': string; } interface Plan { diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.html b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.html index 58b0b3090df..0c7a867f089 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.html +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.html @@ -16,6 +16,15 @@ ~ limitations under the License. --> +<ng-template #titleTpl> + <div class="title-wrapper"> + <a *ngIf="jobDetail['application-id']" (click)="navigateToApplication()" class="back-link"> + ← Return to Application + </a> + <span>{{ jobDetail.name }}</span> + </div> +</ng-template> + <ng-template #extraTpl> <div class="operate-action"> <span *ngIf="statusTips">{{ statusTips }}</span> @@ -41,7 +50,7 @@ <ng-container *ngIf="jobDetail && !isLoading"> <nz-descriptions - [nzTitle]="jobDetail.name" + [nzTitle]="titleTpl" [nzExtra]="extraTpl" nzBordered nzSize="small" diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.less b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.less index 776a1de5d9d..9a2ef00ce4a 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.less +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.less @@ -38,4 +38,22 @@ display: flex; align-items: center; } + + .back-link { + display: inline-flex; + align-items: center; + color: @link-color; + text-decoration: none; + margin-right: @margin-xs; + font-size: @font-size-base; + cursor: pointer; + + &:hover { + color: @link-hover-color; + } + + &::before { + margin-right: @margin-xs; + } + } } diff --git a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.ts b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.ts index 544ffe87b9a..8d7ded6d298 100644 --- a/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.ts +++ b/flink-runtime-web/web-dashboard/src/app/pages/job/job-detail/status/job-status.component.ts @@ -18,7 +18,7 @@ import { DatePipe, NgIf } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core'; -import { RouterLinkWithHref } from '@angular/router'; +import { Router, RouterLinkWithHref } from '@angular/router'; import { merge, Subject } from 'rxjs'; import { distinctUntilKeyChanged, mergeMap, take, takeUntil, tap } from 'rxjs/operators'; @@ -75,6 +75,7 @@ export class JobStatusComponent implements OnInit, OnDestroy { private readonly jobManagerService: JobManagerService, private readonly statusService: StatusService, private readonly cdr: ChangeDetectorRef, + private readonly router: Router, @Inject(JOB_MODULE_CONFIG) readonly moduleConfig: JobModuleConfig ) { this.listOfNavigation = moduleConfig.routerTabs || JOB_MODULE_DEFAULT_CONFIG.routerTabs; @@ -138,4 +139,10 @@ export class JobStatusComponent implements OnInit, OnDestroy { } this.cdr.markForCheck(); } + + navigateToApplication(): void { + if (this.jobDetail['application-id']) { + this.router.navigate(['/', 'application', 'running', this.jobDetail['application-id']]).then(); + } + } } diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/JobDetailsHandler.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/JobDetailsHandler.java index 18a5cb7c340..4fb246e26c3 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/JobDetailsHandler.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/handler/job/JobDetailsHandler.java @@ -148,6 +148,7 @@ public class JobDetailsHandler return new JobDetailsInfo( executionGraph.getJobID(), + executionGraph.getApplicationId().orElse(null), executionGraph.getJobName(), executionGraph.isStoppable(), executionGraph.getState(), diff --git a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfo.java b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfo.java index ff2330692cb..2f646d3b387 100644 --- a/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfo.java +++ b/flink-runtime/src/main/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfo.java @@ -18,6 +18,7 @@ package org.apache.flink.runtime.rest.messages.job; +import org.apache.flink.api.common.ApplicationID; import org.apache.flink.api.common.JobID; import org.apache.flink.api.common.JobStatus; import org.apache.flink.runtime.execution.ExecutionState; @@ -27,6 +28,8 @@ import org.apache.flink.runtime.jobgraph.JobVertexID; import org.apache.flink.runtime.rest.messages.JobPlanInfo; import org.apache.flink.runtime.rest.messages.ResponseBody; import org.apache.flink.runtime.rest.messages.job.metrics.IOMetricsInfo; +import org.apache.flink.runtime.rest.messages.json.ApplicationIDDeserializer; +import org.apache.flink.runtime.rest.messages.json.ApplicationIDSerializer; import org.apache.flink.runtime.rest.messages.json.JobIDDeserializer; import org.apache.flink.runtime.rest.messages.json.JobIDSerializer; import org.apache.flink.runtime.rest.messages.json.JobVertexIDDeserializer; @@ -90,6 +93,8 @@ public class JobDetailsInfo implements ResponseBody { public static final String FIELD_NAME_PENDING_OPERATORS = "pending-operators"; + public static final String FIELD_NAME_APPLICATION_ID = "application-id"; + @JsonProperty(FIELD_NAME_JOB_ID) @JsonSerialize(using = JobIDSerializer.class) private final JobID jobId; @@ -118,6 +123,10 @@ public class JobDetailsInfo implements ResponseBody { @JsonProperty(FIELD_NAME_MAX_PARALLELISM) private final long maxParallelism; + @JsonProperty(FIELD_NAME_APPLICATION_ID) + @JsonSerialize(using = ApplicationIDSerializer.class) + private final ApplicationID applicationId; + @JsonProperty(FIELD_NAME_NOW) private final long now; @@ -145,6 +154,9 @@ public class JobDetailsInfo implements ResponseBody { public JobDetailsInfo( @JsonDeserialize(using = JobIDDeserializer.class) @JsonProperty(FIELD_NAME_JOB_ID) JobID jobId, + @JsonDeserialize(using = ApplicationIDDeserializer.class) + @JsonProperty(FIELD_NAME_APPLICATION_ID) + ApplicationID applicationId, @JsonProperty(FIELD_NAME_JOB_NAME) String name, @JsonProperty(FIELD_NAME_IS_STOPPABLE) boolean isStoppable, @JsonProperty(FIELD_NAME_JOB_STATUS) JobStatus jobStatus, @@ -164,6 +176,7 @@ public class JobDetailsInfo implements ResponseBody { JobPlanInfo.RawJson streamGraphJson, @JsonProperty(FIELD_NAME_PENDING_OPERATORS) int pendingOperators) { this.jobId = Preconditions.checkNotNull(jobId); + this.applicationId = applicationId; this.name = Preconditions.checkNotNull(name); this.isStoppable = isStoppable; this.jobStatus = Preconditions.checkNotNull(jobStatus); @@ -200,6 +213,7 @@ public class JobDetailsInfo implements ResponseBody { && Objects.equals(name, that.name) && jobStatus == that.jobStatus && jobType == that.jobType + && Objects.equals(applicationId, that.applicationId) && Objects.equals(timestamps, that.timestamps) && Objects.equals(jobVertexInfos, that.jobVertexInfos) && Objects.equals(jobVerticesPerState, that.jobVerticesPerState) @@ -220,6 +234,7 @@ public class JobDetailsInfo implements ResponseBody { endTime, duration, maxParallelism, + applicationId, now, timestamps, jobVertexInfos, @@ -239,6 +254,11 @@ public class JobDetailsInfo implements ResponseBody { return name; } + @JsonIgnore + public ApplicationID getApplicationId() { + return applicationId; + } + @JsonIgnore public boolean isStoppable() { return isStoppable; diff --git a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfoTest.java b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfoTest.java index c4361507f10..50bbe812182 100644 --- a/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfoTest.java +++ b/flink-runtime/src/test/java/org/apache/flink/runtime/rest/messages/job/JobDetailsInfoTest.java @@ -18,6 +18,7 @@ package org.apache.flink.runtime.rest.messages.job; +import org.apache.flink.api.common.ApplicationID; import org.apache.flink.api.common.JobID; import org.apache.flink.api.common.JobStatus; import org.apache.flink.runtime.execution.ExecutionState; @@ -72,6 +73,7 @@ class JobDetailsInfoTest extends RestResponseMarshallingTestBase<JobDetailsInfo> return new JobDetailsInfo( new JobID(), + new ApplicationID(), "foobar", true, JobStatus.values()[random.nextInt(JobStatus.values().length)],
