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)],

Reply via email to