From 330abd47adc3e9ae72cd6adcbfd1d196f9c119c0 Mon Sep 17 00:00:00 2001 From: jongomez Date: Wed, 25 Oct 2023 14:25:02 +0100 Subject: [PATCH] feat: fetches jobs per board --- .../docusaurus-playground/src/pages/index.mdx | 4 +- .../components/mdx/JobList/JobList.scss | 0 .../client/components/mdx/JobList/JobList.tsx | 53 ------ .../client/components/mdx/JobList/index.ts | 1 - .../components/mdx/JobList/useFetchJobs.ts | 108 ------------- .../components/mdx/JobLists/JobLists.scss | 52 ++++++ .../components/mdx/JobLists/JobLists.tsx | 77 +++++++++ .../mdx/JobLists/SingleJobBoardList.tsx | 47 ++++++ .../client/components/mdx/JobLists/index.ts | 1 + .../mdx/JobLists/jobListsDummyData.ts | 145 +++++++++++++++++ .../components/mdx/JobLists/useFetchJobs.ts | 152 ++++++++++++++++++ .../src/client/components/mdx/index.ts | 2 +- 12 files changed, 477 insertions(+), 165 deletions(-) delete mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.scss delete mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.tsx delete mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobList/index.ts delete mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobList/useFetchJobs.ts create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.scss create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.tsx create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/SingleJobBoardList.tsx create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/index.ts create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/jobListsDummyData.ts create mode 100644 packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/useFetchJobs.ts diff --git a/packages/docusaurus-playground/src/pages/index.mdx b/packages/docusaurus-playground/src/pages/index.mdx index 0a107ce..20c25d3 100644 --- a/packages/docusaurus-playground/src/pages/index.mdx +++ b/packages/docusaurus-playground/src/pages/index.mdx @@ -18,7 +18,7 @@ import { SectionHeader, TimelineItem, ShowcaseCard, - JobList, + JobLists, } from '../components/mdx' @@ -353,4 +353,4 @@ import { Build on Waku - + diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.scss deleted file mode 100644 index e69de29..0000000 diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.tsx deleted file mode 100644 index 2f759b8..0000000 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/JobList.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useState } from 'react' -import useFetchJobs, { JobBoard, Job } from './useFetchJobs' - -type Props = { - jobBoard: JobBoard - titleFilter?: string -} - -export const JobList: React.FC = ({ jobBoard, titleFilter = '' }) => { - const { data, error, fetchJobs } = useFetchJobs() - const [isLoading, setIsLoading] = useState(false) - - const handleFetchJobs = async () => { - setIsLoading(true) - await fetchJobs([jobBoard], titleFilter) - setIsLoading(false) - } - - React.useEffect(() => { - handleFetchJobs() - }, [jobBoard, titleFilter]) - - if (isLoading) { - return
Loading...
- } - - if (error) { - return
Error: {error.message}
- } - - if (!data) { - return
No data
- } - - return ( -
-

Job List

- -
- ) -} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/index.ts deleted file mode 100644 index 22f40c2..0000000 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './JobList' diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/useFetchJobs.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/useFetchJobs.ts deleted file mode 100644 index 083aed0..0000000 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/JobList/useFetchJobs.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { useState } from 'react' - -// Inspired by: -// https://github.com/acid-info/rnd-dynamic-list/blob/master/react/useFetchJobs.js - -const jobBoards = [ - 'acidtest', - 'logos', - 'status', - 'nimbus', - 'codex', - 'nomos', - 'statusnetwork', - 'ift', - 'vac', - 'waku', -] as const - -export type JobBoard = (typeof jobBoards)[number] - -const jobBoardMappings: Record = { - acidtest: 'testacidinfo', - logos: 'logos', - status: 'status72', - nimbus: 'nimbus', - codex: 'codex', - nomos: 'nomos', - statusnetwork: 'thestatusnetwork', - ift: 'instituteoffreetechnologies', - vac: 'vac', - waku: 'waku', -} - -export type Job = { - absolute_url: string - data_compliance?: { - type: string - requires_consent: boolean - requires_processing_consent: boolean - requires_retention_consent: boolean - retention_period: null | string // Assuming retention_period can be a string or null. - }[] - internal_job_id: number - location?: { - name: string - } - metadata?: any - id: number - updated_at: string - requisition_id: string - title: string -} - -type JobData = { - jobs: Job[] - meta: { - total: number - } -} | null - -type UseFetchJobs = { - data: JobData - error: any - fetchJobs: (jobBoardsToFetch: JobBoard[], titleFilter: string) => void -} - -export const useFetchJobs = (): UseFetchJobs => { - const [data, setData] = useState(null) - const [error, setError] = useState(null) - - const fetchJobs = async (jobBoardsToFetch: JobBoard[], titleFilter) => { - try { - const resultsPerBoard: Partial> = {} - - for (let board of jobBoardsToFetch) { - const response = await fetch( - `https://boards-api.greenhouse.io/v1/boards/${jobBoardMappings[board]}/jobs`, - ) - const jobData = await response.json() - resultsPerBoard[board] = jobData.jobs - } - - let jobs = Object.values(resultsPerBoard).flat() as unknown as Job[] - - if (titleFilter) { - jobs = jobs.filter( - (job) => job.title && job.title.includes(titleFilter), - ) - } - - setData({ jobs, meta: { total: jobs.length } }) - } catch (err) { - if (err instanceof Error) { - setError(err) - } else if (typeof err === 'string') { - setError(new Error(err)) - } else { - setError(new Error('Unknown error')) - } - - console.error(err) - } - } - - return { data, error, fetchJobs } -} - -export default useFetchJobs diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.scss new file mode 100644 index 0000000..215677d --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.scss @@ -0,0 +1,52 @@ +@use '../../../css/utils'; +@use '../../../css/lsd'; + +.mdx-jl__header { + margin-bottom: 40px; + margin-top: 16px; +} + +.mdx-jl__single-job-board-container { + border-top: 1px solid rgb(var(--lsd-border-primary)); + padding-top: 8px; +} + +.mdx-jl__board-title { + font-size: 12px; + line-height: 16px; +} + +.mdx-jl__job-title-container { + display: flex; + align-items: center; + + padding-bottom: 8px; +} + +.mdx-jl__external-link-icon { + margin-left: 8px; +} + +.mdx-jl__job-list { + list-style-type: none; + padding: 8px 0 24px 0; + margin: 0; +} + +.mdx-jl__job-list-item { + padding: 14px 0; +} + +.mdx-jl__job-link { + display: block; + width: fit-content; + text-decoration: none; + + &:hover { + text-decoration: none; + + .mdx-jl__job-title { + text-decoration: underline; + } + } +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.tsx new file mode 100644 index 0000000..ac34029 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/JobLists.tsx @@ -0,0 +1,77 @@ +import { Typography } from '@acid-info/lsd-react' +import { SingleJobBoardList } from './SingleJobBoardList' +import useFetchJobs, { JobBoard, JobsPerBoard } from './useFetchJobs' +import React from 'react' +import { jobListsDummyData } from './jobListsDummyData' + +const hasJobs = (jobsPerBoard: JobsPerBoard): boolean => { + if (!jobsPerBoard) return false + let jobFound = false + + Object.keys(jobsPerBoard).forEach((board) => { + if (jobsPerBoard[board]!.jobs.length > 0) { + jobFound = true + return + } + }) + + return jobFound +} + +type JobListsProps = React.HTMLAttributes & { + jobBoard: JobBoard + titleFilter?: string + fetchAll?: boolean + useDummyData?: boolean +} + +export const JobLists: React.FC = ({ + jobBoard, + titleFilter = '', + fetchAll = false, + useDummyData = false, + ...props +}) => { + const { data, error, isLoading } = useFetchJobs( + [jobBoard], + fetchAll, + titleFilter, + useDummyData ? jobListsDummyData : null, + ) + + console.log('data', data) + console.log('error', error) + console.log('isLoading', isLoading) + + if (isLoading) { + return
Loading...
+ } + + if (error) { + return
Error fetching data
+ } + + if (!data || !hasJobs(data)) { + return
No job openings to show
+ } + + const returnedJobBoards = Object.keys(data) + + return ( +
+ + Current job openings + + {returnedJobBoards.map((board) => { + const jobDataForBoard = data[board] + return ( + + ) + })} +
+ ) +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/SingleJobBoardList.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/SingleJobBoardList.tsx new file mode 100644 index 0000000..416291e --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/SingleJobBoardList.tsx @@ -0,0 +1,47 @@ +import { Typography } from '@acid-info/lsd-react' +import { Job, JobBoard } from './useFetchJobs' +import React from 'react' +import { IconExternalLink } from '@logos-theme/components/Icon' +import './JobLists.scss' + +type SingleJobBoardListProps = { + jobBoard: JobBoard + jobs: Job[] +} + +export const SingleJobBoardList: React.FC = ({ + jobBoard, + jobs, +}) => { + return ( +
+ + {jobBoard} + + +
+ ) +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/index.ts new file mode 100644 index 0000000..8dd0638 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/index.ts @@ -0,0 +1 @@ +export * from './JobLists' diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/jobListsDummyData.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/jobListsDummyData.ts new file mode 100644 index 0000000..a3be806 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/jobListsDummyData.ts @@ -0,0 +1,145 @@ +import { JobsPerBoard } from './useFetchJobs' + +// Only for dev purposes - prevents making multiple requests to the API while developing. +export const jobListsDummyData: JobsPerBoard = { + nomos: { + jobs: [ + { + absolute_url: 'https://boards.greenhouse.io/nomos/jobs/5453089', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2315608, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5453089, + updated_at: '2023-10-20T07:06:31-04:00', + requisition_id: 'PROV-Nom-5', + title: 'Applied Network Researcher', + }, + ], + meta: { + total: 1, + }, + }, + waku: { + jobs: [ + { + absolute_url: 'https://boards.greenhouse.io/waku/jobs/5453096', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2055187, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5453096, + updated_at: '2023-10-20T07:00:46-04:00', + requisition_id: 'PROV-Sec-2', + title: 'Protocol Engineer', + }, + { + absolute_url: 'https://boards.greenhouse.io/waku/jobs/5453098', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 1830496, + location: { + name: 'Remote, Worldwide', + }, + metadata: null, + id: 5453098, + updated_at: '2023-10-20T07:01:13-04:00', + requisition_id: 'PROV-Sec-3', + title: 'Protocol Researcher (Distributed Systems)', + }, + { + absolute_url: 'https://boards.greenhouse.io/waku/jobs/5453099', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2584916, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5453099, + updated_at: '2023-10-20T07:01:37-04:00', + requisition_id: 'SDK-2', + title: 'Software Engineer (Chat SDK)', + }, + { + absolute_url: 'https://boards.greenhouse.io/waku/jobs/5453102', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2656108, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5453102, + updated_at: '2023-10-20T07:02:20-04:00', + requisition_id: 'PROV-Wak-13', + title: 'Software Engineer Distributed Systems Testing', + }, + { + absolute_url: 'https://boards.greenhouse.io/waku/jobs/5453103', + data_compliance: [ + { + type: 'gdpr', + requires_consent: false, + requires_processing_consent: false, + requires_retention_consent: false, + retention_period: null, + }, + ], + internal_job_id: 2694724, + location: { + name: 'Remote (Worldwide)', + }, + metadata: null, + id: 5453103, + updated_at: '2023-10-20T13:12:19-04:00', + requisition_id: 'WAK-BD-1', + title: 'Technical Business Development Lead ', + }, + ], + meta: { + total: 5, + }, + }, +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/useFetchJobs.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/useFetchJobs.ts new file mode 100644 index 0000000..7993f0e --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/JobLists/useFetchJobs.ts @@ -0,0 +1,152 @@ +import { useEffect, useRef, useState } from 'react' + +/* +const allJobBoards = [ + 'testacidinfo', // Acid Test job board + 'logos', + 'nimbus', + 'codex', + 'nomos', + 'thestatusnetwork', + 'instituteoffreetechnologies', // aka IFT + 'vac', + 'waku', +] as const + +*/ + +const allJobBoards = ['codex', 'nomos', 'vac', 'waku'] as const + +export type JobBoard = string + +export type Job = { + absolute_url: string + data_compliance?: { + type: string + requires_consent: boolean + requires_processing_consent: boolean + requires_retention_consent: boolean + retention_period: null | string + }[] + internal_job_id: number + location: { + name: string + } + metadata?: any + id: number + updated_at: string + requisition_id: string + title: string +} + +type JobData = { + jobs: Job[] + meta: { + total: number + } +} | null + +export type JobsPerBoard = Record | null + +type UseFetchJobsReturn = { + data: JobsPerBoard + error: Error | null + isLoading: boolean +} + +const parseJobs = (rawJobs: Job[], titleFilter?: string): JobData => { + const jobs = titleFilter + ? rawJobs.filter((job) => job.title?.includes(titleFilter)) + : rawJobs + + return { jobs, meta: { total: jobs.length } } +} + +export const useFetchJobs = ( + jobBoardsToFetch: JobBoard[] = [], + fetchAllJobBoards: boolean = false, + titleFilter: string = '', + dummyResponse: JobsPerBoard = null, +): UseFetchJobsReturn => { + const [data, setData] = useState(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const isMounted = useRef(true) + + const fetchJobs = async (): Promise<{ + jobsPerBoard: JobsPerBoard + errorData: Error | null + }> => { + try { + if (dummyResponse) { + return { jobsPerBoard: dummyResponse, errorData: null } + } + + const jobsPerBoard: JobsPerBoard = {} + + if (fetchAllJobBoards) { + const promises = allJobBoards.map((boardName) => + fetch( + `https://boards-api.greenhouse.io/v1/boards/${boardName}/jobs`, + ).then((response) => response.json()), + ) + + const allResults = await Promise.all(promises) + allResults.forEach((result, index) => { + const currentBoard = allJobBoards[index] + + if (!currentBoard) { + throw new Error('No current board found for ' + result) + } + + jobsPerBoard[currentBoard] = parseJobs(result.jobs, titleFilter) + }) + } else { + for (let board of jobBoardsToFetch) { + const response = await fetch( + `https://boards-api.greenhouse.io/v1/boards/${board}/jobs`, + ) + const jobData = await response.json() + jobsPerBoard[board] = parseJobs(jobData.jobs, titleFilter) + } + } + + return { + jobsPerBoard, + errorData: null, + } + } catch (err) { + const errorData = + err instanceof Error + ? err + : typeof err === 'string' + ? new Error(err) + : new Error('Unknown error') + + console.error(err) + return { jobsPerBoard: null, errorData } + } + } + + useEffect(() => { + if (isLoading) return + + setIsLoading(true) + fetchJobs().then(({ jobsPerBoard, errorData }) => { + // We should not update state if the component is unmounted. + if (!isMounted.current) return + + if (jobsPerBoard) setData(jobsPerBoard) + if (errorData) setError(errorData) + setIsLoading(false) + }) + + return () => { + isMounted.current = false + } + }, []) + + return { data, error, isLoading } +} + +export default useFetchJobs diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts index a09c986..ae3212b 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts @@ -24,4 +24,4 @@ export * from './Showcase' export * from './ShowcaseCard' export * from './SocialCard' export * from './TimelineItem' -export * from './JobList' +export * from './JobLists'