From 11a2637db6d4e0e9cf6083684fb89fc5015d72ca Mon Sep 17 00:00:00 2001
From: Jialei <3217223+jialeicui@users.noreply.github.com>
Date: Fri, 14 Jul 2023 11:43:24 +0800
Subject: [PATCH] chore(controller): refine the exposed links (#2504)
---
.../src/domain/job/components/ExposedLink.tsx | 26 ++++++++++++++
console/src/domain/job/schemas/job.tsx | 13 ++++++-
console/src/pages/Job/JobListCard.tsx | 20 +++--------
.../mlops/api/protocol/job/ExposedLinkVo.java | 29 +++++++++++++++
.../mlops/api/protocol/job/JobVo.java | 2 +-
.../domain/job/converter/JobConverter.java | 36 ++++++++++++-------
.../mlops/domain/job/step/ExposedType.java | 22 ++++++++++++
.../job/converter/JobConverterTest.java | 8 +++--
8 files changed, 124 insertions(+), 32 deletions(-)
create mode 100644 console/src/domain/job/components/ExposedLink.tsx
create mode 100644 server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/ExposedLinkVo.java
create mode 100644 server/controller/src/main/java/ai/starwhale/mlops/domain/job/step/ExposedType.java
diff --git a/console/src/domain/job/components/ExposedLink.tsx b/console/src/domain/job/components/ExposedLink.tsx
new file mode 100644
index 0000000000..eec4525a67
--- /dev/null
+++ b/console/src/domain/job/components/ExposedLink.tsx
@@ -0,0 +1,26 @@
+import React from 'react'
+import { ExposedLinkType, IExposedLinkSchema } from '@job/schemas/job'
+import IconFont, { IconTypesT } from '@starwhale/ui/IconFont'
+
+export interface IExposedLinkProps {
+ data: IExposedLinkSchema
+}
+
+const VSCODE_NAME = 'VS_CODE'
+
+function ExposedLink({ data: { type, name, link } }: IExposedLinkProps) {
+ let font: IconTypesT = 'global'
+ let title = name
+ if (type === ExposedLinkType.DEV_MODE && name === VSCODE_NAME) {
+ font = 'vscode'
+ title = 'vscode'
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default ExposedLink
diff --git a/console/src/domain/job/schemas/job.tsx b/console/src/domain/job/schemas/job.tsx
index 5b99da1d38..182f53ebd2 100644
--- a/console/src/domain/job/schemas/job.tsx
+++ b/console/src/domain/job/schemas/job.tsx
@@ -22,6 +22,17 @@ export enum JobStatusType {
UNKNOWN = 'UNKNOWN',
}
+export enum ExposedLinkType {
+ DEV_MODE = 'DEV_MODE',
+ WEB_HANDLER = 'WEB_HANDLER',
+}
+
+export interface IExposedLinkSchema {
+ type: ExposedLinkType
+ name: string
+ link: string
+}
+
export interface IJobSchema extends IResourceSchema {
uuid: string
name: string
@@ -39,7 +50,7 @@ export interface IJobSchema extends IResourceSchema {
stopTime?: number
createdTime?: number
pinnedTime?: number
- exposedLinks?: string[]
+ exposedLinks?: IExposedLinkSchema[]
isTimeToLiveInSec?: boolean
timeToLiveInSec?: number
}
diff --git a/console/src/pages/Job/JobListCard.tsx b/console/src/pages/Job/JobListCard.tsx
index 13b51bec93..7040f077d4 100644
--- a/console/src/pages/Job/JobListCard.tsx
+++ b/console/src/pages/Job/JobListCard.tsx
@@ -18,10 +18,10 @@ import JobStatus from '@/domain/job/components/JobStatus'
import Button from '@starwhale/ui/Button'
import { useAuthPrivileged, WithCurrentAuth } from '@/api/WithAuth'
import { IconTooltip } from '@starwhale/ui/Tooltip'
-import IconFont from '@starwhale/ui/IconFont'
import { useProjectRole } from '@project/hooks/useProjectRole'
import { ConfigurationOverride } from '@starwhale/ui/base/helpers/overrides'
import { ConfirmButton } from '@starwhale/ui'
+import ExposedLink from '@job/components/ExposedLink'
interface IActionButtonProps {
jobId: string
@@ -226,21 +226,11 @@ export default function JobListCard() {
typeof job.duration === 'string' ? '-' : durationToStr(job.duration),
job?.stopTime && job?.stopTime > 0 ? formatTimestampDateTime(job?.stopTime) : '-',
,
-
+
{actions[job.jobStatus] ?? ''}
- {job.exposedLinks?.map((link) => {
- return (
-
-
-
- )
- })}
+ {job.exposedLinks?.map((exposed) => (
+
+ ))}
,
]
}) ?? []
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/ExposedLinkVo.java b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/ExposedLinkVo.java
new file mode 100644
index 0000000000..9d5c268591
--- /dev/null
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/ExposedLinkVo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 Starwhale, Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+package ai.starwhale.mlops.api.protocol.job;
+
+import ai.starwhale.mlops.domain.job.step.ExposedType;
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class ExposedLinkVo {
+ private ExposedType type;
+ private String name;
+ private String link;
+}
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobVo.java b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobVo.java
index 404c82d2e7..8069c950cd 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobVo.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobVo.java
@@ -82,7 +82,7 @@ public class JobVo implements Serializable {
// expose links is used to get the serving url of the model, it may contain:
// 1. vscode url when the model is running under dev mode
// 2. serving url when the model is running a web handler (which using the non-zero expose handler decorator)
- private List
exposedLinks;
+ private List exposedLinks;
@JsonProperty("duration")
public Long getDuration() {
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobConverter.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobConverter.java
index 7e392f3c6e..14e1a66ff7 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobConverter.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobConverter.java
@@ -17,6 +17,7 @@
package ai.starwhale.mlops.domain.job.converter;
+import ai.starwhale.mlops.api.protocol.job.ExposedLinkVo;
import ai.starwhale.mlops.api.protocol.job.JobVo;
import ai.starwhale.mlops.api.protocol.runtime.RuntimeVo;
import ai.starwhale.mlops.api.protocol.user.UserVo;
@@ -24,12 +25,14 @@
import ai.starwhale.mlops.common.proxy.WebServerInTask;
import ai.starwhale.mlops.domain.dataset.DatasetDao;
import ai.starwhale.mlops.domain.dataset.bo.DatasetVersion;
+import ai.starwhale.mlops.domain.job.DevWay;
import ai.starwhale.mlops.domain.job.bo.Job;
import ai.starwhale.mlops.domain.job.cache.HotJobHolder;
import ai.starwhale.mlops.domain.job.po.JobEntity;
import ai.starwhale.mlops.domain.job.spec.JobSpecParser;
import ai.starwhale.mlops.domain.job.spec.StepSpec;
import ai.starwhale.mlops.domain.job.status.JobStatus;
+import ai.starwhale.mlops.domain.job.step.ExposedType;
import ai.starwhale.mlops.domain.runtime.RuntimeService;
import ai.starwhale.mlops.domain.system.SystemSettingService;
import ai.starwhale.mlops.domain.task.bo.Task;
@@ -40,12 +43,12 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
+import reactor.function.Consumer4;
@Component
public class JobConverter {
@@ -99,38 +102,38 @@ private List findDatasetVersionNamesByJobId(Long jobId) {
* @param jobId job id
* @return exposed links
*/
- private List generateJobExposedLinks(Long jobId) {
- var links = new ArrayList();
+ private List generateJobExposedLinks(Long jobId) {
+ var exposed = new ArrayList();
var jobs = hotJobHolder.ofIds(List.of(jobId));
if (CollectionUtils.isEmpty(jobs)) {
- return links;
+ return exposed;
}
var job = jobs.stream().findAny().get();
// only running job should generate exposed links
var jobStatus = job.getStatus();
if (jobStatus != JobStatus.RUNNING) {
- return links;
+ return exposed;
}
// check if job has exposed port in step spec
var stepSpecStr = job.getStepSpec();
if (!StringUtils.hasText(stepSpecStr)) {
- return links;
+ return exposed;
}
List stepSpecs;
try {
stepSpecs = jobSpecParser.parseAndFlattenStepFromYaml(stepSpecStr);
} catch (JsonProcessingException e) {
- return links;
+ return exposed;
}
if (CollectionUtils.isEmpty(stepSpecs)) {
- return links;
+ return exposed;
}
var steps = job.getSteps();
- BiConsumer addRunningTask = (task, port) -> {
+ Consumer4 addRunningTask = (task, port, type, name) -> {
if (task.getStatus() != TaskStatus.RUNNING) {
return;
}
@@ -139,13 +142,13 @@ private List generateJobExposedLinks(Long jobId) {
return;
}
var link = webServerInTask.generateGatewayUrl(task.getId(), task.getIp(), port);
- links.add(link);
+ exposed.add(ExposedLinkVo.builder().type(type).name(name).link(link).build());
};
// dev mode
if (job.isDevMode()) {
job.getSteps().stream().flatMap(s -> s.getTasks().stream())
- .forEach(task -> addRunningTask.accept(task, devPort));
+ .forEach(task -> addRunningTask.accept(task, devPort, ExposedType.DEV_MODE, DevWay.VS_CODE.name()));
}
// web handler
@@ -165,10 +168,17 @@ private List generateJobExposedLinks(Long jobId) {
if (CollectionUtils.isEmpty(tasks)) {
return;
}
- tasks.forEach(task -> addRunningTask.accept(task, exposedPort));
+ String name;
+ // https://github.com/star-whale/starwhale/blob/c924313166d065ab941a99fa1dec04b7bfbe5fa7/client/starwhale/core/model/model.py#L246-L252
+ if (Boolean.TRUE.equals(stepSpec.getVirtual()) && stepSpec.getName().equals("serving")) {
+ name = "online evaluation";
+ } else {
+ name = stepSpec.getName();
+ }
+ tasks.forEach(t -> addRunningTask.accept(t, exposedPort, ExposedType.WEB_HANDLER, name));
});
- return links;
+ return exposed;
}
public JobVo convert(Job job) throws ConvertException {
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/step/ExposedType.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/step/ExposedType.java
new file mode 100644
index 0000000000..4de1bfc66a
--- /dev/null
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/step/ExposedType.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 Starwhale, Inc. All Rights Reserved.
+ *
+ * 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.
+ */
+
+package ai.starwhale.mlops.domain.job.step;
+
+public enum ExposedType {
+ DEV_MODE,
+ WEB_HANDLER,
+}
diff --git a/server/controller/src/test/java/ai/starwhale/mlops/domain/job/converter/JobConverterTest.java b/server/controller/src/test/java/ai/starwhale/mlops/domain/job/converter/JobConverterTest.java
index 06403116c0..d0ed598b0b 100644
--- a/server/controller/src/test/java/ai/starwhale/mlops/domain/job/converter/JobConverterTest.java
+++ b/server/controller/src/test/java/ai/starwhale/mlops/domain/job/converter/JobConverterTest.java
@@ -28,6 +28,7 @@
import static org.mockito.BDDMockito.mock;
import static org.mockito.Mockito.when;
+import ai.starwhale.mlops.api.protocol.job.ExposedLinkVo;
import ai.starwhale.mlops.api.protocol.runtime.RuntimeVo;
import ai.starwhale.mlops.api.protocol.user.UserVo;
import ai.starwhale.mlops.common.IdConverter;
@@ -39,6 +40,7 @@
import ai.starwhale.mlops.domain.job.spec.JobSpecParser;
import ai.starwhale.mlops.domain.job.spec.StepSpec;
import ai.starwhale.mlops.domain.job.status.JobStatus;
+import ai.starwhale.mlops.domain.job.step.ExposedType;
import ai.starwhale.mlops.domain.job.step.bo.Step;
import ai.starwhale.mlops.domain.model.po.ModelVersionEntity;
import ai.starwhale.mlops.domain.runtime.RuntimeService;
@@ -166,13 +168,15 @@ public void testConvertJobExposedLinks() throws JsonProcessingException {
entity.setJobStatus(JobStatus.RUNNING);
job.setStatus(JobStatus.RUNNING);
res = jobConvertor.convert(entity);
- assertThat(res.getExposedLinks(), is(List.of("/foo/bar/")));
+ var expectWeb = ExposedLinkVo.builder().type(ExposedType.WEB_HANDLER).link("/foo/bar/").name("step").build();
+ assertThat(res.getExposedLinks(), is(List.of(expectWeb)));
// get debug mode links when dev mode is on
when(webServerInTask.generateGatewayUrl(7L, "1.1.1.1", 1234)).thenReturn("/baz/");
entity.setDevMode(true);
job.setDevMode(true);
res = jobConvertor.convert(entity);
- assertThat(res.getExposedLinks(), is(List.of("/baz/", "/foo/bar/")));
+ var expectDevMode = ExposedLinkVo.builder().type(ExposedType.DEV_MODE).link("/baz/").name("VS_CODE").build();
+ assertThat(res.getExposedLinks(), is(List.of(expectDevMode, expectWeb)));
}
}