feat: 提测通知 + 测试全通过自动完成开发任务
1. TestCaseTab 显示提测状态横幅: - 绿色:开发已全部提测,可以开始测试 - 橙色:开发进行中,显示提测进度 (x/y) 2. TestCase 全部通过 + Bug 全关闭时,自动将版本下所有 submitted 状态的 DevTask 流转为 done(无需手动操作) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useMemo, useEffect } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { Plus, ClipboardCheck, Filter } from 'lucide-react';
|
import { Plus, ClipboardCheck, Filter, CheckCircle2 } from 'lucide-react';
|
||||||
import { useTestCaseStore } from '@/stores/useTestCaseStore';
|
import { useTestCaseStore } from '@/stores/useTestCaseStore';
|
||||||
import { useBugStore } from '@/stores/useBugStore';
|
import { useBugStore } from '@/stores/useBugStore';
|
||||||
|
import { useDevTaskStore } from '@/stores/useDevTaskStore';
|
||||||
import { useRequirementStore } from '@/stores/useRequirementStore';
|
import { useRequirementStore } from '@/stores/useRequirementStore';
|
||||||
import { useAuthStore } from '@/stores/useAuthStore';
|
import { useAuthStore } from '@/stores/useAuthStore';
|
||||||
import { TestCaseRow } from './TestCaseRow';
|
import { TestCaseRow } from './TestCaseRow';
|
||||||
@@ -22,6 +23,7 @@ interface Props {
|
|||||||
export function TestCaseTab({ versionId, requirementIds }: Props) {
|
export function TestCaseTab({ versionId, requirementIds }: Props) {
|
||||||
const { testCases, fetchTestCases } = useTestCaseStore();
|
const { testCases, fetchTestCases } = useTestCaseStore();
|
||||||
const { bugs, fetchBugs } = useBugStore();
|
const { bugs, fetchBugs } = useBugStore();
|
||||||
|
const { tasks: devTasks } = useDevTaskStore();
|
||||||
const { requirements } = useRequirementStore();
|
const { requirements } = useRequirementStore();
|
||||||
const user = useAuthStore((s) => s.user);
|
const user = useAuthStore((s) => s.user);
|
||||||
|
|
||||||
@@ -30,9 +32,13 @@ export function TestCaseTab({ versionId, requirementIds }: Props) {
|
|||||||
|
|
||||||
const versionCases = useMemo(
|
const versionCases = useMemo(
|
||||||
() => testCases.filter((c) => c.versionId === versionId),
|
() => testCases.filter((c) => c.versionId === versionId),
|
||||||
[testCases, requirementIds],
|
[testCases, versionId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 提测状态判断
|
||||||
|
const versionDevTasks = useMemo(() => devTasks.filter((t) => requirementIds.includes(t.requirementId)), [devTasks, requirementIds]);
|
||||||
|
const allSubmitted = versionDevTasks.length > 0 && versionDevTasks.every((t) => t.status === 'submitted' || t.status === 'done');
|
||||||
|
|
||||||
const stats = calcTestProgress(versionCases);
|
const stats = calcTestProgress(versionCases);
|
||||||
|
|
||||||
// 筛选
|
// 筛选
|
||||||
@@ -73,6 +79,19 @@ export function TestCaseTab({ versionId, requirementIds }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
{/* 提测通知 */}
|
||||||
|
{allSubmitted && (
|
||||||
|
<div className="flex items-center gap-2 px-4 py-2.5 rounded-lg bg-emerald-50 border border-emerald-200">
|
||||||
|
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
|
||||||
|
<span className="text-[12px] font-medium text-emerald-700">开发已全部提测,可以开始测试</span>
|
||||||
|
{versionDevTasks.length > 0 && <span className="text-[11px] text-emerald-600 ml-auto">{versionDevTasks.length} 个开发任务已交付</span>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{versionDevTasks.length > 0 && !allSubmitted && (
|
||||||
|
<div className="flex items-center gap-2 px-4 py-2.5 rounded-lg bg-orange-50 border border-orange-200">
|
||||||
|
<span className="text-[12px] text-orange-700">开发进行中,尚未全部提测({versionDevTasks.filter((t) => t.status === 'submitted' || t.status === 'done').length}/{versionDevTasks.length})</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* 统计栏 */}
|
{/* 统计栏 */}
|
||||||
<div className="flex items-center gap-4 px-4 py-3 rounded-lg bg-[var(--bg-subtle)] border border-[var(--line)]">
|
<div className="flex items-center gap-4 px-4 py-3 rounded-lg bg-[var(--bg-subtle)] border border-[var(--line)]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -83,6 +83,26 @@ export const useTestCaseStore = create<TestCaseState>((set, get) => ({
|
|||||||
if (to === 'blocked' && extra?.blockReason) patch.blockReason = extra.blockReason;
|
if (to === 'blocked' && extra?.blockReason) patch.blockReason = extra.blockReason;
|
||||||
if (to === 'running') { patch.failReason = undefined; patch.blockReason = undefined; }
|
if (to === 'running') { patch.failReason = undefined; patch.blockReason = undefined; }
|
||||||
get().updateTestCase(id, patch);
|
get().updateTestCase(id, patch);
|
||||||
|
|
||||||
|
// 副作用:当版本所有用例通过 + Bug全关闭 → DevTask 自动完成
|
||||||
|
if (to === 'passed' && tc.versionId) {
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const { useDevTaskStore } = require('@/stores/useDevTaskStore');
|
||||||
|
const { useBugStore } = require('@/stores/useBugStore');
|
||||||
|
const allCases = get().testCases.filter((c) => c.versionId === tc.versionId);
|
||||||
|
const allPassed = allCases.every((c) => c.id === id ? true : c.status === 'passed');
|
||||||
|
if (!allPassed) return;
|
||||||
|
const allBugs = useBugStore.getState().bugs.filter((b: any) => b.versionId === tc.versionId);
|
||||||
|
const allBugsClosed = allBugs.every((b: any) => b.status === 'closed' || b.status === 'rejected');
|
||||||
|
if (!allBugsClosed) return;
|
||||||
|
const devStore = useDevTaskStore.getState();
|
||||||
|
const versionTasks = devStore.tasks.filter((t: any) => t.status === 'submitted');
|
||||||
|
versionTasks.forEach((t: any) => devStore.changeStatus(t.id, 'done'));
|
||||||
|
} catch {}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user