feat: comment to test run's case (#390)

This commit is contained in:
kimatata
2026-02-23 12:26:07 +09:00
committed by GitHub
parent 1f4ac0ae7b
commit a9674c81ab
27 changed files with 1045 additions and 40 deletions

View File

@@ -15,7 +15,7 @@ import {
SortDescriptor,
Chip,
} from '@heroui/react';
import { ChevronDown, MoreVertical, CopyPlus, CopyMinus } from 'lucide-react';
import { ChevronDown, MoreVertical, CopyPlus, CopyMinus, MessageCircle } from 'lucide-react';
import RunCaseStatus from './RunCaseStatus';
import { Link, NextUiLinkClasses } from '@/src/i18n/routing';
import { testRunCaseStatus } from '@/config/selection';
@@ -64,6 +64,7 @@ export default function TestCaseSelector({
{ name: messages.priority, uid: 'priority', sortable: true },
{ name: messages.tags, uid: 'tags', sortable: false },
{ name: messages.status, uid: 'runStatus', sortable: true },
{ name: messages.comments, uid: 'comments', sortable: false },
{ name: messages.actions, uid: 'actions' },
];
@@ -115,6 +116,7 @@ export default function TestCaseSelector({
const cellValue = testCase[columnKey as keyof CaseType];
const isIncluded = isCaseIncluded(testCase);
const runStatus = testCase.RunCases && testCase.RunCases.length > 0 ? testCase.RunCases[0].status : 0;
const commentCount = testCase.RunCases && testCase.RunCases.length > 0 ? testCase.RunCases[0].commentCount || 0 : 0;
switch (columnKey) {
case 'title':
@@ -179,6 +181,24 @@ export default function TestCaseSelector({
</DropdownMenu>
</Dropdown>
);
case 'comments':
return (
<div className={isIncluded ? '' : notIncludedCaseClass}>
{isIncluded && commentCount > 0 ? (
<Link
href={`/projects/${projectId}/runs/${runId}/cases/${testCase.id}?tab=comments`}
locale={locale}
className="flex items-center gap-1"
onPointerDown={(e) => e.stopPropagation()}
>
<MessageCircle size={16} />
<span>{commentCount}</span>
</Link>
) : (
<span className="text-default-400">-</span>
)}
</div>
);
case 'actions':
return (
<Dropdown>

View File

@@ -1,7 +1,7 @@
'use client';
import { useEffect, useState, useContext } from 'react';
import { Tabs, Tab, Chip } from '@heroui/react';
import { useSearchParams } from 'next/navigation';
import { Tabs, Tab } from '@heroui/react';
import CaseDetail from './CaseDetail';
import Comments from '@/components/Comments';
import History from '@/components/History';
@@ -9,30 +9,50 @@ import { TokenContext } from '@/utils/TokenProvider';
import { fetchCase } from '@/utils/caseControl';
import { logError } from '@/utils/errorHandler';
import type { CaseType, StepType } from '@/types/case';
import type { RunDetailMessages } from '@/types/run';
import type { RunCaseType, RunDetailMessages } from '@/types/run';
import type { PriorityMessages } from '@/types/priority';
import type { TestTypeMessages } from '@/types/testType';
import type { CommentMessages } from '@/types/comment';
type Props = {
projectId: string;
runId: string;
locale: string;
caseId: string;
messages: RunDetailMessages;
testTypeMessages: TestTypeMessages;
priorityMessages: PriorityMessages;
commentMessages: CommentMessages;
};
export default function TestCaseDetailPane({
projectId,
runId,
locale,
caseId,
messages,
testTypeMessages,
priorityMessages,
commentMessages,
}: Props) {
const context = useContext(TokenContext);
const searchParams = useSearchParams();
const [selectedTab, setSelectedTab] = useState('caseDetail');
const [isFetching, setIsFetching] = useState(false);
const [testCase, setTestCase] = useState<CaseType | null>(null);
const [runCaseId, setRunCaseId] = useState<number | undefined>(undefined);
useEffect(() => {
// if the url has ?tab=comments, then select the comments tab
const tab = searchParams.get('tab');
if (tab === 'comments') {
setSelectedTab('comments');
} else if (tab === 'history') {
setSelectedTab('history');
} else {
setSelectedTab('caseDetail');
}
}, [searchParams]);
useEffect(() => {
async function fetchDataEffect() {
@@ -46,6 +66,14 @@ export default function TestCaseDetailPane({
data.Steps.sort((a: StepType, b: StepType) => a.caseSteps.stepNo - b.caseSteps.stepNo);
}
setTestCase(data);
// Find the runCase for this case in this run
if (data.RunCases && data.RunCases.length > 0) {
const runCase = data.RunCases.find((rc: RunCaseType) => rc.runId === Number(runId));
if (runCase) {
setRunCaseId(runCase.id);
}
}
} catch (error: unknown) {
logError('Error fetching case data', error);
} finally {
@@ -54,15 +82,20 @@ export default function TestCaseDetailPane({
}
fetchDataEffect();
}, [context, caseId]);
}, [context, caseId, runId]);
if (isFetching || !testCase) {
return <div>loading...</div>;
} else {
return (
<div className="flex w-full flex-col p-3">
<Tabs aria-label="Options" size="sm">
<Tab key="caseDetail" title="Case Detail">
<div className="flex h-full w-full flex-col p-3">
<Tabs
aria-label="Options"
size="sm"
selectedKey={selectedTab}
onSelectionChange={(key) => setSelectedTab(String(key))}
>
<Tab key="caseDetail" title={messages.caseDetail}>
<CaseDetail
projectId={projectId}
testCase={testCase}
@@ -72,20 +105,15 @@ export default function TestCaseDetailPane({
priorityMessages={priorityMessages}
/>
</Tab>
<Tab
key="comments"
title={
<div className="flex items-center space-x-2">
<span>Comments</span>
<Chip size="sm" variant="faded">
3
</Chip>
</div>
}
>
<Comments />
<Tab key="comments" title={messages.comments}>
<Comments
projectId={projectId}
commentableType="RunCase"
commentableId={runCaseId}
messages={commentMessages}
/>
</Tab>
<Tab key="history" title="History">
<Tab key="history" title={messages.history}>
<History />
</Tab>
</Tabs>

View File

@@ -21,6 +21,9 @@ export default function Page({
preconditions: t('preconditions'),
expectedResult: t('expected_result'),
detailsOfTheStep: t('details_of_the_step'),
caseDetail: t('case_detail'),
comments: t('comments'),
history: t('history'),
};
const pt = useTranslations('Priority');
@@ -48,14 +51,33 @@ export default function Page({
manual: tt('manual'),
};
const ct = useTranslations('Comments');
const commentMessages = {
comments: ct('comments'),
noComments: ct('no_comments'),
addComment: ct('add_comment'),
save: ct('save'),
cancel: ct('cancel'),
placeholder: ct('placeholder'),
notIncludedInRun: ct('not_included_in_run'),
commentAdded: ct('comment_added'),
failedToAddComment: ct('failed_to_add_comment'),
commentUpdated: ct('comment_updated'),
failedToUpdateComment: ct('failed_to_update_comment'),
commentDeleted: ct('comment_deleted'),
failedToDeleteComment: ct('failed_to_delete_comment'),
};
return (
<DetailPane
projectId={params.projectId}
runId={params.runId}
caseId={params.caseId}
locale={params.locale}
messages={messages}
priorityMessages={priorityMessages}
testTypeMessages={testTypeMessages}
commentMessages={commentMessages}
/>
);
}

View File

@@ -51,6 +51,7 @@ export default function RunLayout({
selected: t('selected'),
tags: t('tags'),
selectTags: t('select_tags'),
comments: t('comments'),
};
const rst = useTranslations('RunStatus');