diff --git a/backend/routes/users/signin.js b/backend/routes/users/signin.js index 9f16026..3b740f4 100644 --- a/backend/routes/users/signin.js +++ b/backend/routes/users/signin.js @@ -27,9 +27,9 @@ module.exports = function (sequelize) { return res.status(401).json({ error: 'Authentication failed' }); } const accessToken = jwt.sign({ userId: user.id }, secretKey, { - expiresIn: '1h', + expiresIn: '24h', }); - const expiresAt = Date.now() + 3600 * 1000; // expire date(ms) + const expiresAt = Date.now() + 3600 * 1000 * 24; // expire date(ms) res.status(200).json({ access_token: accessToken, expires_at: expiresAt, user }); } catch (error) { diff --git a/backend/routes/users/signup.js b/backend/routes/users/signup.js index ee7ed6e..8398f29 100644 --- a/backend/routes/users/signup.js +++ b/backend/routes/users/signup.js @@ -29,9 +29,9 @@ module.exports = function (sequelize) { }); const accessToken = jwt.sign({ userId: user.id }, secretKey, { - expiresIn: '1h', + expiresIn: '24h', }); - const expiresAt = Date.now() + 3600 * 1000; // expire date(ms) + const expiresAt = Date.now() + 3600 * 1000 * 24; // expire date(ms) user.password = undefined; res.status(200).json({ access_token: accessToken, expires_at: expiresAt, user }); diff --git a/frontend/components/TestCasePriority.tsx b/frontend/components/TestCasePriority.tsx new file mode 100644 index 0000000..bf0e642 --- /dev/null +++ b/frontend/components/TestCasePriority.tsx @@ -0,0 +1,17 @@ +import { Circle } from 'lucide-react'; +import { priorities } from '@/config/selection'; +import { PriorityMessages } from '@/types/priority'; + +type Props = { + priorityValue: number; + priorityMessages: PriorityMessages; +}; + +export default function TestCasePriority({ priorityValue, priorityMessages }: Props) { + return ( +
+ +
{priorityMessages[priorities[priorityValue].uid]}
+
+ ); +} diff --git a/frontend/config/selection.ts b/frontend/config/selection.ts index a4cf6d7..06c2b64 100644 --- a/frontend/config/selection.ts +++ b/frontend/config/selection.ts @@ -1,3 +1,7 @@ +import { RunStatusType, TestRunCaseStatusType } from '@/types/status'; +import { TestTypeType } from '@/types/testType'; +import { PriorityType } from '@/types/priority'; + const roles = [{ uid: 'administrator' }, { uid: 'user' }]; const memberRoles = [{ uid: 'manager' }, { uid: 'developer' }, { uid: 'reporter' }]; @@ -9,14 +13,37 @@ const locales = [ { code: 'ja', name: '日本語' }, ]; -const priorities = [ +// The status of each test run +const testRunStatus: RunStatusType[] = [ + { uid: 'new' }, + { uid: 'inProgress' }, + { uid: 'underReview' }, + { uid: 'rejected' }, + { uid: 'done' }, + { uid: 'closed' }, +]; + +// The status of each test case in test run +const testRunCaseStatus: TestRunCaseStatusType[] = [ + { + uid: 'untested', + color: 'primary', + chartColor: '#3ac6e1', + }, + { uid: 'passed', color: 'success', chartColor: '#6ea56c' }, + { uid: 'failed', color: 'danger', chartColor: '#f15f47' }, + { uid: 'retest', color: 'warning', chartColor: '#fba91e' }, + { uid: 'skipped', color: 'primary', chartColor: '#805aab' }, +]; + +const priorities: PriorityType[] = [ { uid: 'critical', color: '#bb3e03', chartColor: '#bb3e03' }, { uid: 'high', color: '#ca6702', chartColor: '#ca6702' }, { uid: 'medium', color: '#ee9b00', chartColor: '#ee9b00' }, { uid: 'low', color: '#94d2bd', chartColor: '#94d2bd' }, ]; -const testTypes = [ +const testTypes: TestTypeType[] = [ { uid: 'other', chartColor: categoricalPalette[0] }, { uid: 'security', chartColor: categoricalPalette[1] }, { uid: 'performance', chartColor: categoricalPalette[2] }, @@ -41,27 +68,6 @@ const automationStatus = [ const templates = [{ uid: 'text' }, { uid: 'step' }]; -const testRunStatus = [ - { uid: 'new' }, - { uid: 'inProgress' }, - { uid: 'underReview' }, - { uid: 'rejected' }, - { uid: 'done' }, - { uid: 'closed' }, -]; - -const testRunCaseStatus = [ - { - uid: 'untested', - color: 'primary', - chartColor: '#3ac6e1', - }, - { uid: 'passed', color: 'success', chartColor: '#6ea56c' }, - { uid: 'failed', color: 'danger', chartColor: '#f15f47' }, - { uid: 'retest', color: 'warning', chartColor: '#fba91e' }, - { uid: 'skipped', color: 'primary', chartColor: '#805aab' }, -]; - export { roles, memberRoles, diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 92cf712..598c61b 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -1,4 +1,40 @@ { + "RunStatus": { + "new": "New", + "inProgress": "In progress", + "underReview": "Under review", + "rejected": "Rejected", + "done": "Done", + "closed": "Closed" + }, + "RunCaseStatus": { + "untested": "Untested", + "passed": "Passed", + "failed": "Failed", + "retest": "Retest", + "skipped": "Skipped" + }, + "Priority": { + "critical": "Critical", + "high": "High", + "medium": "Medium", + "low": "Low" + }, + "Type": { + "other": "Other", + "security": "Security", + "performance": "Performance", + "accessibility": "Accessibility", + "functional": "Functional", + "acceptance": "Acceptance", + "usability": "Usability", + "smoke_sanity": "Smoke&Sanity", + "compatibility": "Compatibility", + "destructive": "Destructive", + "regression": "Regression", + "automated": "Automated", + "manual": "Manual" + }, "Index": { "get_started": "Get Started", "demo": "Demo", @@ -107,31 +143,9 @@ "test_cases": "Test Cases", "test_runs": "Test Runs", "progress": "Progress", - "untested": "Untested", - "passed": "Passed", - "failed": "Failed", - "retest": "Retest", - "skipped": "Skipped", "test_classification": "Test Classification", "by_type": "By test type", - "by_priority": "By test priority", - "other": "Other", - "security": "Security", - "performance": "Performance", - "accessibility": "Accessibility", - "functional": "Functional", - "acceptance": "Acceptance", - "usability": "Usability", - "smoke_sanity": "Smoke&Sanity", - "compatibility": "Compatibility", - "destructive": "Destructive", - "regression": "Regression", - "automated": "Automated", - "manual": "Manual", - "critical": "Critical", - "high": "High", - "medium": "Medium", - "low": "Low" + "by_priority": "By test priority" }, "Folders": { "folder": "Folder", @@ -160,10 +174,6 @@ "are_you_sure": "Are you sure you want to delete test cases?", "new_test_case": "New Test Case", "status": "Status", - "critical": "Critical", - "high": "High", - "medium": "Medium", - "low": "Low", "no_cases_found": "No test cases found", "case_title": "Test Case Title", "case_description": "Test Case Description", @@ -180,33 +190,16 @@ "description": "Description", "test_case_description": "Test case description", "priority": "Priority", - "critical": "Critical", - "high": "High", - "medium": "Medium", - "low": "Low", "type": "Type", - "other": "Other", - "security": "Security", - "performance": "Performance", - "accessibility": "Accessibility", - "functional": "Functional", - "acceptance": "Acceptance", - "usability": "Usability", - "smoke_sanity": "Smoke&Sanity", - "compatibility": "Compatibility", - "destructive": "Destructive", - "regression": "Regression", - "automated": "Automated", - "manual": "Manual", "template": "Template", "test_detail": "Test detail", "preconditions": "Preconditions", + "expected_result": "Expected result", "step": "Step", "text": "Text", "steps": "Steps", "new_step": "New Step", "details_of_the_step": "Details of the step", - "expected_result": "Expected result", "delete_this_step": "Delete this step", "insert_step": "Insert step", "attachments": "Attachments", @@ -249,30 +242,22 @@ "title": "Title", "please_enter": "Please enter run name", "description": "Description", - "new": "New", - "inProgress": "In progress", - "underReview": "Under review", - "rejected": "Rejected", - "done": "Done", - "closed": "Closed", "priority": "Priority", "status": "Status", "actions": "Actions", - "critical": "Critical", - "high": "High", - "medium": "Medium", - "low": "Low", - "untested": "Untested", - "passed": "Passed", - "failed": "Failed", - "retest": "Retest", - "skipped": "Skipped", "select_test_case": "Select test cases", "test_case_selection": "Test case selection", "include_in_run": "Include in run", "exclude_from_run": "Exclude from run", "no_cases_found": "No cases found", - "are_you_sure_leave": "Are you sure you want to leave the page?" + "are_you_sure_leave": "Are you sure you want to leave the page?", + "type": "Type", + "test_detail": "Test detail", + "steps": "Steps", + "preconditions": "Preconditions", + "expected_result": "Expected result", + "details_of_the_step": "Details of the step", + "close": "Close" }, "Members": { "member_management": "Member Management", diff --git a/frontend/messages/ja.json b/frontend/messages/ja.json index b12a4e0..9f4567e 100644 --- a/frontend/messages/ja.json +++ b/frontend/messages/ja.json @@ -1,4 +1,41 @@ { + "RunStatus": { + "new": "新規", + "inProgress": "進行中", + "underReview": "レビュー中", + "rejected": "却下", + "done": "完了", + "closed": "クローズ" + }, + "RunCaseStatus": { + "untested": "未実行", + "passed": "成功", + "failed": "失敗", + "retest": "再テスト", + "skipped": "スキップ" + }, + "Priority": { + "priority": "優先度", + "critical": "致", + "high": "高", + "medium": "中", + "low": "低" + }, + "Type": { + "other": "その他", + "security": "セキュリティ", + "performance": "パフォーマンス", + "accessibility": "アクセシビリティ", + "functional": "機能", + "acceptance": "受け入れ", + "usability": "ユーザビリティ", + "smoke_sanity": "スモーク/サニティ", + "compatibility": "互換性", + "destructive": "破壊", + "regression": "回帰", + "automated": "自動", + "manual": "手動" + }, "Index": { "get_started": "テスト管理を始める", "demo": "デモ", @@ -107,31 +144,9 @@ "test_cases": "テストケース", "test_runs": "テストラン", "progress": "進捗", - "untested": "未実行", - "passed": "成功", - "failed": "失敗", - "retest": "再テスト", - "skipped": "スキップ", "test_classification": "テスト分類", "by_type": "タイプ別", - "by_priority": "優先度別", - "other": "その他", - "security": "セキュリティ", - "performance": "パフォーマンス", - "accessibility": "アクセシビリティ", - "functional": "機能", - "acceptance": "受け入れ", - "usability": "ユーザビリティ", - "smoke_sanity": "スモーク/サニティ", - "compatibility": "互換性", - "destructive": "破壊", - "regression": "回帰", - "automated": "自動", - "manual": "手動", - "critical": "致", - "high": "高", - "medium": "中", - "low": "低" + "by_priority": "優先度別" }, "Folders": { "folder": "フォルダー", @@ -160,10 +175,6 @@ "are_you_sure": "テストケースを削除してもよろしいですか?", "new_test_case": "新規テストケース", "status": "ステータス", - "critical": "致", - "high": "高", - "medium": "中", - "low": "低", "no_cases_found": "テストケースがありません", "case_title": "テストケースタイトル", "case_description": "テストケース詳細", @@ -180,33 +191,16 @@ "description": "詳細", "test_case_description": "テストケース詳細", "priority": "優先度", - "critical": "致", - "high": "高", - "medium": "中", - "low": "低", "type": "タイプ", - "other": "その他", - "security": "セキュリティ", - "performance": "パフォーマンス", - "accessibility": "アクセシビリティ", - "functional": "機能", - "acceptance": "受け入れ", - "usability": "ユーザビリティ", - "smoke_sanity": "スモーク/サニティ", - "compatibility": "互換性", - "destructive": "破壊", - "regression": "回帰", - "automated": "自動", - "manual": "手動", "template": "テンプレート", "test_detail": "テスト詳細", "preconditions": "前提条件", + "expected_result": "期待結果", "step": "ステップ", "text": "テキスト", "steps": "ステップ", "new_step": "新規ステップ", "details_of_the_step": "ステップ詳細", - "expected_result": "期待結果", "delete_this_step": "このステップを削除", "insert_step": "ステップを挿入", "attachments": "添付ファイル", @@ -248,31 +242,23 @@ "id": "ID", "title": "タイトル", "description": "詳細", - "new": "新規", - "inProgress": "進行中", - "underReview": "レビュー中", - "rejected": "却下", - "done": "完了", - "closed": "クローズ", "please_enter": "タイトルを入力してください", "priority": "優先度", "status": "ステータス", "actions": "アクション", - "critical": "致", - "high": "高", - "medium": "中", - "low": "低", - "untested": "未実行", - "passed": "成功", - "failed": "失敗", - "retest": "再テスト", - "skipped": "スキップ", "select_test_case": "テストケースを選択", "test_case_selection": "テストケース選択", "include_in_run": "テストランに含める", "exclude_from_run": "テストランから除外する", "no_cases_found": "テストケースが見つかりません", - "are_you_sure_leave": "ページを離れてもよろしいですか" + "are_you_sure_leave": "ページを離れてもよろしいですか", + "type": "タイプ", + "test_detail": "テスト詳細", + "steps": "ステップ", + "preconditions": "前提条件", + "expected_result": "期待結果", + "details_of_the_step": "ステップ詳細", + "close": "閉じる" }, "Members": { "member_management": "メンバー管理", diff --git a/frontend/src/app/[locale]/HeaderNavbarMenu.tsx b/frontend/src/app/[locale]/HeaderNavbarMenu.tsx index f787b60..b7c1aa7 100644 --- a/frontend/src/app/[locale]/HeaderNavbarMenu.tsx +++ b/frontend/src/app/[locale]/HeaderNavbarMenu.tsx @@ -143,7 +143,7 @@ export default function HeaderNavbarMenu({ messages, locale }: Props) { {}} /> - setIsMenuOpen(!isMenuOpen)} /> + setIsMenuOpen(!isMenuOpen)} /> @@ -220,7 +220,7 @@ export default function HeaderNavbarMenu({ messages, locale }: Props) { key="signin" startContent={} title={messages.signIn} - onClick={() => { + onPress={() => { router.push('/account/signin', { locale: locale }); setIsMenuOpen(false); }} @@ -229,7 +229,7 @@ export default function HeaderNavbarMenu({ messages, locale }: Props) { key="signup" title={messages.signUp} startContent={} - onClick={() => { + onPress={() => { router.push('/account/signup', { locale: locale }); setIsMenuOpen(false); }} @@ -248,7 +248,7 @@ export default function HeaderNavbarMenu({ messages, locale }: Props) { key={entry.code} startContent={} title={entry.name} - onClick={() => { + onPress={() => { changeLocale(entry.code); setIsMenuOpen(false); }} diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx index bb8bf12..b689d95 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/CasesPane.tsx @@ -6,15 +6,17 @@ import { fetchCases, createCase, deleteCases } from '@/utils/caseControl'; import { CaseType, CasesMessages } from '@/types/case'; import DeleteConfirmDialog from '@/components/DeleteConfirmDialog'; import CaseDialog from './CaseDialog'; +import { PriorityMessages } from '@/types/priority'; type Props = { projectId: string; folderId: string; messages: CasesMessages; + priorityMessages: PriorityMessages; locale: string; }; -export default function CasesPane({ projectId, folderId, messages, locale }: Props) { +export default function CasesPane({ projectId, folderId, messages, priorityMessages, locale }: Props) { const [cases, setCases] = useState([]); const context = useContext(TokenContext); const [isCaseDialogOpen, setIsCaseDialogOpen] = useState(false); @@ -81,6 +83,7 @@ export default function CasesPane({ projectId, folderId, messages, locale }: Pro onDeleteCase={onDeleteCase} onDeleteCases={onDeleteCases} messages={messages} + priorityMessages={priorityMessages} locale={locale} /> diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx index 6de21ac..e4110a1 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/TestCaseTable.tsx @@ -17,7 +17,8 @@ import { import { Link, NextUiLinkClasses } from '@/src/navigation'; import { Plus, MoreVertical, Trash, Circle } from 'lucide-react'; import { CaseType, CasesMessages } from '@/types/case'; -import { priorities } from '@/config/selection'; +import { PriorityMessages } from '@/types/priority'; +import TestCasePriority from '@/components/TestCasePriority'; type Props = { projectId: string; @@ -27,6 +28,7 @@ type Props = { onDeleteCase: (caseId: number) => void; onDeleteCases: (caseIds: number[]) => void; messages: CasesMessages; + priorityMessages: PriorityMessages; locale: string; }; @@ -38,6 +40,7 @@ export default function TestCaseTable({ onDeleteCase, onDeleteCases, messages, + priorityMessages, locale, }: Props) { const headerColumns = [ @@ -89,12 +92,7 @@ export default function TestCaseTable({ ); case 'priority': - return ( -
- -
{messages[priorities[cellValue].uid]}
-
- ); + return ; case 'actions': return ( diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx index d9728ef..c956a9e 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/CaseEditor.tsx @@ -6,12 +6,14 @@ import { Save, Plus, ArrowLeft, Circle } from 'lucide-react'; import { priorities, testTypes, templates } from '@/config/selection'; import CaseStepsEditor from './CaseStepsEditor'; import CaseAttachmentsEditor from './CaseAttachmentsEditor'; -import { CaseType, AttachmentType, CaseMessages, StepType } from '@/types/case'; import { fetchCase, updateCase } from '@/utils/caseControl'; import { updateSteps } from './stepControl'; import { fetchCreateAttachments, fetchDownloadAttachment, fetchDeleteAttachment } from './attachmentControl'; import { TokenContext } from '@/utils/TokenProvider'; import { useFormGuard } from '@/utils/formGuard'; +import { CaseType, AttachmentType, CaseMessages, StepType } from '@/types/case'; +import { PriorityMessages } from '@/types/priority'; +import { TestTypeMessages } from '@/types/testType'; const defaultTestCase = { id: 0, @@ -36,10 +38,20 @@ type Props = { folderId: string; caseId: string; messages: CaseMessages; + testTypeMessages: TestTypeMessages; + priorityMessages: PriorityMessages; locale: string; }; -export default function CaseEditor({ projectId, folderId, caseId, messages, locale }: Props) { +export default function CaseEditor({ + projectId, + folderId, + caseId, + messages, + testTypeMessages, + priorityMessages, + locale, +}: Props) { const context = useContext(TokenContext); const [testCase, setTestCase] = useState(defaultTestCase); const [isTitleInvalid, setIsTitleInvalid] = useState(false); @@ -262,7 +274,7 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca > {priorities.map((priority, index) => ( - {messages[priority.uid]} + {priorityMessages[priority.uid]} ))} @@ -283,7 +295,7 @@ export default function CaseEditor({ projectId, folderId, caseId, messages, loca > {testTypes.map((type, index) => ( - {messages[type.uid]} + {testTypeMessages[type.uid]} ))} diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx index e859a4f..e2ace79 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/[caseId]/page.tsx @@ -1,5 +1,7 @@ +import { PriorityMessages } from '@/types/priority'; import CaseEditor from './CaseEditor'; import { useTranslations } from 'next-intl'; +import { TestTypeMessages } from '@/types/testType'; export default function Page({ params, @@ -22,33 +24,16 @@ export default function Page({ description: t('description'), testCaseDescription: t('test_case_description'), priority: t('priority'), - critical: t('critical'), - high: t('high'), - medium: t('medium'), - low: t('low'), type: t('type'), - other: t('other'), - security: t('security'), - performance: t('performance'), - accessibility: t('accessibility'), - functional: t('functional'), - acceptance: t('acceptance'), - usability: t('usability'), - smokeSanity: t('smoke_sanity'), - compatibility: t('compatibility'), - destructive: t('destructive'), - regression: t('regression'), - automated: t('automated'), - manual: t('manual'), template: t('template'), testDetail: t('test_detail'), preconditions: t('preconditions'), + expectedResult: t('expected_result'), step: t('step'), text: t('text'), steps: t('steps'), newStep: t('new_step'), detailsOfTheStep: t('details_of_the_step'), - expectedResult: t('expected_result'), deleteThisStep: t('delete_this_step'), insertStep: t('insert_step'), attachments: t('attachments'), @@ -61,12 +46,39 @@ export default function Page({ areYouSureLeave: t('are_you_sure_leave'), }; + const tt = useTranslations('Type'); + const testTypeMessages: TestTypeMessages = { + other: tt('other'), + security: tt('security'), + performance: tt('performance'), + accessibility: tt('accessibility'), + functional: tt('functional'), + acceptance: tt('acceptance'), + usability: tt('usability'), + smokeSanity: tt('smoke_sanity'), + compatibility: tt('compatibility'), + destructive: tt('destructive'), + regression: tt('regression'), + automated: tt('automated'), + manual: tt('manual'), + }; + + const priorityTranslation = useTranslations('Priority'); + const priorityMessages: PriorityMessages = { + critical: priorityTranslation('critical'), + high: priorityTranslation('high'), + medium: priorityTranslation('medium'), + low: priorityTranslation('low'), + }; + return ( ); diff --git a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/page.tsx b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/page.tsx index ce43a1c..1ce606c 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/page.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/folders/[folderId]/cases/page.tsx @@ -1,3 +1,4 @@ +import { PriorityMessages } from '@/types/priority'; import CasesPane from './CasesPane'; import { useTranslations } from 'next-intl'; @@ -15,10 +16,6 @@ export default function Page({ params }: { params: { projectId: string; folderId areYouSure: t('are_you_sure'), newTestCase: t('new_test_case'), status: t('status'), - critical: t('critical'), - high: t('high'), - medium: t('medium'), - low: t('low'), noCasesFound: t('no_cases_found'), caseTitle: t('case_title'), caseDescription: t('case_description'), @@ -26,9 +23,23 @@ export default function Page({ params }: { params: { projectId: string; folderId pleaseEnter: t('please_enter'), }; + const priorityTranslation = useTranslations('Priority'); + const priorityMessages: PriorityMessages = { + critical: priorityTranslation('critical'), + high: priorityTranslation('high'), + medium: priorityTranslation('medium'), + low: priorityTranslation('low'), + }; + return ( <> - + ); } diff --git a/frontend/src/app/[locale]/projects/[projectId]/home/ProjectHome.tsx b/frontend/src/app/[locale]/projects/[projectId]/home/ProjectHome.tsx index 06f00b5..4f98ff6 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/home/ProjectHome.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/home/ProjectHome.tsx @@ -8,11 +8,15 @@ import { ProgressSeriesType } from '@/types/run'; import { HomeMessages } from './page'; import { TokenContext } from '@/utils/TokenProvider'; import { aggregateBasicInfo, aggregateTestPriority, aggregateTestType, aggregateProgress } from './aggregate'; +import Config from '@/config/config'; +import { useTheme } from 'next-themes'; import TestTypesChart from './TestTypesDonutChart'; import TestPriorityChart from './TestPriorityDonutChart'; import TestProgressBarChart from './TestProgressColumnChart'; -import Config from '@/config/config'; -import { useTheme } from 'next-themes'; +import { TestRunCaseStatusMessages } from '@/types/status'; +import { TestTypeMessages } from '@/types/testType'; +import { PriorityMessages } from '@/types/priority'; + const apiServer = Config.apiServer; async function fetchProject(jwt: string, projectId: number) { @@ -41,9 +45,18 @@ async function fetchProject(jwt: string, projectId: number) { type Props = { projectId: string; messages: HomeMessages; + testRunCaseStatusMessages: TestRunCaseStatusMessages; + testTypeMessages: TestTypeMessages; + priorityMessages: PriorityMessages; }; -export function ProjectHome({ projectId, messages }: Props) { +export function ProjectHome({ + projectId, + messages, + testRunCaseStatusMessages, + testTypeMessages, + priorityMessages, +}: Props) { const context = useContext(TokenContext); const { theme, setTheme } = useTheme(); const [project, setProject] = useState({ @@ -67,7 +80,7 @@ export function ProjectHome({ projectId, messages }: Props) { } try { - const data = await fetchProject(context.token.access_token, projectId); + const data = await fetchProject(context.token.access_token, Number(projectId)); setProject(data); } catch (error: any) { console.error('Error in effect:', error.message); @@ -90,7 +103,7 @@ export function ProjectHome({ projectId, messages }: Props) { const priorityRet = aggregateTestPriority(project); setPriorityCounts([...priorityRet]); - const { series, categories } = aggregateProgress(project, messages); + const { series, categories } = aggregateProgress(project, testRunCaseStatusMessages); setProgressSeries([...series]); setProgressCategories([...categories]); } @@ -130,11 +143,11 @@ export function ProjectHome({ projectId, messages }: Props) {

{messages.byType}

- +

{messages.byPriority}

- +
diff --git a/frontend/src/app/[locale]/projects/[projectId]/home/TestPriorityDonutChart.tsx b/frontend/src/app/[locale]/projects/[projectId]/home/TestPriorityDonutChart.tsx index 67330e1..757647e 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/home/TestPriorityDonutChart.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/home/TestPriorityDonutChart.tsx @@ -3,16 +3,16 @@ import { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; import { priorities } from '@/config/selection'; import { CasePriorityCountType } from '@/types/case'; -import { HomeMessages } from './page'; +import { PriorityMessages } from '@/types/priority'; const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); type Props = { priorityCounts: CasePriorityCountType[]; - messages: HomeMessages; + priorityMessages: PriorityMessages; theme: string; }; -export default function TestPriorityDonutChart({ priorityCounts, messages, theme }: Props) { +export default function TestPriorityDonutChart({ priorityCounts, priorityMessages, theme }: Props) { const [chartData, setChartData] = useState({ series: [], options: { @@ -29,7 +29,7 @@ export default function TestPriorityDonutChart({ priorityCounts, messages, theme return found ? found.count : 0; }); - const labels = priorities.map((entry) => messages[entry.uid]); + const labels = priorities.map((entry) => priorityMessages[entry.uid]); const colors = priorities.map((entry) => entry.chartColor); const legend = { labels: { diff --git a/frontend/src/app/[locale]/projects/[projectId]/home/TestTypesDonutChart.tsx b/frontend/src/app/[locale]/projects/[projectId]/home/TestTypesDonutChart.tsx index a520162..8a2486b 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/home/TestTypesDonutChart.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/home/TestTypesDonutChart.tsx @@ -3,16 +3,16 @@ import { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; import { testTypes } from '@/config/selection'; import { CaseTypeCountType } from '@/types/case'; -import { HomeMessages } from './page'; +import { TestTypeMessages } from '@/types/testType'; const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); type Props = { typesCounts: CaseTypeCountType[]; - messages: HomeMessages; + testTypeMessages: TestTypeMessages; theme: string; }; -export default function TestTypesDonutChart({ typesCounts, messages, theme }: Props) { +export default function TestTypesDonutChart({ typesCounts, testTypeMessages, theme }: Props) { const [chartData, setChartData] = useState({ series: [], options: { @@ -29,7 +29,7 @@ export default function TestTypesDonutChart({ typesCounts, messages, theme }: Pr return found ? found.count : 0; }); - const labels = testTypes.map((entry) => messages[entry.uid]); + const labels = testTypes.map((entry) => testTypeMessages[entry.uid]); const colors = testTypes.map((entry) => entry.chartColor); const legend = { labels: { diff --git a/frontend/src/app/[locale]/projects/[projectId]/home/aggregate.ts b/frontend/src/app/[locale]/projects/[projectId]/home/aggregate.ts index aca7a3b..41c7ffc 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/home/aggregate.ts +++ b/frontend/src/app/[locale]/projects/[projectId]/home/aggregate.ts @@ -1,6 +1,6 @@ import { ProjectType } from '@/types/project'; import { testTypes, priorities, testRunCaseStatus } from '@/config/selection'; -import { HomeMessages } from './page'; +import { TestRunCaseStatusMessages } from '@/types/status'; // aggregate folder, case, run mum function aggregateBasicInfo(project: ProjectType) { @@ -50,9 +50,9 @@ function aggregateTestPriority(project: ProjectType) { return result; } -function aggregateProgress(project: ProjectType, messages: HomeMessages) { +function aggregateProgress(project: ProjectType, testRunCaseStatusMessages: TestRunCaseStatusMessages) { let series = testRunCaseStatus.map((status) => { - return { name: messages[status.uid], data: [] }; + return { name: testRunCaseStatusMessages[status.uid], data: [] }; }); let categories = []; diff --git a/frontend/src/app/[locale]/projects/[projectId]/home/page.tsx b/frontend/src/app/[locale]/projects/[projectId]/home/page.tsx index 73b4c57..dc862f8 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/home/page.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/home/page.tsx @@ -1,37 +1,17 @@ import { ProjectHome } from './ProjectHome'; import { useTranslations } from 'next-intl'; +import { PriorityMessages } from '@/types/priority'; +import { TestTypeMessages } from '@/types/testType'; +import { TestRunCaseStatusMessages } from '@/types/status'; export type HomeMessages = { folders: string; testCases: string; testRuns: string; progress: string; - untested: string; - passed: string; - failed: string; - retest: string; - skipped: string; testClassification: string; byType: string; byPriority: string; - testTypes: string; - other: string; - security: string; - performance: string; - accessibility: string; - functional: string; - acceptance: string; - usability: string; - smokeSanity: string; - compatibility: string; - destructive: string; - regression: string; - automated: string; - manual: string; - critical: string; - high: string; - medium: string; - low: string; }; export default function Page({ params }: { params: { projectId: string } }) { @@ -41,35 +21,54 @@ export default function Page({ params }: { params: { projectId: string } }) { testCases: t('test_cases'), testRuns: t('test_runs'), progress: t('progress'), - untested: t('untested'), - passed: t('passed'), - failed: t('failed'), - retest: t('retest'), - skipped: t('skipped'), testClassification: t('test_classification'), byType: t('by_type'), byPriority: t('by_priority'), - other: t('other'), - security: t('security'), - performance: t('performance'), - accessibility: t('accessibility'), - functional: t('functional'), - acceptance: t('acceptance'), - usability: t('usability'), - smokeSanity: t('smoke_sanity'), - compatibility: t('compatibility'), - destructive: t('destructive'), - regression: t('regression'), - automated: t('automated'), - manual: t('manual'), - critical: t('critical'), - high: t('high'), - medium: t('medium'), - low: t('low'), }; + + const rcst = useTranslations('RunCaseStatus'); + const testRunCaseStatusMessages: TestRunCaseStatusMessages = { + untested: rcst('untested'), + passed: rcst('passed'), + failed: rcst('failed'), + retest: rcst('retest'), + skipped: rcst('skipped'), + }; + + const tt = useTranslations('Type'); + const testTypeMessages: TestTypeMessages = { + other: tt('other'), + security: tt('security'), + performance: tt('performance'), + accessibility: tt('accessibility'), + functional: tt('functional'), + acceptance: tt('acceptance'), + usability: tt('usability'), + smokeSanity: tt('smoke_sanity'), + compatibility: tt('compatibility'), + destructive: tt('destructive'), + regression: tt('regression'), + automated: tt('automated'), + manual: tt('manual'), + }; + + const pt = useTranslations('Priority'); + const priorityMessages: PriorityMessages = { + critical: pt('critical'), + high: pt('high'), + medium: pt('medium'), + low: pt('low'), + }; + return ( <> - + ); } diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx index 6aa6947..098bc4f 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunEditor.tsx @@ -37,6 +37,9 @@ import { fetchFolders } from '../../folders/foldersControl'; import { TokenContext } from '@/utils/TokenProvider'; import { useTheme } from 'next-themes'; import { useFormGuard } from '@/utils/formGuard'; +import { PriorityMessages } from '@/types/priority'; +import { RunStatusMessages, TestRunCaseStatusMessages } from '@/types/status'; +import { TestTypeMessages } from '@/types/testType'; const defaultTestRun = { id: 0, @@ -53,10 +56,23 @@ type Props = { projectId: string; runId: string; messages: RunMessages; + runStatusMessages: RunStatusMessages; + testRunCaseStatusMessages: TestRunCaseStatusMessages; + priorityMessages: PriorityMessages; + testTypeMessages: TestTypeMessages; locale: string; }; -export default function RunEditor({ projectId, runId, messages, locale }: Props) { +export default function RunEditor({ + projectId, + runId, + messages, + runStatusMessages, + testRunCaseStatusMessages, + priorityMessages, + testTypeMessages, + locale, +}: Props) { const context = useContext(TokenContext); const { theme, setTheme } = useTheme(); const [testRun, setTestRun] = useState(defaultTestRun); @@ -202,7 +218,11 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props) - +
@@ -246,7 +266,7 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props) > {testRunStatus.map((status, index) => ( - {messages[status.uid]} + {runStatusMessages[status.uid]} ))} @@ -310,10 +330,13 @@ export default function RunEditor({ projectId, runId, messages, locale }: Props) isDisabled={!context.isProjectReporter(Number(projectId))} selectedKeys={selectedKeys} onSelectionChange={setSelectedKeys} - onStatusChange={handleChangeStatus} + onChangeStatus={handleChangeStatus} onIncludeCase={(includeTestId) => handleIncludeExcludeCase(true, includeTestId)} onExcludeCase={(excludeCaseId) => handleIncludeExcludeCase(false, excludeCaseId)} messages={messages} + testRunCaseStatusMessages={testRunCaseStatusMessages} + priorityMessages={priorityMessages} + testTypeMessages={testTypeMessages} />
diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx index c492a45..46109c8 100644 --- a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/RunPregressDonutChart.tsx @@ -2,16 +2,17 @@ import React from 'react'; import { useState, useEffect } from 'react'; import dynamic from 'next/dynamic'; import { testRunCaseStatus } from '@/config/selection'; -import { RunStatusCountType, RunMessages } from '@/types/run'; +import { RunStatusCountType } from '@/types/run'; +import { TestRunCaseStatusMessages } from '@/types/status'; const Chart = dynamic(() => import('react-apexcharts'), { ssr: false }); type Props = { statusCounts: RunStatusCountType[]; - messages: RunMessages; + testRunCaseStatusMessages: TestRunCaseStatusMessages; theme: string | undefined; }; -export default function RunProgressDounut({ statusCounts, messages, theme }: Props) { +export default function RunProgressDounut({ statusCounts, testRunCaseStatusMessages, theme }: Props) { const [chartData, setChartData] = useState({ series: [], options: { @@ -28,7 +29,7 @@ export default function RunProgressDounut({ statusCounts, messages, theme }: Pro return found ? found.count : 0; }); - const labels = testRunCaseStatus.map((entry) => messages[entry.uid]); + const labels = testRunCaseStatus.map((entry) => testRunCaseStatusMessages[entry.uid]); const colors = testRunCaseStatus.map((entry) => entry.chartColor); const legend = { labels: { diff --git a/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/TestCaseDetailDialog.tsx b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/TestCaseDetailDialog.tsx new file mode 100644 index 0000000..1d27e70 --- /dev/null +++ b/frontend/src/app/[locale]/projects/[projectId]/runs/[runId]/TestCaseDetailDialog.tsx @@ -0,0 +1,173 @@ +import { useState, useEffect, useContext } from 'react'; +import { Button, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Avatar, Textarea } from '@nextui-org/react'; +import { testTypes, templates } from '@/config/selection'; +import { RunMessages } from '@/types/run'; +import { CaseType, StepType } from '@/types/case'; +import { PriorityMessages } from '@/types/priority'; +import TestCasePriority from '@/components/TestCasePriority'; +import { TokenContext } from '@/utils/TokenProvider'; +import { fetchCase } from '@/utils/caseControl'; +import { TestTypeMessages } from '@/types/testType'; + +type Props = { + isOpen: boolean; + caseId: number; + onCancel: () => void; + onChangeStatus: (changeCaseId: number, status: number) => {}; + messages: RunMessages; + testTypeMessages: TestTypeMessages; + priorityMessages: PriorityMessages; +}; + +const defaultTestCase = { + id: 0, + title: '', + state: 0, + priority: 0, + type: 0, + automationStatus: 0, + description: '', + template: 0, + preConditions: '', + expectedResults: '', + folderId: 0, +}; + +export default function TestCaseDetailDialog({ + isOpen, + caseId, + onCancel, + onChangeStatus, + messages, + testTypeMessages, + priorityMessages, +}: Props) { + const context = useContext(TokenContext); + const [testCase, setTestCase] = useState(defaultTestCase); + + useEffect(() => { + async function fetchDataEffect() { + if (!context.isSignedIn()) { + return; + } + + if (!caseId || caseId <= 0) { + return; + } + + try { + const data = await fetchCase(context.token.access_token, Number(caseId)); + if (data.Steps && data.Steps.length > 0) { + data.Steps.sort((a: StepType, b: StepType) => { + const stepNoA = a.caseSteps.stepNo; + const stepNoB = b.caseSteps.stepNo; + return stepNoA - stepNoB; + }); + } + + setTestCase(data); + } catch (error: any) { + console.error('Error in effect:', error.message); + } + } + + fetchDataEffect(); + }, [context, caseId]); + + return ( + { + onCancel(); + }} + classNames={{ + header: 'border-b-[1px] border-[#e5e5e5]', + body: 'border-b-[1px] border-[#e5e5e5]', + }} + > + + {testCase.title} + +

{messages.description}

+
{testCase.description}
+ +
+
+

{messages.priority}

+ +
+ +
+

{messages.type}

+
{testTypeMessages[testTypes[testCase.type].uid]}
+
+
+
+ + {templates[testCase.template].uid === 'text' ? ( + <> +

{messages.testDetail}

+
+
+