From c2288ba69cb1f7f2816b5a036b26fc39bd343db0 Mon Sep 17 00:00:00 2001 From: Su Yin <53945359+tnsyn@users.noreply.github.com> Date: Sun, 6 Nov 2022 16:04:00 +0800 Subject: [PATCH] [resumes][feat] Add top 10 shortcut (#518) --- apps/portal/src/pages/resumes/index.tsx | 3 + .../router/resumes/resumes-resume-router.ts | 137 ++++---- .../resumes/resumes-resume-user-router.ts | 297 ++++++++++-------- .../portal/src/utils/resumes/resumeFilters.ts | 4 + .../utils/resumes/resumeGetFilterCounts.ts | 39 +++ 5 files changed, 282 insertions(+), 198 deletions(-) create mode 100644 apps/portal/src/utils/resumes/resumeGetFilterCounts.ts diff --git a/apps/portal/src/pages/resumes/index.tsx b/apps/portal/src/pages/resumes/index.tsx index 157d25b1..142dd3c0 100644 --- a/apps/portal/src/pages/resumes/index.tsx +++ b/apps/portal/src/pages/resumes/index.tsx @@ -187,6 +187,7 @@ export default function ResumeHomePage() { 'resumes.resume.findAll', { experienceFilters: userFilters.experience.map(({ value }) => value), + isTop10: userFilters.isTop10, isUnreviewed: userFilters.isUnreviewed, locationFilters: userFilters.location.map(({ value }) => value), roleFilters: userFilters.role.map(({ value }) => value), @@ -206,6 +207,7 @@ export default function ResumeHomePage() { 'resumes.resume.user.findUserStarred', { experienceFilters: userFilters.experience.map(({ value }) => value), + isTop10: userFilters.isTop10, isUnreviewed: userFilters.isUnreviewed, locationFilters: userFilters.location.map(({ value }) => value), roleFilters: userFilters.role.map(({ value }) => value), @@ -226,6 +228,7 @@ export default function ResumeHomePage() { 'resumes.resume.user.findUserCreated', { experienceFilters: userFilters.experience.map(({ value }) => value), + isTop10: userFilters.isTop10, isUnreviewed: userFilters.isUnreviewed, locationFilters: userFilters.location.map(({ value }) => value), roleFilters: userFilters.role.map(({ value }) => value), diff --git a/apps/portal/src/server/router/resumes/resumes-resume-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-router.ts index 81c5625b..54f4e099 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-router.ts @@ -1,6 +1,9 @@ import { z } from 'zod'; import { Vote } from '@prisma/client'; +import type { FilterCounts } from '~/utils/resumes/resumeFilters'; +import { resumeGetFilterCounts } from '~/utils/resumes/resumeGetFilterCounts'; + import { createRouter } from '../context'; import type { Resume } from '~/types/resume'; @@ -9,6 +12,7 @@ export const resumesRouter = createRouter() .query('findAll', { input: z.object({ experienceFilters: z.string().array(), + isTop10: z.boolean(), isUnreviewed: z.boolean(), locationFilters: z.string().array(), roleFilters: z.string().array(), @@ -25,19 +29,14 @@ export const resumesRouter = createRouter() sortOrder, isUnreviewed, skip, + isTop10, searchValue, take, } = input; const userId = ctx.session?.user?.id; - const totalRecords = await ctx.prisma.resumesResume.count({ - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); + let totalRecords = 10; + let filterCounts = {} as FilterCounts; + const resumesData = await ctx.prisma.resumesResume.findMany({ include: { _count: { @@ -77,7 +76,7 @@ export const resumesRouter = createRouter() }, } : { comments: { _count: 'desc' } }, - skip, + skip: isTop10 ? 0 : skip, take, where: { experience: { in: experienceFilters }, @@ -107,62 +106,76 @@ export const resumesRouter = createRouter() return resume; }); - // Group by role and count, taking into account all role/experience/locationId/isUnreviewed filters and search value - const roleCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['role'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); + if (isTop10) { + filterCounts = resumeGetFilterCounts(mappedResumeData); + } else { + totalRecords = await ctx.prisma.resumesResume.count({ + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + }, + }); - // Map all nonzero counts from array to object where key = role and value = count - const mappedRoleCounts = Object.fromEntries( - roleCounts.map((rc) => [rc.role, rc._count._all]), - ); + // Group by role and count, taking into account all role/experience/locationId/isUnreviewed filters and search value + const roleCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['role'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + }, + }); - const experienceCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['experience'], - where: { - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); - const mappedExperienceCounts = Object.fromEntries( - experienceCounts.map((ec) => [ec.experience, ec._count._all]), - ); + // Map all nonzero counts from array to object where key = role and value = count + const mappedRoleCounts = Object.fromEntries( + roleCounts.map((rc) => [rc.role, rc._count._all]), + ); - const locationCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['locationId'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); - const mappedLocationCounts = Object.fromEntries( - locationCounts.map((lc) => [lc.locationId, lc._count._all]), - ); + const experienceCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['experience'], + where: { + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + }, + }); + const mappedExperienceCounts = Object.fromEntries( + experienceCounts.map((ec) => [ec.experience, ec._count._all]), + ); - const filterCounts = { - experience: mappedExperienceCounts, - location: mappedLocationCounts, - role: mappedRoleCounts, - }; + const locationCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['locationId'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + }, + }); + const mappedLocationCounts = Object.fromEntries( + locationCounts.map((lc) => [lc.locationId, lc._count._all]), + ); + + filterCounts = { + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, + }; + } return { filterCounts, diff --git a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts index aaeebeed..bdd6d81c 100644 --- a/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts +++ b/apps/portal/src/server/router/resumes/resumes-resume-user-router.ts @@ -1,5 +1,8 @@ import { z } from 'zod'; +import type { FilterCounts } from '~/utils/resumes/resumeFilters'; +import { resumeGetFilterCounts } from '~/utils/resumes/resumeGetFilterCounts'; + import { createProtectedRouter } from '../context'; import type { Resume } from '~/types/resume'; @@ -63,6 +66,7 @@ export const resumesResumeUserRouter = createProtectedRouter() .query('findUserStarred', { input: z.object({ experienceFilters: z.string().array(), + isTop10: z.boolean(), isUnreviewed: z.boolean(), locationFilters: z.string().array(), roleFilters: z.string().array(), @@ -78,23 +82,16 @@ export const resumesResumeUserRouter = createProtectedRouter() locationFilters, experienceFilters, searchValue, + isTop10, sortOrder, isUnreviewed, skip, take, } = input; - const totalRecords = await ctx.prisma.resumesStar.count({ - where: { - resume: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - userId, - }, - }); + + let totalRecords = 10; + let filterCounts = {} as FilterCounts; + const resumeStarsData = await ctx.prisma.resumesStar.findMany({ include: { resume: { @@ -140,7 +137,7 @@ export const resumesResumeUserRouter = createProtectedRouter() }, }, }, - skip, + skip: isTop10 ? 0 : skip, take, where: { resume: { @@ -174,74 +171,91 @@ export const resumesResumeUserRouter = createProtectedRouter() return resume; }); - const roleCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['role'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - stars: { - some: { - userId, + if (isTop10) { + filterCounts = resumeGetFilterCounts(mappedResumeData); + } else { + totalRecords = await ctx.prisma.resumesStar.count({ + where: { + resume: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, }, + userId, }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); - const mappedRoleCounts = Object.fromEntries( - roleCounts.map((rc) => [rc.role, rc._count._all]), - ); + }); - const experienceCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['experience'], - where: { - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - stars: { - some: { - userId, + const roleCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['role'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + stars: { + some: { + userId, + }, }, + title: { contains: searchValue, mode: 'insensitive' }, }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); - const mappedExperienceCounts = Object.fromEntries( - experienceCounts.map((ec) => [ec.experience, ec._count._all]), - ); + }); + const mappedRoleCounts = Object.fromEntries( + roleCounts.map((rc) => [rc.role, rc._count._all]), + ); - const locationCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['locationId'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - role: { in: roleFilters }, - stars: { - some: { - userId, + const experienceCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['experience'], + where: { + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + stars: { + some: { + userId, + }, }, + title: { contains: searchValue, mode: 'insensitive' }, }, - title: { contains: searchValue, mode: 'insensitive' }, - }, - }); - const mappedLocationCounts = Object.fromEntries( - locationCounts.map((lc) => [lc.locationId, lc._count._all]), - ); + }); + const mappedExperienceCounts = Object.fromEntries( + experienceCounts.map((ec) => [ec.experience, ec._count._all]), + ); + + const locationCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['locationId'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + role: { in: roleFilters }, + stars: { + some: { + userId, + }, + }, + title: { contains: searchValue, mode: 'insensitive' }, + }, + }); + const mappedLocationCounts = Object.fromEntries( + locationCounts.map((lc) => [lc.locationId, lc._count._all]), + ); - const filterCounts = { - experience: mappedExperienceCounts, - location: mappedLocationCounts, - role: mappedRoleCounts, - }; + filterCounts = { + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, + }; + } return { filterCounts, mappedResumeData, totalRecords }; }, @@ -249,6 +263,7 @@ export const resumesResumeUserRouter = createProtectedRouter() .query('findUserCreated', { input: z.object({ experienceFilters: z.string().array(), + isTop10: z.boolean(), isUnreviewed: z.boolean(), locationFilters: z.string().array(), roleFilters: z.string().array(), @@ -265,20 +280,15 @@ export const resumesResumeUserRouter = createProtectedRouter() experienceFilters, sortOrder, searchValue, + isTop10, isUnreviewed, take, skip, } = input; - const totalRecords = await ctx.prisma.resumesResume.count({ - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - userId, - }, - }); + + let totalRecords = 10; + let filterCounts = {} as FilterCounts; + const resumesData = await ctx.prisma.resumesResume.findMany({ include: { _count: { @@ -315,7 +325,7 @@ export const resumesResumeUserRouter = createProtectedRouter() }, } : { comments: { _count: 'desc' } }, - skip, + skip: isTop10 ? 0 : skip, take, where: { experience: { in: experienceFilters }, @@ -346,62 +356,77 @@ export const resumesResumeUserRouter = createProtectedRouter() return resume; }); - const roleCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['role'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - userId, - }, - }); - const mappedRoleCounts = Object.fromEntries( - roleCounts.map((rc) => [rc.role, rc._count._all]), - ); + if (isTop10) { + filterCounts = resumeGetFilterCounts(mappedResumeData); + } else { + totalRecords = await ctx.prisma.resumesResume.count({ + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + userId, + }, + }); - const experienceCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['experience'], - where: { - isResolved: isUnreviewed ? false : {}, - locationId: { in: locationFilters }, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - userId, - }, - }); - const mappedExperienceCounts = Object.fromEntries( - experienceCounts.map((ec) => [ec.experience, ec._count._all]), - ); + const roleCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['role'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + userId, + }, + }); + const mappedRoleCounts = Object.fromEntries( + roleCounts.map((rc) => [rc.role, rc._count._all]), + ); - const locationCounts = await ctx.prisma.resumesResume.groupBy({ - _count: { - _all: true, - }, - by: ['locationId'], - where: { - experience: { in: experienceFilters }, - isResolved: isUnreviewed ? false : {}, - role: { in: roleFilters }, - title: { contains: searchValue, mode: 'insensitive' }, - userId, - }, - }); - const mappedLocationCounts = Object.fromEntries( - locationCounts.map((lc) => [lc.locationId, lc._count._all]), - ); + const experienceCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['experience'], + where: { + isResolved: isUnreviewed ? false : {}, + locationId: { in: locationFilters }, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + userId, + }, + }); + const mappedExperienceCounts = Object.fromEntries( + experienceCounts.map((ec) => [ec.experience, ec._count._all]), + ); - const filterCounts = { - experience: mappedExperienceCounts, - location: mappedLocationCounts, - role: mappedRoleCounts, - }; + const locationCounts = await ctx.prisma.resumesResume.groupBy({ + _count: { + _all: true, + }, + by: ['locationId'], + where: { + experience: { in: experienceFilters }, + isResolved: isUnreviewed ? false : {}, + role: { in: roleFilters }, + title: { contains: searchValue, mode: 'insensitive' }, + userId, + }, + }); + const mappedLocationCounts = Object.fromEntries( + locationCounts.map((lc) => [lc.locationId, lc._count._all]), + ); + + filterCounts = { + experience: mappedExperienceCounts, + location: mappedLocationCounts, + role: mappedRoleCounts, + }; + } return { filterCounts, mappedResumeData, totalRecords }; }, diff --git a/apps/portal/src/utils/resumes/resumeFilters.ts b/apps/portal/src/utils/resumes/resumeFilters.ts index 96233f16..4ff02371 100644 --- a/apps/portal/src/utils/resumes/resumeFilters.ts +++ b/apps/portal/src/utils/resumes/resumeFilters.ts @@ -5,8 +5,10 @@ import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import { JobTitleLabels } from '~/components/shared/JobTitles'; export type FilterId = 'experience' | 'location' | 'role'; +export type FilterCounts = Record>; export type CustomFilter = { + isTop10: boolean; isUnreviewed: boolean; }; @@ -145,6 +147,7 @@ export const INITIAL_LOCATIONS: Array = [ export const INITIAL_FILTER_STATE: FilterState = { experience: EXPERIENCES, + isTop10: false, isUnreviewed: true, location: INITIAL_LOCATIONS, role: INITIAL_ROLES, @@ -185,6 +188,7 @@ export const SHORTCUTS: Array = [ { filters: { ...INITIAL_FILTER_STATE, + isTop10: true, isUnreviewed: false, }, name: 'Top 10', diff --git a/apps/portal/src/utils/resumes/resumeGetFilterCounts.ts b/apps/portal/src/utils/resumes/resumeGetFilterCounts.ts new file mode 100644 index 00000000..896155ca --- /dev/null +++ b/apps/portal/src/utils/resumes/resumeGetFilterCounts.ts @@ -0,0 +1,39 @@ +import type { Resume } from '~/types/resume'; + +export function resumeGetFilterCounts(data: Array) { + const roleCounts: Record = {}; + for (let i = 0; i < data.length; i++) { + const { role } = data[i]; + if (!(role in roleCounts)) { + roleCounts[role] = 1; + } else { + roleCounts[role]++; + } + } + + const experienceCounts: Record = {}; + for (let i = 0; i < data.length; i++) { + const { experience } = data[i]; + if (!(experience in experienceCounts)) { + experienceCounts[experience] = 1; + } else { + experienceCounts[experience]++; + } + } + + const locationCounts: Record = {}; + for (let i = 0; i < data.length; i++) { + const { locationId } = data[i]; + if (!(locationId in locationCounts)) { + locationCounts[locationId] = 1; + } else { + locationCounts[locationId]++; + } + } + + return { + experience: experienceCounts, + location: locationCounts, + role: roleCounts, + }; +}