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