diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.css b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.css
index 680eeef4ef..58178f879c 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.css
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.css
@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 3410006 */
- src: url('iconfont.woff2?t=1686280722163') format('woff2'),
- url('iconfont.woff?t=1686280722163') format('woff'),
- url('iconfont.ttf?t=1686280722163') format('truetype');
+ src: url('iconfont.woff2?t=1686896835304') format('woff2'),
+ url('iconfont.woff?t=1686896835304') format('woff'),
+ url('iconfont.ttf?t=1686896835304') format('truetype');
}
.iconfont {
@@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}
+.icon-top2:before {
+ content: "\e66c";
+}
+
+.icon-top:before {
+ content: "\e66d";
+}
+
.icon-vscode:before {
content: "\e66b";
}
diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.js b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.js
index 1ca17ceaf8..111afd7d37 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.js
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.js
@@ -1 +1 @@
-window._iconfont_svg_string_3410006='',function(c){var l=(l=document.getElementsByTagName("script"))[l.length-1],h=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,i,v,m,t,s=function(l,h){h.parentNode.insertBefore(l,h)};if(h&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}o=function(){var l,h=document.createElement("div");h.innerHTML=c._iconfont_svg_string_3410006,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(l=document.body).firstChild?s(h,l.firstChild):l.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(v=o,m=c.document,t=!1,a(),m.onreadystatechange=function(){"complete"==m.readyState&&(m.onreadystatechange=null,z())})}function z(){t||(t=!0,v())}function a(){try{m.documentElement.doScroll("left")}catch(l){return void setTimeout(a,50)}z()}}(window);
\ No newline at end of file
+window._iconfont_svg_string_3410006='',function(c){var l=(l=document.getElementsByTagName("script"))[l.length-1],h=l.getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var o,i,v,m,t,s=function(l,h){h.parentNode.insertBefore(l,h)};if(h&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}o=function(){var l,h=document.createElement("div");h.innerHTML=c._iconfont_svg_string_3410006,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(l=document.body).firstChild?s(h,l.firstChild):l.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(o,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),o()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(v=o,m=c.document,t=!1,a(),m.onreadystatechange=function(){"complete"==m.readyState&&(m.onreadystatechange=null,z())})}function z(){t||(t=!0,v())}function a(){try{m.documentElement.doScroll("left")}catch(l){return void setTimeout(a,50)}z()}}(window);
\ No newline at end of file
diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.json b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.json
index 5468673f49..aab1d9af51 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.json
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.json
@@ -5,6 +5,20 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
+ {
+ "icon_id": "36036106",
+ "name": "top2",
+ "font_class": "top2",
+ "unicode": "e66c",
+ "unicode_decimal": 58988
+ },
+ {
+ "icon_id": "36036107",
+ "name": "top",
+ "font_class": "top",
+ "unicode": "e66d",
+ "unicode_decimal": 58989
+ },
{
"icon_id": "35913528",
"name": "vscode",
diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.ttf b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.ttf
index dced5e5cf1..5742e0a0ff 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.ttf
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.ttf
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:47bd9865ff76b4da2a288b31e84e9abd48cb6d609a6019eebd9f2afed4ff5059
-size 19452
+oid sha256:83cbfdd48556cd7f0dc6e2d758419e9e4f845e550dc9fe80c232a943320ac35f
+size 19656
diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff
index de76379db0..b1c30a759a 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:45b7a2f5d02aeac3d0899acb4f9d71ee35511b2af3a1fb5ea3f636ffd48d9488
-size 11184
+oid sha256:c499779c905413a9e73e19fa36ec327ef9e89224fbae1116694c37a0974403c1
+size 11284
diff --git a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff2 b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff2
index b6e3c25790..a8d3ce1236 100644
--- a/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff2
+++ b/console/packages/starwhale-ui/src/IconFont/fonts/iconfont.woff2
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:4f276a6454232ab95f972658b30e847aa208b261ad0d3faab6c9684f2d209ac5
-size 9348
+oid sha256:e3e358750480eff88a6b8a6608eaccc93f86a14fdeb875e35dcb17e4fd2f4c25
+size 9388
diff --git a/console/packages/starwhale-ui/src/IconFont/index.tsx b/console/packages/starwhale-ui/src/IconFont/index.tsx
index 5db782d700..772e9290ce 100644
--- a/console/packages/starwhale-ui/src/IconFont/index.tsx
+++ b/console/packages/starwhale-ui/src/IconFont/index.tsx
@@ -109,6 +109,8 @@ export type IconTypesT =
| 'WeChat2'
| 'vscode'
| 'docker'
+ | 'top'
+ | 'top2'
interface IIconFontProps {
style?: React.CSSProperties
diff --git a/console/packages/starwhale-ui/src/Tooltip/Tooltip.tsx b/console/packages/starwhale-ui/src/Tooltip/Tooltip.tsx
index d0baf8efa3..04151df85b 100644
--- a/console/packages/starwhale-ui/src/Tooltip/Tooltip.tsx
+++ b/console/packages/starwhale-ui/src/Tooltip/Tooltip.tsx
@@ -2,7 +2,16 @@ import { StatefulTooltip } from 'baseui/tooltip'
import IconFont, { IconTypesT } from '../IconFont'
import { themedUseStyletron } from '../theme/styletron'
-function IconTooltip({ content, icon, ...props }: { content: React.ReactNode; icon: IconTypesT }) {
+function IconTooltip({
+ content,
+ icon,
+ iconStyle,
+ ...props
+}: {
+ content: React.ReactNode
+ icon: IconTypesT
+ iconStyle: React.CSSProperties
+}) {
const [css] = themedUseStyletron()
return (
@@ -16,7 +25,7 @@ function IconTooltip({ content, icon, ...props }: { content: React.ReactNode; ic
},
})}
>
-
+
)
diff --git a/console/src/domain/job/components/JobForm.tsx b/console/src/domain/job/components/JobForm.tsx
index c8b8b69425..2752a27ec4 100644
--- a/console/src/domain/job/components/JobForm.tsx
+++ b/console/src/domain/job/components/JobForm.tsx
@@ -99,7 +99,7 @@ export default function JobForm({ job, onSubmit }: IJobFormProps) {
if (!modelVersion) return
setBuiltInRuntime(modelVersion?.builtInRuntime ?? '')
setType(modelVersion?.builtInRuntime ? RuntimeType.BUILTIN : RuntimeType.OTHER)
- }, [RuntimeType.BUILTIN, RuntimeType.OTHER, modelVersion])
+ }, [modelVersion, RuntimeType])
const fullStepSource: StepSpec[] | undefined = React.useMemo(() => {
if (!modelVersion) return undefined
@@ -159,7 +159,7 @@ export default function JobForm({ job, onSubmit }: IJobFormProps) {
setLoading(false)
}
},
- [checkStepSource, stepSpecOverWrites, onSubmit, type, RuntimeType.BUILTIN, stepSource, history]
+ [onSubmit, history, stepSpecOverWrites, stepSource, checkStepSource, type]
)
const handleEditorChange = React.useCallback(
diff --git a/console/src/domain/job/schemas/job.tsx b/console/src/domain/job/schemas/job.tsx
index 2a742327f1..896be46409 100644
--- a/console/src/domain/job/schemas/job.tsx
+++ b/console/src/domain/job/schemas/job.tsx
@@ -38,6 +38,7 @@ export interface IJobSchema extends IResourceSchema {
comment?: string
stopTime?: number
createdTime?: number
+ pinnedTime?: number
exposedLinks?: string[]
}
diff --git a/console/src/domain/job/services/job.ts b/console/src/domain/job/services/job.ts
index 2bb9834baa..7309ffc6fa 100644
--- a/console/src/domain/job/services/job.ts
+++ b/console/src/domain/job/services/job.ts
@@ -52,3 +52,10 @@ export async function executeInTask(
})
return resp.data
}
+
+export async function pinJob(projectId: string, jobId: string, pinned: boolean): Promise {
+ const resp = await axios.post(`/api/v1/project/${projectId}/job/${jobId}/pin`, {
+ pinned,
+ })
+ return resp.data
+}
diff --git a/console/src/i18n/locales.ts b/console/src/i18n/locales.ts
index a985257da0..32c3640f1a 100644
--- a/console/src/i18n/locales.ts
+++ b/console/src/i18n/locales.ts
@@ -301,6 +301,14 @@ const job = {
en: 'Execute Cmd In Task Container',
zh: '在任务容器中执行命令',
},
+ 'job.pin': {
+ en: 'Pin the job',
+ zh: '置顶',
+ },
+ 'job.unpin': {
+ en: 'Unpin the job',
+ zh: '取消置顶',
+ },
'job.expose.title': {
en: 'Open Expose Service',
zh: '开启外部访问',
diff --git a/console/src/pages/Job/JobListCard.tsx b/console/src/pages/Job/JobListCard.tsx
index 6a7dd19894..4ec3381770 100644
--- a/console/src/pages/Job/JobListCard.tsx
+++ b/console/src/pages/Job/JobListCard.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useState } from 'react'
import Card from '@/components/Card'
-import { createJob, doJobAction } from '@job/services/job'
+import { createJob, doJobAction, pinJob } from '@job/services/job'
import { usePage } from '@/hooks/usePage'
import { ICreateJobSchema, JobActionType, JobStatusType } from '@job/schemas/job'
import JobForm from '@job/components/JobForm'
@@ -17,6 +17,7 @@ import { MonoText } from '@/components/Text'
import JobStatus from '@/domain/job/components/JobStatus'
import Button from '@starwhale/ui/Button'
import { WithCurrentAuth } from '@/api/WithAuth'
+import { IconTooltip } from '@starwhale/ui/Tooltip'
import IconFont from '@starwhale/ui/IconFont'
export default function JobListCard() {
@@ -29,8 +30,8 @@ export default function JobListCard() {
const handleCreateJob = useCallback(
async (data: ICreateJobSchema) => {
await createJob(projectId, data)
- await jobsInfo.refetch()
setIsCreateJobOpen(false)
+ jobsInfo.refetch()
},
[jobsInfo, projectId]
)
@@ -38,12 +39,20 @@ export default function JobListCard() {
async (jobId, type: JobActionType) => {
await doJobAction(projectId, jobId, type)
toaster.positive(t('job action done'), { autoHideDuration: 2000 })
- await jobsInfo.refetch()
setIsCreateJobOpen(false)
+ jobsInfo.refetch()
},
[jobsInfo, projectId, t]
)
+ const handlePin = useCallback(
+ async (jobId, pinned) => {
+ await pinJob(projectId, jobId, pinned)
+ jobsInfo.refetch()
+ },
+ [jobsInfo, projectId]
+ )
+
return (
<>
- {job.uuid}
- ,
+
+
+
+ {job.id}
+
+
,
job.resourcePool,
job.modelName,
{job.modelVersion},
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/JobApi.java b/server/controller/src/main/java/ai/starwhale/mlops/api/JobApi.java
index 21fe77ad23..58f7d07e38 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/api/JobApi.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/JobApi.java
@@ -19,6 +19,7 @@
import ai.starwhale.mlops.api.protocol.ResponseMessage;
import ai.starwhale.mlops.api.protocol.job.ExecRequest;
import ai.starwhale.mlops.api.protocol.job.ExecResponse;
+import ai.starwhale.mlops.api.protocol.job.JobModifyPinRequest;
import ai.starwhale.mlops.api.protocol.job.JobModifyRequest;
import ai.starwhale.mlops.api.protocol.job.JobRequest;
import ai.starwhale.mlops.api.protocol.job.JobVo;
@@ -204,7 +205,18 @@ ResponseEntity> modifyJobComment(
schema = @Schema())
@PathVariable("jobUrl")
String jobUrl,
- @Valid @RequestBody JobModifyRequest jobRequest);
+ @Valid @RequestBody JobModifyRequest jobRequest);
+
+ @Operation(summary = "Pin Job")
+ @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "ok") })
+ @PostMapping(value = "/project/{projectUrl}/job/{jobUrl}/pin", produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("hasAnyRole('OWNER', 'MAINTAINER')")
+ ResponseEntity> modifyJobPinStatus(
+ @Parameter(in = ParameterIn.PATH, description = "Project url", schema = @Schema())
+ @PathVariable("projectUrl") String projectUrl,
+ @Parameter(in = ParameterIn.PATH, description = "Job id or uuid", required = true, schema = @Schema())
+ @PathVariable("jobUrl") String jobUrl,
+ @Valid @RequestBody JobModifyPinRequest jobRequest);
@Operation(summary = "DAG of Job")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "ok")})
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/JobController.java b/server/controller/src/main/java/ai/starwhale/mlops/api/JobController.java
index 6f91f48069..3ba976333e 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/api/JobController.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/JobController.java
@@ -20,6 +20,7 @@
import ai.starwhale.mlops.api.protocol.ResponseMessage;
import ai.starwhale.mlops.api.protocol.job.ExecRequest;
import ai.starwhale.mlops.api.protocol.job.ExecResponse;
+import ai.starwhale.mlops.api.protocol.job.JobModifyPinRequest;
import ai.starwhale.mlops.api.protocol.job.JobModifyRequest;
import ai.starwhale.mlops.api.protocol.job.JobRequest;
import ai.starwhale.mlops.api.protocol.job.JobVo;
@@ -197,6 +198,21 @@ public ResponseEntity> modifyJobComment(
return ResponseEntity.ok(Code.success.asResponse("success"));
}
+ @Override
+ public ResponseEntity> modifyJobPinStatus(
+ String projectUrl,
+ String jobUrl,
+ JobModifyPinRequest jobRequest
+ ) {
+ Boolean res = jobService.updateJobPinStatus(projectUrl, jobUrl, jobRequest.isPinned());
+
+ if (!res) {
+ throw new StarwhaleApiException(new SwProcessException(ErrorType.DB, "Update job pin status failed."),
+ HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+ return ResponseEntity.ok(Code.success.asResponse("success"));
+ }
+
@Override
public ResponseEntity> getJobDag(String projectUrl, String jobUrl) {
return ResponseEntity.ok(Code.success.asResponse(dagQuerier.dagOfJob(jobUrl)));
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobModifyPinRequest.java b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobModifyPinRequest.java
new file mode 100644
index 0000000000..8a1dd16f4b
--- /dev/null
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobModifyPinRequest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonProperty;
+import javax.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.validation.annotation.Validated;
+
+@Data
+@Validated
+public class JobModifyPinRequest {
+
+ @NotNull
+ @JsonProperty("pinned")
+ private boolean pinned;
+}
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobRequest.java b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobRequest.java
index 629ee73785..34c9833264 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobRequest.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/api/protocol/job/JobRequest.java
@@ -65,4 +65,5 @@ public class JobRequest implements Serializable {
private DevWay devWay = DevWay.VS_CODE;
private Long timeToLiveInSec;
+
}
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 a823dccd03..404c82d2e7 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
@@ -95,4 +95,7 @@ public Long getDuration() {
return stopTime - createdTime;
}
+ @JsonProperty("pinnedTime")
+ private Long pinnedTime;
+
}
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobDao.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobDao.java
index c9da0032c5..3db81a6443 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobDao.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobDao.java
@@ -30,6 +30,7 @@
import ai.starwhale.mlops.domain.job.storage.JobRepo;
import ai.starwhale.mlops.exception.SwValidationException;
import ai.starwhale.mlops.exception.api.StarwhaleApiException;
+import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Set;
@@ -176,6 +177,16 @@ public Job findJob(String jobUrl) {
}
}
+ public boolean updateJobPinStatus(String jobUrl, boolean pinned) {
+ Date pinnedTime = pinned ? Date.from(Instant.now()) : null;
+
+ if (idConvertor.isId(jobUrl)) {
+ return jobMapper.updateJobPinStatus(idConvertor.revert(jobUrl), pinnedTime) > 0;
+ } else {
+ return jobMapper.updateJobPinStatusByUuid(jobUrl, pinnedTime) > 0;
+ }
+ }
+
@Override
public BundleEntity findById(Long id) {
return jobMapper.findJobById(id);
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobService.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobService.java
index f2eed13bbf..c0e3d326c9 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobService.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/JobService.java
@@ -165,6 +165,10 @@ public Boolean updateJobComment(String projectUrl, String jobUrl, String comment
return jobDao.updateJobComment(jobUrl, comment);
}
+ public Boolean updateJobPinStatus(String projectUrl, String jobUrl, Boolean pinned) {
+ return jobDao.updateJobPinStatus(jobUrl, pinned);
+ }
+
public Boolean removeJob(String projectUrl, String jobUrl) {
var job = jobDao.findJob(jobUrl);
Trash trash = Trash.builder()
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/bo/Job.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/bo/Job.java
index b1cd7d503f..eb562728fd 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/bo/Job.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/bo/Job.java
@@ -96,6 +96,8 @@ public class Job extends TimeConcern {
String devPassword;
Date autoReleaseTime;
+ Date pinnedTime;
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobBoConverter.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobBoConverter.java
index 9de219af06..dfcbba6b2f 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobBoConverter.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/converter/JobBoConverter.java
@@ -165,6 +165,7 @@ public Job fromEntity(JobEntity jobEntity) {
.devWay(jobEntity.getDevWay())
.devPassword(jobEntity.getDevPassword())
.autoReleaseTime(jobEntity.getAutoReleaseTime())
+ .pinnedTime(jobEntity.getPinnedTime())
.build();
} catch (JsonProcessingException e) {
throw new SwValidationException(ValidSubject.JOB, e.getMessage());
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 44f1d4fc49..e264d749a1 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
@@ -184,6 +184,7 @@ public JobVo convert(Job job) throws ConvertException {
.duration(job.getDurationMs())
.comment(job.getComment())
.resourcePool(job.getResourcePool().getName())
+ .pinnedTime(job.getPinnedTime().getTime())
.exposedLinks(generateJobExposedLinks(job.getId()))
.build();
}
@@ -191,6 +192,7 @@ public JobVo convert(Job job) throws ConvertException {
public JobVo convert(JobEntity jobEntity) throws ConvertException {
var runtimes = findRuntimeByVersionIds(List.of(jobEntity.getRuntimeVersionId()));
var datasets = findDatasetVersionNamesByJobId(jobEntity.getId());
+ Long pinnedTime = jobEntity.getPinnedTime() != null ? jobEntity.getPinnedTime().getTime() : null;
return JobVo.builder()
.id(idConvertor.convert(jobEntity.getId()))
@@ -207,6 +209,7 @@ public JobVo convert(JobEntity jobEntity) throws ConvertException {
.comment(jobEntity.getComment())
.resourcePool(systemSettingService.queryResourcePool(jobEntity.getResourcePool()).getName())
.exposedLinks(generateJobExposedLinks(jobEntity.getId()))
+ .pinnedTime(pinnedTime)
.build();
}
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/mapper/JobMapper.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/mapper/JobMapper.java
index c98b2da6ca..f235c3fdd7 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/mapper/JobMapper.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/mapper/JobMapper.java
@@ -54,4 +54,10 @@ void updateJobFinishedTime(
int recoverJob(@Param("id") Long id);
int recoverJobByUuid(@Param("uuid") String uuid);
+
+ int updateJobPinStatus(@Param("id") Long id, @Param("pinnedTime") Date pinnedTime);
+
+ int updateJobPinStatusByUuid(
+ @Param("uuid") String uuid,
+ @Param("pinnedTime") Date pinnedTime);
}
diff --git a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/po/JobEntity.java b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/po/JobEntity.java
index f21d990613..18f2784775 100644
--- a/server/controller/src/main/java/ai/starwhale/mlops/domain/job/po/JobEntity.java
+++ b/server/controller/src/main/java/ai/starwhale/mlops/domain/job/po/JobEntity.java
@@ -85,6 +85,8 @@ public class JobEntity extends BaseEntity implements BundleEntity {
private String devPassword;
private Date autoReleaseTime;
+ private Date pinnedTime;
+
@Override
public String getName() {
return jobUuid;
diff --git a/server/controller/src/main/resources/db/migration/v0_4_0/V0_4_0_007__add_job_pin.sql b/server/controller/src/main/resources/db/migration/v0_4_0/V0_4_0_007__add_job_pin.sql
new file mode 100644
index 0000000000..3fc3f16122
--- /dev/null
+++ b/server/controller/src/main/resources/db/migration/v0_4_0/V0_4_0_007__add_job_pin.sql
@@ -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.
+ */
+
+
+ALTER TABLE job_info
+ ADD pinned_time DATETIME NULL;
+
+create index pinned_time_index
+ on job_info (pinned_time);
diff --git a/server/controller/src/main/resources/mapper/JobMapper.xml b/server/controller/src/main/resources/mapper/JobMapper.xml
index 096cd2c4a0..3e7a96465b 100644
--- a/server/controller/src/main/resources/mapper/JobMapper.xml
+++ b/server/controller/src/main/resources/mapper/JobMapper.xml
@@ -31,6 +31,7 @@
j.dev_way,
j.dev_password,
j.auto_release_time,
+ j.pinned_time,
p.project_name,
p.is_deleted as project_is_deleted,
p.is_default as project_is_default,
@@ -70,7 +71,7 @@
and model_version_id = #{modelId}
- order by job_id desc
+ order by pinned_time desc, job_id desc