Skip to content

Commit

Permalink
feat: support job pin/unpin (#2386)
Browse files Browse the repository at this point in the history
  • Loading branch information
waynelwz authored Jun 26, 2023
1 parent f4cab6d commit fe1f3e3
Show file tree
Hide file tree
Showing 30 changed files with 253 additions and 23 deletions.
14 changes: 11 additions & 3 deletions console/packages/starwhale-ui/src/IconFont/fonts/iconfont.css
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -13,6 +13,14 @@
-moz-osx-font-smoothing: grayscale;
}

.icon-top2:before {
content: "\e66c";
}

.icon-top:before {
content: "\e66d";
}

.icon-vscode:before {
content: "\e66b";
}
Expand Down

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions console/packages/starwhale-ui/src/IconFont/fonts/iconfont.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions console/packages/starwhale-ui/src/IconFont/fonts/iconfont.ttf
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
2 changes: 2 additions & 0 deletions console/packages/starwhale-ui/src/IconFont/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ export type IconTypesT =
| 'WeChat2'
| 'vscode'
| 'docker'
| 'top'
| 'top2'

interface IIconFontProps {
style?: React.CSSProperties
Expand Down
13 changes: 11 additions & 2 deletions console/packages/starwhale-ui/src/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -16,7 +25,7 @@ function IconTooltip({ content, icon, ...props }: { content: React.ReactNode; ic
},
})}
>
<IconFont type={icon} />
<IconFont type={icon} style={iconStyle} />
</p>
</StatefulTooltip>
)
Expand Down
4 changes: 2 additions & 2 deletions console/src/domain/job/components/JobForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions console/src/domain/job/schemas/job.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface IJobSchema extends IResourceSchema {
comment?: string
stopTime?: number
createdTime?: number
pinnedTime?: number
exposedLinks?: string[]
}

Expand Down
7 changes: 7 additions & 0 deletions console/src/domain/job/services/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ export async function executeInTask(
})
return resp.data
}

export async function pinJob(projectId: string, jobId: string, pinned: boolean): Promise<IJobSchema> {
const resp = await axios.post<IJobSchema>(`/api/v1/project/${projectId}/job/${jobId}/pin`, {
pinned,
})
return resp.data
}
8 changes: 8 additions & 0 deletions console/src/i18n/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '开启外部访问',
Expand Down
32 changes: 26 additions & 6 deletions console/src/pages/Job/JobListCard.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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() {
Expand All @@ -29,21 +30,29 @@ export default function JobListCard() {
const handleCreateJob = useCallback(
async (data: ICreateJobSchema) => {
await createJob(projectId, data)
await jobsInfo.refetch()
setIsCreateJobOpen(false)
jobsInfo.refetch()
},
[jobsInfo, projectId]
)
const handleAction = useCallback(
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 (
<>
<Card
Expand Down Expand Up @@ -155,9 +164,20 @@ export default function JobListCard() {
}

return [
<TextLink key={job.id} to={`/projects/${projectId}/jobs/${job.id}/actions`}>
<MonoText>{job.uuid}</MonoText>
</TextLink>,
<div key='id' style={{ display: 'flex', gap: '8px' }}>
<Button key='pin' as='link' onClick={() => handlePin(job.id, !job.pinnedTime)}>
<IconTooltip
content={job.pinnedTime ? t('job.unpin') : t('job.pin')}
icon={job.pinnedTime ? 'top' : 'top2'}
iconStyle={{
color: job.pinnedTime ? '#FF9A00' : 'rgba(2,16,43,0.40)',
}}
/>
</Button>
<TextLink key={job.id} to={`/projects/${projectId}/jobs/${job.id}/actions`}>
<MonoText>{job.id}</MonoText>
</TextLink>
</div>,
job.resourcePool,
job.modelName,
<MonoText key='modelVersion'>{job.modelVersion}</MonoText>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -204,7 +205,18 @@ ResponseEntity<ResponseMessage<String>> 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<ResponseMessage<String>> 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")})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -197,6 +198,21 @@ public ResponseEntity<ResponseMessage<String>> modifyJobComment(
return ResponseEntity.ok(Code.success.asResponse("success"));
}

@Override
public ResponseEntity<ResponseMessage<String>> 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<ResponseMessage<Graph>> getJobDag(String projectUrl, String jobUrl) {
return ResponseEntity.ok(Code.success.asResponse(dagQuerier.dagOfJob(jobUrl)));
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ public class JobRequest implements Serializable {
private DevWay devWay = DevWay.VS_CODE;

private Long timeToLiveInSec;

}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,7 @@ public Long getDuration() {
return stopTime - createdTime;
}

@JsonProperty("pinnedTime")
private Long pinnedTime;

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ public class Job extends TimeConcern {
String devPassword;
Date autoReleaseTime;

Date pinnedTime;

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Loading

0 comments on commit fe1f3e3

Please sign in to comment.