From 79500b8a3588060811fe380d183078d2b0e3107b Mon Sep 17 00:00:00 2001 From: Yangshun Tay Date: Sun, 6 Nov 2022 09:17:01 +0800 Subject: [PATCH] [portal][feat] add ranking to job titles typeahead --- .../offers/forms/FormJobTitlesTypeahead.tsx | 16 ++- .../questions/typeahead/RoleTypeahead.tsx | 2 +- .../resumes/shared/ResumeRoleTypeahead.tsx | 2 +- .../portal/src/components/shared/JobTitles.ts | 126 +++++++++++------- ...lesTypahead.tsx => JobTitlesTypeahead.tsx} | 4 +- apps/portal/src/pages/offers/index.tsx | 10 +- apps/portal/src/pages/questions/browse.tsx | 5 +- apps/portal/src/pages/test__.tsx | 2 +- .../questions/relabelQuestionAggregates.ts | 5 +- .../portal/src/utils/resumes/resumeFilters.ts | 5 +- 10 files changed, 107 insertions(+), 70 deletions(-) rename apps/portal/src/components/shared/{JobTitlesTypahead.tsx => JobTitlesTypeahead.tsx} (91%) diff --git a/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx b/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx index fa35d320..bffe93b8 100644 --- a/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx +++ b/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx @@ -3,7 +3,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import type { JobTitleType } from '~/components/shared/JobTitles'; import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; -import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypeahead'; type Props = Omit< ComponentProps, @@ -21,11 +21,15 @@ export default function FormJobTitlesTypeahead({ name, ...props }: Props) { return ( { setValue(name, option?.value); }} diff --git a/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx b/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx index dac54418..fc7ec0ff 100644 --- a/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx +++ b/apps/portal/src/components/questions/typeahead/RoleTypeahead.tsx @@ -12,7 +12,7 @@ export type RoleTypeaheadProps = Omit< >; const ROLES: FilterChoices = Object.entries(JobTitleLabels).map( - ([slug, label]) => ({ + ([slug, { label }]) => ({ id: slug, label, value: slug, diff --git a/apps/portal/src/components/resumes/shared/ResumeRoleTypeahead.tsx b/apps/portal/src/components/resumes/shared/ResumeRoleTypeahead.tsx index d619ccf2..b5459200 100644 --- a/apps/portal/src/components/resumes/shared/ResumeRoleTypeahead.tsx +++ b/apps/portal/src/components/resumes/shared/ResumeRoleTypeahead.tsx @@ -30,7 +30,7 @@ export default function ResumeRoleTypeahead({ }: Props) { const [query, setQuery] = useState(''); const options = Object.entries(JobTitleLabels) - .map(([slug, label]) => ({ + .map(([slug, { label }]) => ({ id: slug, label, value: slug, diff --git a/apps/portal/src/components/shared/JobTitles.ts b/apps/portal/src/components/shared/JobTitles.ts index 1c1edc11..d992a79d 100644 --- a/apps/portal/src/components/shared/JobTitles.ts +++ b/apps/portal/src/components/shared/JobTitles.ts @@ -1,56 +1,82 @@ -export const JobTitleLabels = { - 'ai-engineer': 'Artificial Intelligence (AI) Engineer', - 'algorithms-engineer': 'Algorithms Engineer', - 'android-engineer': 'Android Software Engineer', - 'applications-engineer': 'Applications Engineer', - 'back-end-engineer': 'Back End Engineer', - 'business-analyst': 'Business Analyst', - 'business-engineer': 'Business Engineer', - 'capacity-engineer': 'Capacity Engineer', - 'customer-engineer': 'Customer Engineer', - 'data-analyst': 'Data Analyst', - 'data-engineer': 'Data Engineer', - 'data-scientist': 'Data Scientist', - 'devops-engineer': 'DevOps Engineer', - 'engineering-director': 'Engineering Director', - 'engineering-manager': 'Engineering Manager', - 'enterprise-engineer': 'Enterprise Engineer', - 'forward-deployed-engineer': 'Forward Deployed Engineer', - 'front-end-engineer': 'Front End Engineer', - 'full-stack-engineer': 'Full Stack Engineer', - 'gameplay-engineer': 'Gameplay Engineer', - 'hardware-engineer': 'Hardware Engineer', - 'infrastructure-engineer': 'Infrastructure Engineer', - 'ios-engineer': 'iOS Software Engineer', - 'machine-learning-engineer': 'Machine Learning (ML) Engineer', - 'machine-learning-researcher': 'Machine Learning (ML) Researcher', - 'mobile-engineer': 'Mobile Software Engineer (iOS + Android)', - 'networks-engineer': 'Networks Engineer', - 'partner-engineer': 'Partner Engineer', - 'product-engineer': 'Product Engineer', - 'product-manager': 'Product Manager', - 'production-engineer': 'Production Engineer', - 'project-manager': 'Project Manager', - 'release-engineer': 'Release Engineer', - 'research-engineer': 'Research Engineer', - 'research-scientist': 'Research Scientist', - 'rotational-engineer': 'Rotational Engineer', - 'sales-engineer': 'Sales Engineer', - 'security-engineer': 'Security Engineer', - 'site-reliability-engineer': 'Site Reliability Engineer (SRE)', - 'software-engineer': 'Software Engineer', - 'solutions-architect': 'Solutions Architect', - 'solutions-engineer': 'Solutions Engineer', - 'systems-analyst': 'Systems Analyst', - 'systems-engineer': 'Systems Engineer', - 'tech-ops-engineer': 'Tech Ops Engineer', - 'technical-program-manager': 'Technical Program Manager', - 'test-engineer': 'QA/Test Engineer (SDET)', - 'ux-engineer': 'User Experience (UX) Engineer', +type JobTitleData = Record< + string, + Readonly<{ + label: string; + ranking: number; + }> +>; + +export const JobTitleLabels: JobTitleData = { + 'ai-engineer': { label: 'Artificial Intelligence (AI) Engineer', ranking: 5 }, + 'algorithms-engineer': { label: 'Algorithms Engineer', ranking: 0 }, + 'android-engineer': { label: 'Android Software Engineer', ranking: 8 }, + 'applications-engineer': { label: 'Applications Engineer', ranking: 0 }, + 'back-end-engineer': { label: 'Back End Engineer', ranking: 9 }, + 'business-analyst': { label: 'Business Analyst', ranking: 0 }, + 'business-engineer': { label: 'Business Engineer', ranking: 5 }, + 'capacity-engineer': { label: 'Capacity Engineer', ranking: 0 }, + 'customer-engineer': { label: 'Customer Engineer', ranking: 0 }, + 'data-analyst': { label: 'Data Analyst', ranking: 0 }, + 'data-engineer': { label: 'Data Engineer', ranking: 0 }, + 'data-scientist': { label: 'Data Scientist', ranking: 5 }, + 'devops-engineer': { label: 'DevOps Engineer', ranking: 0 }, + 'engineering-director': { label: 'Engineering Director', ranking: 0 }, + 'engineering-manager': { label: 'Engineering Manager', ranking: 0 }, + 'enterprise-engineer': { label: 'Enterprise Engineer', ranking: 0 }, + 'forward-deployed-engineer': { + label: 'Forward Deployed Engineer (FDE)', + ranking: 0, + }, + 'front-end-engineer': { label: 'Front End Engineer', ranking: 9 }, + 'full-stack-engineer': { label: 'Full Stack Engineer', ranking: 9 }, + 'gameplay-engineer': { label: 'Gameplay Engineer', ranking: 0 }, + 'hardware-engineer': { label: 'Hardware Engineer', ranking: 0 }, + 'infrastructure-engineer': { label: 'Infrastructure Engineer', ranking: 0 }, + 'ios-engineer': { label: 'iOS Software Engineer', ranking: 0 }, + 'machine-learning-engineer': { + label: 'Machine Learning (ML) Engineer', + ranking: 5, + }, + 'machine-learning-researcher': { + label: 'Machine Learning (ML) Researcher', + ranking: 0, + }, + 'mobile-engineer': { + label: 'Mobile Software Engineer (iOS + Android)', + ranking: 8, + }, + 'networks-engineer': { label: 'Networks Engineer', ranking: 0 }, + 'partner-engineer': { label: 'Partner Engineer', ranking: 0 }, + 'product-engineer': { label: 'Product Engineer', ranking: 7 }, + 'product-manager': { label: 'Product Manager', ranking: 0 }, + 'production-engineer': { label: 'Production Engineer', ranking: 8 }, + 'project-manager': { label: 'Project Manager', ranking: 0 }, + 'release-engineer': { label: 'Release Engineer', ranking: 0 }, + 'research-engineer': { label: 'Research Engineer', ranking: 6 }, + 'research-scientist': { label: 'Research Scientist', ranking: 7 }, + 'rotational-engineer': { label: 'Rotational Engineer', ranking: 0 }, + 'sales-engineer': { label: 'Sales Engineer', ranking: 0 }, + 'security-engineer': { label: 'Security Engineer', ranking: 7 }, + 'site-reliability-engineer': { + label: 'Site Reliability Engineer (SRE)', + ranking: 8, + }, + 'software-engineer': { label: 'Software Engineer', ranking: 10 }, + 'solutions-architect': { label: 'Solutions Architect', ranking: 0 }, + 'solutions-engineer': { label: 'Solutions Engineer', ranking: 0 }, + 'systems-analyst': { label: 'Systems Analyst', ranking: 0 }, + 'systems-engineer': { label: 'Systems Engineer', ranking: 0 }, + 'tech-ops-engineer': { label: 'Tech Ops Engineer', ranking: 0 }, + 'technical-program-manager': { + label: 'Technical Program Manager', + ranking: 0, + }, + 'test-engineer': { label: 'QA/Test Engineer (SDET)', ranking: 6 }, + 'ux-engineer': { label: 'User Experience (UX) Engineer', ranking: 0 }, }; export type JobTitleType = keyof typeof JobTitleLabels; export function getLabelForJobTitleType(jobTitle: JobTitleType): string { - return JobTitleLabels[jobTitle]; + return JobTitleLabels[jobTitle].label; } diff --git a/apps/portal/src/components/shared/JobTitlesTypahead.tsx b/apps/portal/src/components/shared/JobTitlesTypeahead.tsx similarity index 91% rename from apps/portal/src/components/shared/JobTitlesTypahead.tsx rename to apps/portal/src/components/shared/JobTitlesTypeahead.tsx index 9de68c45..71d35481 100644 --- a/apps/portal/src/components/shared/JobTitlesTypahead.tsx +++ b/apps/portal/src/components/shared/JobTitlesTypeahead.tsx @@ -28,11 +28,13 @@ export default function JobTitlesTypeahead({ }: Props) { const [query, setQuery] = useState(''); const options = Object.entries(JobTitleLabels) - .map(([slug, label]) => ({ + .map(([slug, { label, ranking }]) => ({ id: slug, label, + ranking, value: slug, })) + .sort((a, b) => b.ranking - a.ranking) .filter( ({ label }) => label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) > -1, diff --git a/apps/portal/src/pages/offers/index.tsx b/apps/portal/src/pages/offers/index.tsx index 232fd382..d0e820f4 100644 --- a/apps/portal/src/pages/offers/index.tsx +++ b/apps/portal/src/pages/offers/index.tsx @@ -9,8 +9,8 @@ import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import Container from '~/components/shared/Container'; import CountriesTypeahead from '~/components/shared/CountriesTypeahead'; import type { JobTitleType } from '~/components/shared/JobTitles'; -import { JobTitleLabels } from '~/components/shared/JobTitles'; -import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; +import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypeahead'; import { useSearchParamSingle } from '~/utils/offers/useSearchParam'; @@ -79,7 +79,9 @@ export default function OffersHomePage() { selectedJobTitleId ? { id: selectedJobTitleId, - label: JobTitleLabels[selectedJobTitleId as JobTitleType], + label: getLabelForJobTitleType( + selectedJobTitleId as JobTitleType, + ), value: selectedJobTitleId, } : null @@ -139,4 +141,4 @@ export default function OffersHomePage() { ); -} \ No newline at end of file +} diff --git a/apps/portal/src/pages/questions/browse.tsx b/apps/portal/src/pages/questions/browse.tsx index 25525a7a..e1f70355 100644 --- a/apps/portal/src/pages/questions/browse.tsx +++ b/apps/portal/src/pages/questions/browse.tsx @@ -17,7 +17,8 @@ import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; import CompanyTypeahead from '~/components/questions/typeahead/CompanyTypeahead'; import LocationTypeahead from '~/components/questions/typeahead/LocationTypeahead'; import RoleTypeahead from '~/components/questions/typeahead/RoleTypeahead'; -import { JobTitleLabels } from '~/components/shared/JobTitles'; +import type { JobTitleType } from '~/components/shared/JobTitles'; +import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import type { QuestionAge } from '~/utils/questions/constants'; import { QUESTION_SORT_TYPES } from '~/utils/questions/constants'; @@ -316,7 +317,7 @@ export default function QuestionsBrowsePage() { return selectedRoles.map((role) => ({ checked: true, id: role, - label: JobTitleLabels[role as keyof typeof JobTitleLabels], + label: getLabelForJobTitleType(role as JobTitleType), value: role, })); }, [selectedRoles]); diff --git a/apps/portal/src/pages/test__.tsx b/apps/portal/src/pages/test__.tsx index 68ea77c9..e2b470f2 100644 --- a/apps/portal/src/pages/test__.tsx +++ b/apps/portal/src/pages/test__.tsx @@ -7,7 +7,7 @@ import { HorizontalDivider } from '@tih/ui'; import CitiesTypeahead from '~/components/shared/CitiesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CountriesTypeahead from '~/components/shared/CountriesTypeahead'; -import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypeahead'; import type { Month, MonthYearOptional, diff --git a/apps/portal/src/utils/questions/relabelQuestionAggregates.ts b/apps/portal/src/utils/questions/relabelQuestionAggregates.ts index 05ee68e9..efeac5f8 100644 --- a/apps/portal/src/utils/questions/relabelQuestionAggregates.ts +++ b/apps/portal/src/utils/questions/relabelQuestionAggregates.ts @@ -1,4 +1,5 @@ -import { JobTitleLabels } from '~/components/shared/JobTitles'; +import type { JobTitleType } from '~/components/shared/JobTitles'; +import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import type { AggregatedQuestionEncounter } from '~/types/questions'; @@ -8,7 +9,7 @@ export default function relabelQuestionAggregates({ }: AggregatedQuestionEncounter) { const newRoleCounts = Object.fromEntries( Object.entries(roleCounts).map(([roleId, count]) => [ - JobTitleLabels[roleId as keyof typeof JobTitleLabels], + getLabelForJobTitleType(roleId as JobTitleType), count, ]), ); diff --git a/apps/portal/src/utils/resumes/resumeFilters.ts b/apps/portal/src/utils/resumes/resumeFilters.ts index 3b324a1d..96233f16 100644 --- a/apps/portal/src/utils/resumes/resumeFilters.ts +++ b/apps/portal/src/utils/resumes/resumeFilters.ts @@ -1,6 +1,7 @@ import type { TypeaheadOption } from '@tih/ui'; import type { JobTitleType } from '~/components/shared/JobTitles'; +import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; import { JobTitleLabels } from '~/components/shared/JobTitles'; export type FilterId = 'experience' | 'location' | 'role'; @@ -42,7 +43,7 @@ export const getTypeaheadOption = ( case 'role': return { id: filterValue, - label: JobTitleLabels[filterValue as keyof typeof JobTitleLabels], + label: getLabelForJobTitleType(filterValue as JobTitleType), value: filterValue, }; case 'location': @@ -222,7 +223,7 @@ export const getFilterLabel = ( filters = EXPERIENCES; break; case 'role': - filters = Object.entries(JobTitleLabels).map(([slug, label]) => ({ + filters = Object.entries(JobTitleLabels).map(([slug, { label }]) => ({ id: slug, label, value: slug,