diff --git a/next/api/src/router/ticket.ts b/next/api/src/router/ticket.ts index 2aba13ce8..9da8ead1c 100644 --- a/next/api/src/router/ticket.ts +++ b/next/api/src/router/ticket.ts @@ -72,11 +72,6 @@ export const ticketFiltersSchema = yup.object({ privateTagValue: yup.string(), language: yup.csv(yup.string().required()), - // fields - // TODO: use enum - fieldName: yup.string(), - fieldValue: yup.string(), - // pagination page: yup.number().integer().min(1).default(1), pageSize: yup.number().integer().min(0).max(100).default(10), @@ -119,167 +114,123 @@ router.get( const sortItems = sort.get(ctx); - const [finalQuery, count] = await (async () => { - if (params.fieldName && params.fieldValue) { - const ticketFieldQuery = TicketFieldValue.queryBuilder() - .where('values', '==', { - field: params.fieldName, - value: params.fieldValue, - }) - .skip((params.page - 1) * params.pageSize) - .limit(params.pageSize) - .orderBy('createdAt', 'desc'); - - if (params.createdAtFrom) { - ticketFieldQuery.where('createdAt', '>=', params.createdAtFrom); - } - if (params.createdAtTo) { - ticketFieldQuery.where('createdAt', '<=', params.createdAtTo); - } - - // we can't get the count in the second query, but we can in the first query - const [ticketFieldValues, count] = params.count - ? await ticketFieldQuery.findAndCount({ - useMasterKey: true, - }) - : [await ticketFieldQuery.find({ useMasterKey: true }), undefined]; - - return [ - Ticket.queryBuilder() - .where( - 'objectId', - 'in', - ticketFieldValues.map(({ ticketId }) => ticketId) - ) - .orderBy('createdAt', 'desc'), - count, - ]; - } else { - const categoryIds = new Set(params.categoryId); - const rootId = params.product || params.rootCategoryId; - - if (rootId) { - categoryIds.add(rootId); - const subCategories = await categoryService.getSubCategories(rootId); - subCategories.forEach((c) => categoryIds.add(c.id)); - } - - const query = Ticket.queryBuilder(); - - if (params.where) { - query.setRawCondition(params.where); - } - if (params.authorId) { - query.where('author', '==', User.ptr(params.authorId)); - } - if (params.assigneeId) { - addPointersCondition(query, 'assignee', params.assigneeId, User); - } - if (params.groupId) { - addPointersCondition(query, 'group', params.groupId, Group); - } - if (params.reporterId) { - addPointersCondition(query, 'reporter', params.reporterId, User); - } - if (params.participantId) { - query.where('joinedCustomerServices.objectId', 'in', params.participantId); - } - if (categoryIds.size) { - query.where('category.objectId', 'in', Array.from(categoryIds)); - } - if (params.status) { - query.where('status', 'in', params.status); - } - if (params['evaluation.star'] !== undefined) { - query.where('evaluation.star', '==', params['evaluation.star']); - } - if (params['evaluation.ts']) { - const [from, to] = params['evaluation.ts']; - if (from) { - query.where('evaluation.ts', '>=', from); - } - if (to) { - query.where('evaluation.ts', '<=', to); - } - } - if (params.createdAtFrom) { - query.where('createdAt', '>=', params.createdAtFrom); - } - if (params.createdAtTo) { - query.where('createdAt', '<=', params.createdAtTo); - } - if (params.tagKey) { - query.where('tags.key', '==', params.tagKey); - } - if (params.tagValue) { - query.where('tags.value', '==', params.tagValue); - } - if (params.privateTagKey) { - if (!(await currentUser.isCustomerService())) { - ctx.throw(403); - } - query.where('privateTags.key', '==', params.privateTagKey); - } - if (params.privateTagValue) { - if (!(await currentUser.isCustomerService())) { - ctx.throw(403); - } - query.where('privateTags.value', '==', params.privateTagValue); - } + const categoryIds = new Set(params.categoryId); + const rootId = params.product || params.rootCategoryId; - if (params.language) { - addInOrNotExistCondition(query, params.language, 'language'); - } + if (rootId) { + categoryIds.add(rootId); + const subCategories = await categoryService.getSubCategories(rootId); + subCategories.forEach((c) => categoryIds.add(c.id)); + } - query.skip((params.page - 1) * params.pageSize).limit(params.pageSize); - sortItems?.forEach(({ key, order }) => query.orderBy(key, order)); + const query = Ticket.queryBuilder(); - return [query, undefined]; + if (params.where) { + query.setRawCondition(params.where); + } + if (params.authorId) { + query.where('author', '==', User.ptr(params.authorId)); + } + if (params.assigneeId) { + addPointersCondition(query, 'assignee', params.assigneeId, User); + } + if (params.groupId) { + addPointersCondition(query, 'group', params.groupId, Group); + } + if (params.reporterId) { + addPointersCondition(query, 'reporter', params.reporterId, User); + } + if (params.participantId) { + query.where('joinedCustomerServices.objectId', 'in', params.participantId); + } + if (categoryIds.size) { + query.where('category.objectId', 'in', Array.from(categoryIds)); + } + if (params.status) { + query.where('status', 'in', params.status); + } + if (params['evaluation.star'] !== undefined) { + query.where('evaluation.star', '==', params['evaluation.star']); + } + if (params['evaluation.ts']) { + const [from, to] = params['evaluation.ts']; + if (from) { + query.where('evaluation.ts', '>=', from); + } + if (to) { + query.where('evaluation.ts', '<=', to); } - })(); + } + if (params.createdAtFrom) { + query.where('createdAt', '>=', params.createdAtFrom); + } + if (params.createdAtTo) { + query.where('createdAt', '<=', params.createdAtTo); + } + if (params.tagKey) { + query.where('tags.key', '==', params.tagKey); + } + if (params.tagValue) { + query.where('tags.value', '==', params.tagValue); + } + if (params.privateTagKey) { + if (!(await currentUser.isCustomerService())) { + ctx.throw(403); + } + query.where('privateTags.key', '==', params.privateTagKey); + } + if (params.privateTagValue) { + if (!(await currentUser.isCustomerService())) { + ctx.throw(403); + } + query.where('privateTags.value', '==', params.privateTagValue); + } + + if (params.language) { + addInOrNotExistCondition(query, params.language, 'language'); + } + + query.skip((params.page - 1) * params.pageSize).limit(params.pageSize); + sortItems?.forEach(({ key, order }) => query.orderBy(key, order)); if (params.includeAuthor) { - finalQuery.preload('author'); + query.preload('author'); } if (params.includeReporter) { - finalQuery.preload('reporter'); + query.preload('reporter'); } if (params.includeAssignee) { - finalQuery.preload('assignee'); + query.preload('assignee'); } if (params.includeGroup) { if (!(await currentUser.isStaff())) { ctx.throw(403); } - finalQuery.preload('group'); + query.preload('group'); } if (params.includeFiles) { - finalQuery.preload('files'); + query.preload('files'); } if (params.includeUnreadCount) { - finalQuery.preload('notification', { + query.preload('notification', { onQuery: (query) => { return query.where('user', '==', currentUser.toPointer()); }, }); } if (params.includeFields) { - finalQuery.preload('fieldValue', { + query.preload('fieldValue', { authOptions: { useMasterKey: true }, }); } let tickets: Ticket[]; - if (params.count && !count) { - const result = await finalQuery.findAndCount(currentUser.getAuthOptions()); + if (params.count) { + const result = await query.findAndCount(currentUser.getAuthOptions()); tickets = result[0]; ctx.set('X-Total-Count', result[1].toString()); } else { - tickets = await finalQuery.find(currentUser.getAuthOptions()); - - if (params.count && count) { - ctx.set('X-Total-Count', count.toString()); - } + tickets = await query.find(currentUser.getAuthOptions()); } if (params.includeCategoryPath) { diff --git a/next/web/src/App/Admin/Tickets/Filter/FilterForm/index.tsx b/next/web/src/App/Admin/Tickets/Filter/FilterForm/index.tsx index b0e356b0a..af0d65a90 100644 --- a/next/web/src/App/Admin/Tickets/Filter/FilterForm/index.tsx +++ b/next/web/src/App/Admin/Tickets/Filter/FilterForm/index.tsx @@ -153,17 +153,17 @@ const NormalFieldForm = ({ filters, merge, onSubmit }: FilterFormItemProps) => { - const { fieldId: paramFieldId, optionValue, createdAt, textValue } = filters; + const { fieldId: paramFieldId, fieldValue, createdAt } = filters; const [field, setField] = useState(); const [fieldId, isOptionType, isTextType] = useMemo( () => [ field?.id ?? paramFieldId, - field ? OptionTypes.includes(field.type) : !!optionValue, - field ? TextTypes.includes(field.type) : !!textValue, + field ? OptionTypes.includes(field.type) : false, + field ? TextTypes.includes(field.type) : false, ], - [field, optionValue, paramFieldId, textValue] + [field, paramFieldId] ); return ( @@ -180,16 +180,16 @@ const CustomFieldForm = ({ filters, merge, onSubmit }: FilterFormItemProps { - merge({ fieldId: fieldId, optionValue: v }); + merge({ fieldId: fieldId, fieldValue: v }); }} /> ) : isTextType ? ( { - merge({ fieldId: fieldId, textValue: e.target.value }); + merge({ fieldId: fieldId, fieldValue: e.target.value }); }} onKeyDown={(e) => e.key === 'Enter' && onSubmit?.()} /> diff --git a/next/web/src/App/Admin/Tickets/Filter/useTicketFilter.tsx b/next/web/src/App/Admin/Tickets/Filter/useTicketFilter.tsx index 2f8a4b56e..e139459de 100644 --- a/next/web/src/App/Admin/Tickets/Filter/useTicketFilter.tsx +++ b/next/web/src/App/Admin/Tickets/Filter/useTicketFilter.tsx @@ -29,8 +29,7 @@ export interface NormalFilters extends CommonFilters { export interface FieldFilters extends CommonFilters { type: 'field'; fieldId?: string; - optionValue?: string; - textValue?: string; + fieldValue?: string; } export type Filters = NormalFilters | FieldFilters; @@ -39,7 +38,7 @@ const serializeFilters = (filter: Filters): Record = if (filter.type === 'field') { return { filterType: 'field', - ..._.pick(filter, ['fieldId', 'optionValue', 'createdAt', 'textValue']), + ..._.pick(filter, ['fieldId', 'fieldValue', 'createdAt']), }; } else { return { @@ -83,8 +82,7 @@ const deserializeFilters = (params: Record): Filters // field 'fieldId', - 'optionValue', - 'textValue', + 'fieldValue', ]), assigneeId: params.assigneeId?.split(','), groupId: params.groupId?.split(','), diff --git a/next/web/src/App/Admin/Tickets/index.tsx b/next/web/src/App/Admin/Tickets/index.tsx index 3cba06327..917d318ef 100644 --- a/next/web/src/App/Admin/Tickets/index.tsx +++ b/next/web/src/App/Admin/Tickets/index.tsx @@ -46,23 +46,14 @@ function useSmartSearchTickets({ ...options }: UseSmartFetchTicketsOptions) { const isProcessableSearch = type === 'processable'; - const isRegularTicket = - ((filters.type === 'normal' && !filters.keyword) || - (filters.type === 'field' && !!filters.optionValue)) && - !isProcessableSearch; - const isFieldSearch = filters.type === 'field' && !filters.optionValue && !isProcessableSearch; + const isRegularTicket = filters.type === 'normal' && !filters.keyword && !isProcessableSearch; + const isFieldSearch = filters.type === 'field' && !isProcessableSearch; const isKeywordSearch = filters.type === 'normal' && !!filters.keyword && !isProcessableSearch; const dateRange = filters.createdAt && decodeDateRange(filters.createdAt); const useTicketResult = useTickets({ - filters: - filters.type === 'normal' - ? _.omit(filters, ['type', 'fieldId', 'optionValue']) - : { - fieldName: (filters as FieldFilters).fieldId, - fieldValue: (filters as FieldFilters).optionValue, - }, + filters: _.omit(filters, ['type', 'fieldId', 'fieldValue']), ...options, queryOptions: { ...queryOptions, @@ -71,7 +62,7 @@ function useSmartSearchTickets({ }); const useSearchTicketsResult = useSearchTickets((filters as NormalFilters).keyword!, { - filters: _.omit(filters, ['keyword', 'type', 'fieldId', 'optionValue']) as NormalFilters, + filters: _.omit(filters, ['keyword', 'type', 'fieldId', 'fieldValue']) as NormalFilters, ...options, queryOptions: { ...queryOptions, @@ -87,12 +78,12 @@ function useSmartSearchTickets({ }, }); - const { fieldId, textValue } = filters as FieldFilters; + const { fieldId, fieldValue } = filters as FieldFilters; const useFieldSearchResult = useSearchTicketCustomField( { fieldId, - fieldValue: textValue, + fieldValue, createdAt: dateRange ? [dateRange.from, dateRange.to] : undefined, }, {