296 lines
7.7 KiB
Vue
296 lines
7.7 KiB
Vue
<template>
|
|
<Dialog
|
|
v-model="show"
|
|
:options="{
|
|
title: '代码审查',
|
|
size: '6xl'
|
|
}"
|
|
>
|
|
<template v-if="getResults" #body-content>
|
|
<div class="container mx-auto px-4">
|
|
<div class="grid gap-3">
|
|
<div class="flex items-center mb-2">
|
|
<div class="flex items-center">
|
|
<span class="text-lg font-medium text-gray-700 mr-2"
|
|
>状态:</span
|
|
>
|
|
<Badge
|
|
:variant="'subtle'"
|
|
size="lg"
|
|
:label="$resources.codeScreening.pg.status"
|
|
:theme="getBadgeTheme"
|
|
/>
|
|
</div>
|
|
<!-- <Button
|
|
iconLeft="check"
|
|
v-if="
|
|
isSystemUser && $resources.codeScreening.pg.status == 'Open'
|
|
"
|
|
:variant="'subtle'"
|
|
theme="gray"
|
|
size="sm"
|
|
class="mr-4 ml-auto"
|
|
@click="approveRelease"
|
|
>
|
|
批准发布
|
|
</Button>
|
|
<Button
|
|
iconLeft="x"
|
|
v-if="
|
|
isSystemUser && $resources.codeScreening.pg.status == 'Open'
|
|
"
|
|
:variant="'subtle'"
|
|
theme="gray"
|
|
size="sm"
|
|
class="mr-4"
|
|
@click="showRejectReleaseDialog = true"
|
|
>
|
|
拒绝发布
|
|
</Button> -->
|
|
</div>
|
|
<Dialog
|
|
v-model="showRejectReleaseDialog"
|
|
:options="{
|
|
title: '确认',
|
|
size: 'xl',
|
|
actions: [
|
|
{
|
|
label: '拒绝发布',
|
|
variant: 'solid',
|
|
onClick: rejectRelease
|
|
}
|
|
]
|
|
}"
|
|
>
|
|
<template #body-content>
|
|
<FormControl
|
|
:type="'textarea'"
|
|
size="sm"
|
|
variant="subtle"
|
|
placeholder="输入拒绝的原因"
|
|
:disabled="false"
|
|
v-model="rejectionReason"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
|
|
<div
|
|
class="card bg-white shadow-md rounded-md overflow-hidden"
|
|
v-for="file in getResults"
|
|
:key="file.name"
|
|
>
|
|
<div class="card-header bg-gray-200 p-3">
|
|
<h2 class="text-md font-semibold text-gray-800">
|
|
{{ file.name }} - {{ file.score }} 个问题
|
|
</h2>
|
|
</div>
|
|
<div class="p-4">
|
|
<div
|
|
class="issues space-y-4"
|
|
v-for="line in file.lines"
|
|
:key="line.context.line_number"
|
|
>
|
|
<div
|
|
class="issue-item p-4 mb-4"
|
|
v-for="issue in line.issues"
|
|
:key="issue.violation"
|
|
>
|
|
<div class="flex items-center mb-3">
|
|
<span class="text-red-600 mr-2">{{ issue.severity }}</span>
|
|
<span class="text-gray-800">({{ issue.violation }})</span>
|
|
<span class="text-orange-500 font-semibold pl-2">
|
|
- {{ issue.match }}</span
|
|
>
|
|
</div>
|
|
<div class="border border-gray-400 p-2 rounded-md">
|
|
<div
|
|
style="background-color: #d1f8d9"
|
|
class="border rounded-md"
|
|
>
|
|
<div
|
|
v-for="(lineText, i) in line.context.lines"
|
|
:key="i"
|
|
:class="{
|
|
'bg-yellow-200 border rounded-md':
|
|
lineText.includes(issue.match) &&
|
|
line.context.line_range[i] ===
|
|
line.context.line_number
|
|
}"
|
|
>
|
|
<code class="p-2 text-sm whitespace-pre-wrap">
|
|
<span>{{ line.context.line_range[i] }}:</span>
|
|
{{ lineText }}
|
|
</code>
|
|
</div>
|
|
</div>
|
|
<div
|
|
v-if="
|
|
getCommentsForLine(file.name, line.context.line_number)
|
|
.length
|
|
"
|
|
>
|
|
<hr class="h-2 mt-2" />
|
|
<div
|
|
class="comment-item mt-2 p-2"
|
|
v-for="comment in getCommentsForLine(
|
|
file.name,
|
|
line.context.line_number
|
|
)"
|
|
:key="comment.name"
|
|
>
|
|
<div class="flex items-center">
|
|
<Avatar
|
|
:shape="'circle'"
|
|
:image="null"
|
|
:label="comment.commented_by"
|
|
size="md"
|
|
/>
|
|
<strong class="text-gray-900 pl-2 pr-2 text-lg">
|
|
{{ comment.commented_by }}
|
|
</strong>
|
|
<Tooltip
|
|
:text="formatTime(comment.time)"
|
|
:placement="'top'"
|
|
>
|
|
<span class="text-gray-600 text-sm">
|
|
{{ $dayjs(comment.time).fromNow() }}
|
|
</span>
|
|
</Tooltip>
|
|
</div>
|
|
<p class="text-gray-800 text-base p-2 ml-6">
|
|
{{ comment.comment }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<hr class="h-2 mt-2" />
|
|
<NewComment
|
|
v-if="$resources.codeScreening.pg.status == 'Open'"
|
|
:approval_request_name="row.approval_request_name"
|
|
:filename="file.name"
|
|
:line_number="line.context.line_number"
|
|
@comment-submitted="handleCommentSubmitted"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<script>
|
|
import NewComment from './NewComment.vue';
|
|
import { toast } from 'vue-sonner';
|
|
|
|
export default {
|
|
components: {
|
|
NewComment
|
|
},
|
|
props: ['row', 'app', 'isSystemUser'],
|
|
data() {
|
|
return {
|
|
show: true,
|
|
showRejectReleaseDialog: false,
|
|
rejectionReason: ''
|
|
};
|
|
},
|
|
computed: {
|
|
getResults() {
|
|
if (this.$resources.codeScreening.pg) {
|
|
const results = JSON.parse(this.$resources.codeScreening.pg.result);
|
|
return results;
|
|
}
|
|
},
|
|
getComments() {
|
|
if (this.$resources.codeScreening.pg) {
|
|
const results = this.$resources.codeScreening.pg.code_comments;
|
|
return results;
|
|
}
|
|
},
|
|
getBadgeTheme() {
|
|
const status = this.$resources.codeScreening.pg.status.toLowerCase();
|
|
if (status === 'open') {
|
|
return 'blue';
|
|
} else if (status === 'approved') {
|
|
return 'green';
|
|
} else {
|
|
return 'red';
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
getCommentsForLine(filename, lineNumber) {
|
|
// 通过匹配文件名和行号过滤评论
|
|
let filteredComments = this.getComments.filter(
|
|
comment =>
|
|
comment.filename === filename && comment.line_number === lineNumber
|
|
);
|
|
|
|
// 按时间降序排序评论(最新的在前)
|
|
filteredComments.sort((a, b) => new Date(a.time) - new Date(b.time));
|
|
|
|
return filteredComments;
|
|
},
|
|
formatTime(time) {
|
|
const date = new Date(time);
|
|
return date.toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
hour12: true
|
|
});
|
|
},
|
|
handleCommentSubmitted() {
|
|
// 数据库写入需要一些时间。即时重新加载无法快速反映更改。理想情况下,缓存应该在客户端更新
|
|
setTimeout(() => {
|
|
this.$resources.codeScreening.reload();
|
|
}, 1200);
|
|
},
|
|
approveRelease() {
|
|
this.$resources.codeScreening.setValue.submit({
|
|
status: 'Approved',
|
|
reviewed_by: this.$team?.pg?.user
|
|
});
|
|
},
|
|
rejectRelease() {
|
|
// 检查拒绝原因是否为空
|
|
if (!this.rejectionReason) {
|
|
toast.error('拒绝原因是必填项');
|
|
return;
|
|
}
|
|
// 如果提供了原因,则继续拒绝
|
|
this.$resources.codeScreening.setValue.submit({
|
|
status: 'Rejected',
|
|
reason_for_rejection: this.rejectionReason,
|
|
reviewed_by: this.$team?.pg?.user
|
|
});
|
|
this.showRejectReleaseDialog = false;
|
|
}
|
|
},
|
|
resources: {
|
|
codeScreening() {
|
|
return {
|
|
type: 'document',
|
|
pagetype: 'App Release Approval Request',
|
|
name: this.row.approval_request_name,
|
|
fields: [
|
|
'name',
|
|
'marketplace_app',
|
|
'screening_status',
|
|
'app_release',
|
|
'status',
|
|
'result',
|
|
'code_comments'
|
|
],
|
|
auto: true
|
|
};
|
|
}
|
|
}
|
|
};
|
|
</script> |