feat: added logic to search test cases by title and description (#337)
This commit is contained in:
@@ -17,7 +17,7 @@ export default function (sequelize) {
|
|||||||
Tags.belongsToMany(Case, { through: 'caseTags', foreignKey: 'tagId', otherKey: 'caseId' });
|
Tags.belongsToMany(Case, { through: 'caseTags', foreignKey: 'tagId', otherKey: 'caseId' });
|
||||||
|
|
||||||
router.get('/', verifySignedIn, verifyProjectVisibleFromFolderId, async (req, res) => {
|
router.get('/', verifySignedIn, verifyProjectVisibleFromFolderId, async (req, res) => {
|
||||||
const { folderId, title, priority, type, tag } = req.query;
|
const { folderId, search, priority, type, tag } = req.query;
|
||||||
|
|
||||||
if (!folderId) {
|
if (!folderId) {
|
||||||
return res.status(400).json({ error: 'folderId is required' });
|
return res.status(400).json({ error: 'folderId is required' });
|
||||||
@@ -28,15 +28,18 @@ export default function (sequelize) {
|
|||||||
folderId: folderId,
|
folderId: folderId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (title) {
|
if (search) {
|
||||||
const searchTerm = title.trim();
|
const searchTerm = search.trim();
|
||||||
|
|
||||||
if (searchTerm.length > 100) {
|
if (searchTerm.length > 100) {
|
||||||
return res.status(400).json({ error: 'too long title param' });
|
return res.status(400).json({ error: 'too long search param' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchTerm.length >= 1) {
|
if (searchTerm.length >= 1) {
|
||||||
whereClause[Op.or] = [{ title: { [Op.like]: `%${searchTerm}%` } }];
|
whereClause[Op.or] = [
|
||||||
|
{ title: { [Op.like]: `%${searchTerm}%` } },
|
||||||
|
{ description: { [Op.like]: `%${searchTerm}%` } },
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "No test cases found",
|
"no_cases_found": "No test cases found",
|
||||||
"case_title": "Test Case Title",
|
"case_title": "Test Case Title",
|
||||||
"case_description": "Test Case Description",
|
"case_description": "Test Case Description",
|
||||||
|
"case_title_or_description": "Test case title or description",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"please_enter": "Please enter test case title",
|
"please_enter": "Please enter test case title",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "テストケースがありません",
|
"no_cases_found": "テストケースがありません",
|
||||||
"case_title": "テストケースタイトル",
|
"case_title": "テストケースタイトル",
|
||||||
"case_description": "テストケース詳細",
|
"case_description": "テストケース詳細",
|
||||||
|
"case_title_or_description": "テストケースのタイトルまたは説明",
|
||||||
"create": "作成",
|
"create": "作成",
|
||||||
"please_enter": "テストケースタイトルを入力してください",
|
"please_enter": "テストケースタイトルを入力してください",
|
||||||
"filter": "フィルター",
|
"filter": "フィルター",
|
||||||
|
|||||||
@@ -223,6 +223,7 @@
|
|||||||
"no_cases_found": "Nenhum caso de teste encontrado",
|
"no_cases_found": "Nenhum caso de teste encontrado",
|
||||||
"case_title": "Título do Caso de Teste",
|
"case_title": "Título do Caso de Teste",
|
||||||
"case_description": "Descrição do Caso de Teste",
|
"case_description": "Descrição do Caso de Teste",
|
||||||
|
"case_title_or_description": "Título ou descrição do caso de teste",
|
||||||
"create": "Criar",
|
"create": "Criar",
|
||||||
"please_enter": "Por favor, insira o título do caso de teste",
|
"please_enter": "Por favor, insira o título do caso de teste",
|
||||||
"filter": "Filtrar",
|
"filter": "Filtrar",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default function CasesPane({
|
|||||||
}: Props) {
|
}: Props) {
|
||||||
const [cases, setCases] = useState<CaseType[]>([]);
|
const [cases, setCases] = useState<CaseType[]>([]);
|
||||||
const [isCaseDialogOpen, setIsCaseDialogOpen] = useState(false);
|
const [isCaseDialogOpen, setIsCaseDialogOpen] = useState(false);
|
||||||
const [titleFilter, setTitleFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const [priorityFilter, setPriorityFilter] = useState<number[]>([]);
|
const [priorityFilter, setPriorityFilter] = useState<number[]>([]);
|
||||||
const [typeFilter, setTypeFilter] = useState<number[]>([]);
|
const [typeFilter, setTypeFilter] = useState<number[]>([]);
|
||||||
const [tagFilter, setTagFilter] = useState<number[]>([]);
|
const [tagFilter, setTagFilter] = useState<number[]>([]);
|
||||||
@@ -45,13 +45,13 @@ export default function CasesPane({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const updateUrlParams = (updates: { title?: string; priority?: number[]; type?: number[]; tag?: number[] }) => {
|
const updateUrlParams = (updates: { search?: string; priority?: number[]; type?: number[]; tag?: number[] }) => {
|
||||||
const currentParams = new URLSearchParams(searchParams.toString());
|
const currentParams = new URLSearchParams(searchParams.toString());
|
||||||
|
|
||||||
if (updates.title) {
|
if (updates.search) {
|
||||||
currentParams.set('title', updates.title);
|
currentParams.set('search', updates.search);
|
||||||
} else {
|
} else {
|
||||||
currentParams.delete('title');
|
currentParams.delete('search');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates.priority && updates.priority.length > 0) {
|
if (updates.priority && updates.priority.length > 0) {
|
||||||
@@ -80,12 +80,12 @@ export default function CasesPane({
|
|||||||
async function fetchDataEffect() {
|
async function fetchDataEffect() {
|
||||||
if (!context.isSignedIn()) return;
|
if (!context.isSignedIn()) return;
|
||||||
|
|
||||||
const titleParam = searchParams.get('title') || '';
|
const searchParam = searchParams.get('search') || '';
|
||||||
const priorityParam = parseQueryParam(searchParams.get('priority'));
|
const priorityParam = parseQueryParam(searchParams.get('priority'));
|
||||||
const typeParam = parseQueryParam(searchParams.get('type'));
|
const typeParam = parseQueryParam(searchParams.get('type'));
|
||||||
const tagParam = parseQueryParam(searchParams.get('tag'));
|
const tagParam = parseQueryParam(searchParams.get('tag'));
|
||||||
|
|
||||||
setTitleFilter(titleParam);
|
setSearchFilter(searchParam);
|
||||||
setPriorityFilter(priorityParam);
|
setPriorityFilter(priorityParam);
|
||||||
setTypeFilter(typeParam);
|
setTypeFilter(typeParam);
|
||||||
setTagFilter(tagParam);
|
setTagFilter(tagParam);
|
||||||
@@ -94,7 +94,7 @@ export default function CasesPane({
|
|||||||
const data = await fetchCases(
|
const data = await fetchCases(
|
||||||
context.token.access_token,
|
context.token.access_token,
|
||||||
Number(folderId),
|
Number(folderId),
|
||||||
titleParam || undefined,
|
searchParam || undefined,
|
||||||
priorityParam.length > 0 ? priorityParam : undefined,
|
priorityParam.length > 0 ? priorityParam : undefined,
|
||||||
typeParam.length > 0 ? typeParam : undefined,
|
typeParam.length > 0 ? typeParam : undefined,
|
||||||
tagParam.length > 0 ? tagParam : undefined
|
tagParam.length > 0 ? tagParam : undefined
|
||||||
@@ -143,12 +143,12 @@ export default function CasesPane({
|
|||||||
await exportCases(context.token.access_token, Number(folderId), type);
|
await exportCases(context.token.access_token, Number(folderId), type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterChange = (title: string, priorities: number[], types: number[], tag: number[]) => {
|
const handleFilterChange = (search: string, priorities: number[], types: number[], tag: number[]) => {
|
||||||
setTitleFilter(title);
|
setSearchFilter(search);
|
||||||
setPriorityFilter(priorities);
|
setPriorityFilter(priorities);
|
||||||
setTypeFilter(types);
|
setTypeFilter(types);
|
||||||
setTagFilter(tag);
|
setTagFilter(tag);
|
||||||
updateUrlParams({ title: title, priority: priorities, type: types, tag: tag });
|
updateUrlParams({ search: search, priority: priorities, type: types, tag: tag });
|
||||||
};
|
};
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@@ -186,7 +186,7 @@ export default function CasesPane({
|
|||||||
onDeleteCases={onDeleteCases}
|
onDeleteCases={onDeleteCases}
|
||||||
onExportCases={onExportCases}
|
onExportCases={onExportCases}
|
||||||
onFilterChange={handleFilterChange}
|
onFilterChange={handleFilterChange}
|
||||||
activeTitleFilter={titleFilter}
|
activeSearchFilter={searchFilter}
|
||||||
activePriorityFilters={priorityFilter}
|
activePriorityFilters={priorityFilter}
|
||||||
activeTypeFilters={typeFilter}
|
activeTypeFilters={typeFilter}
|
||||||
activeTagFilters={tagFilter}
|
activeTagFilters={tagFilter}
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ type TestCaseFilterProps = {
|
|||||||
messages: CasesMessages;
|
messages: CasesMessages;
|
||||||
priorityMessages: PriorityMessages;
|
priorityMessages: PriorityMessages;
|
||||||
testTypeMessages: TestTypeMessages;
|
testTypeMessages: TestTypeMessages;
|
||||||
activeTitleFilter: string;
|
activeSearchFilter: string;
|
||||||
activePriorityFilters: number[];
|
activePriorityFilters: number[];
|
||||||
activeTypeFilters: number[];
|
activeTypeFilters: number[];
|
||||||
activeTagFilters: number[];
|
activeTagFilters: number[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
onFilterChange: (title: string, priorities: number[], types: number[], tags: number[]) => void;
|
onFilterChange: (search: string, priorities: number[], types: number[], tags: number[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Tag = Pick<TagType, 'id' | 'name'>;
|
type Tag = Pick<TagType, 'id' | 'name'>;
|
||||||
@@ -37,7 +37,7 @@ export default function TestCaseFilter({
|
|||||||
messages,
|
messages,
|
||||||
priorityMessages,
|
priorityMessages,
|
||||||
testTypeMessages,
|
testTypeMessages,
|
||||||
activeTitleFilter,
|
activeSearchFilter,
|
||||||
activePriorityFilters,
|
activePriorityFilters,
|
||||||
activeTypeFilters,
|
activeTypeFilters,
|
||||||
activeTagFilters,
|
activeTagFilters,
|
||||||
@@ -45,7 +45,7 @@ export default function TestCaseFilter({
|
|||||||
projectId,
|
projectId,
|
||||||
}: TestCaseFilterProps) {
|
}: TestCaseFilterProps) {
|
||||||
const tokenContext = useContext(TokenContext);
|
const tokenContext = useContext(TokenContext);
|
||||||
const [title, setTitle] = useState<string>('');
|
const [search, setSearch] = useState<string>('');
|
||||||
const [selectedPriorities, setSelectedPriorities] = useState<Selection>(new Set([]));
|
const [selectedPriorities, setSelectedPriorities] = useState<Selection>(new Set([]));
|
||||||
const [selectedTypes, setSelectedTypes] = useState<Selection>(new Set([]));
|
const [selectedTypes, setSelectedTypes] = useState<Selection>(new Set([]));
|
||||||
const [selectedTags, setSelectedTags] = useState<Selection>(new Set([]));
|
const [selectedTags, setSelectedTags] = useState<Selection>(new Set([]));
|
||||||
@@ -74,8 +74,8 @@ export default function TestCaseFilter({
|
|||||||
}, [activeTagFilters]);
|
}, [activeTagFilters]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(activeTitleFilter);
|
setSearch(activeSearchFilter);
|
||||||
}, [activeTitleFilter]);
|
}, [activeSearchFilter]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activePriorityFilters.length > 0) {
|
if (activePriorityFilters.length > 0) {
|
||||||
@@ -125,7 +125,7 @@ export default function TestCaseFilter({
|
|||||||
.filter((id) => !isNaN(id));
|
.filter((id) => !isNaN(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterChange(title, priorityIndices, typeIndices, tagIds);
|
onFilterChange(search, priorityIndices, typeIndices, tagIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClearFilter = () => {
|
const handleClearFilter = () => {
|
||||||
@@ -137,7 +137,7 @@ export default function TestCaseFilter({
|
|||||||
return (
|
return (
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<div className="mb-3 space-y-1">
|
<div className="mb-3 space-y-1">
|
||||||
<h3 className="text-default-500 text-small">{messages.caseTitle}</h3>
|
<h3 className="text-default-500 text-small">{messages.caseTitleOrDescription}</h3>
|
||||||
<Input
|
<Input
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
classNames={{
|
classNames={{
|
||||||
@@ -148,8 +148,8 @@ export default function TestCaseFilter({
|
|||||||
size="sm"
|
size="sm"
|
||||||
startContent={<SearchIcon size={18} />}
|
startContent={<SearchIcon size={18} />}
|
||||||
type="search"
|
type="search"
|
||||||
value={title}
|
value={search}
|
||||||
onValueChange={setTitle}
|
onValueChange={setSearch}
|
||||||
maxLength={100}
|
maxLength={100}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ type Props = {
|
|||||||
onDeleteCases: (caseIds: number[]) => void;
|
onDeleteCases: (caseIds: number[]) => void;
|
||||||
onExportCases: (type: string) => void;
|
onExportCases: (type: string) => void;
|
||||||
onFilterChange: (query: string, priorities: number[], types: number[], tag: number[]) => void;
|
onFilterChange: (query: string, priorities: number[], types: number[], tag: number[]) => void;
|
||||||
activeTitleFilter: string;
|
activeSearchFilter: string;
|
||||||
activePriorityFilters: number[];
|
activePriorityFilters: number[];
|
||||||
activeTypeFilters: number[];
|
activeTypeFilters: number[];
|
||||||
activeTagFilters: number[];
|
activeTagFilters: number[];
|
||||||
@@ -66,7 +66,7 @@ export default function TestCaseTable({
|
|||||||
onDeleteCases,
|
onDeleteCases,
|
||||||
onExportCases,
|
onExportCases,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
activeTitleFilter,
|
activeSearchFilter,
|
||||||
activePriorityFilters,
|
activePriorityFilters,
|
||||||
activeTypeFilters,
|
activeTypeFilters,
|
||||||
activeTagFilters,
|
activeTagFilters,
|
||||||
@@ -105,7 +105,7 @@ export default function TestCaseTable({
|
|||||||
>
|
>
|
||||||
{highlightSearchTerm({
|
{highlightSearchTerm({
|
||||||
text: cellValue as string,
|
text: cellValue as string,
|
||||||
searchTerm: activeTitleFilter,
|
searchTerm: activeSearchFilter,
|
||||||
})}
|
})}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -147,7 +147,7 @@ export default function TestCaseTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[activeTitleFilter]
|
[activeSearchFilter]
|
||||||
);
|
);
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
@@ -155,7 +155,7 @@ export default function TestCaseTable({
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
const [showFilter, setShowFilter] = useState(false);
|
const [showFilter, setShowFilter] = useState(false);
|
||||||
const activeFilterNum =
|
const activeFilterNum =
|
||||||
(activeTitleFilter ? 1 : 0) + activePriorityFilters.length + activeTypeFilters.length + activeTagFilters.length;
|
(activeSearchFilter ? 1 : 0) + activePriorityFilters.length + activeTypeFilters.length + activeTagFilters.length;
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// select test case
|
// select test case
|
||||||
@@ -318,7 +318,7 @@ export default function TestCaseTable({
|
|||||||
messages={messages}
|
messages={messages}
|
||||||
priorityMessages={priorityMessages}
|
priorityMessages={priorityMessages}
|
||||||
testTypeMessages={testTypeMessages}
|
testTypeMessages={testTypeMessages}
|
||||||
activeTitleFilter={activeTitleFilter}
|
activeSearchFilter={activeSearchFilter}
|
||||||
activePriorityFilters={activePriorityFilters}
|
activePriorityFilters={activePriorityFilters}
|
||||||
activeTypeFilters={activeTypeFilters}
|
activeTypeFilters={activeTypeFilters}
|
||||||
activeTagFilters={activeTagFilters}
|
activeTagFilters={activeTagFilters}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export default function Page({ params }: { params: { projectId: string; folderId
|
|||||||
noCasesFound: t('no_cases_found'),
|
noCasesFound: t('no_cases_found'),
|
||||||
caseTitle: t('case_title'),
|
caseTitle: t('case_title'),
|
||||||
caseDescription: t('case_description'),
|
caseDescription: t('case_description'),
|
||||||
|
caseTitleOrDescription: t('case_title_or_description'),
|
||||||
create: t('create'),
|
create: t('create'),
|
||||||
pleaseEnter: t('please_enter'),
|
pleaseEnter: t('please_enter'),
|
||||||
apply: t('apply'),
|
apply: t('apply'),
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ type CasesMessages = {
|
|||||||
noCasesFound: string;
|
noCasesFound: string;
|
||||||
caseTitle: string;
|
caseTitle: string;
|
||||||
caseDescription: string;
|
caseDescription: string;
|
||||||
|
caseTitleOrDescription: string;
|
||||||
create: string;
|
create: string;
|
||||||
pleaseEnter: string;
|
pleaseEnter: string;
|
||||||
filter: string;
|
filter: string;
|
||||||
|
|||||||
@@ -29,15 +29,15 @@ async function fetchCase(jwt: string, caseId: number) {
|
|||||||
async function fetchCases(
|
async function fetchCases(
|
||||||
jwt: string,
|
jwt: string,
|
||||||
folderId: number,
|
folderId: number,
|
||||||
title?: string,
|
search?: string,
|
||||||
priority?: number[],
|
priority?: number[],
|
||||||
type?: number[],
|
type?: number[],
|
||||||
tag?: number[]
|
tag?: number[]
|
||||||
) {
|
) {
|
||||||
const queryParams = [`folderId=${folderId}`];
|
const queryParams = [`folderId=${folderId}`];
|
||||||
|
|
||||||
if (title) {
|
if (search) {
|
||||||
queryParams.push(`title=${title}`);
|
queryParams.push(`search=${search}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (priority && priority.length > 0) {
|
if (priority && priority.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user