From 4611ed9b07d0ac936ed9a2c750ee264e05059acd Mon Sep 17 00:00:00 2001 From: Hassan_Wari <85742599+hassan254-prog@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:53:30 +0300 Subject: [PATCH] greenhouse-basic syncs integration templates (#1520) * greenhouse-basic syncs integration templates * fix doc --- docs-v2/integration-templates/greenhouse.mdx | 21 +++ docs-v2/integration-templates/overview.mdx | 4 + docs-v2/integrations/all/greenhouse.mdx | 7 +- docs-v2/mint.json | 1 + .../greenhouse-applications.ts | 55 +++++++ .../greenhouse-basic/greenhouse-candidates.ts | 60 +++++++ .../greenhouse-basic/greenhouse-jobs.ts | 51 ++++++ .../greenhouse-basic/nango.yaml | 147 ++++++++++++++++++ packages/shared/flows.yaml | 146 +++++++++++++++++ packages/shared/providers.yaml | 4 + .../template-logos/greenhouse-basic.svg | 3 + 11 files changed, 497 insertions(+), 2 deletions(-) create mode 100644 docs-v2/integration-templates/greenhouse.mdx create mode 100644 integration-templates/greenhouse-basic/greenhouse-applications.ts create mode 100644 integration-templates/greenhouse-basic/greenhouse-candidates.ts create mode 100644 integration-templates/greenhouse-basic/greenhouse-jobs.ts create mode 100644 integration-templates/greenhouse-basic/nango.yaml create mode 100644 packages/webapp/public/images/template-logos/greenhouse-basic.svg diff --git a/docs-v2/integration-templates/greenhouse.mdx b/docs-v2/integration-templates/greenhouse.mdx new file mode 100644 index 0000000000..eec3d484d8 --- /dev/null +++ b/docs-v2/integration-templates/greenhouse.mdx @@ -0,0 +1,21 @@ +--- +title: 'Greenhouse API Integration Template' +sidebarTitle: 'Greenhouse' +--- + +## Get started with the Greenhouse template + + + Learn how to use integration templates in Nango + + + + Get the latest version of the Greenhouse integration template from GitHub + + +## Need help with the template? +Please reach out on the [Slack community](https://nango.dev/slack), we are very active there and happy to help! \ No newline at end of file diff --git a/docs-v2/integration-templates/overview.mdx b/docs-v2/integration-templates/overview.mdx index c8e6bed4a9..fbb687b977 100644 --- a/docs-v2/integration-templates/overview.mdx +++ b/docs-v2/integration-templates/overview.mdx @@ -69,6 +69,10 @@ As Nango and its community expand, we're looking forward to offering hundreds of Sync users, organization units, and authorized 3rd party APIs from Google Workspace + + Sync applications, candidates and jobs from harvest resources on Greenhouse + + Sync users and groups from Microsoft Active Directory diff --git a/docs-v2/integrations/all/greenhouse.mdx b/docs-v2/integrations/all/greenhouse.mdx index 3db8c401a8..995570fe96 100644 --- a/docs-v2/integrations/all/greenhouse.mdx +++ b/docs-v2/integrations/all/greenhouse.mdx @@ -3,13 +3,13 @@ title: Greenhouse sidebarTitle: Greenhouse --- -API configuration: [`greenhouse`](https://nango.dev/providers.yaml) +API configuration: [`greenhouse`](https://nango.dev/providers.yaml), [`greenhouse-basic`](https://nango.dev/providers.yaml) ## Features | Feature | Status | | -------------------------------------------------------------------------------- | ------------------------------- | -| [Auth (OAuth)](/guides/oauth) | ✅ | +| Auth ([Basic](/guides/api-key) + [OAuth](/guides/oauth)) | ✅ | | [Syncs](/guides/sync) & [Actions](/guides/action) | ✅ | | [Nango Proxy](/guides/proxy) | ✅ | | Auto-pagination | 🚫 (time to contribute: <1h) | @@ -18,16 +18,19 @@ API configuration: [`greenhouse`](https://nango.dev/providers.yaml) We can implement missing features in <48h, just ask for it in the [community](https://nango.dev/slack). ## Getting started +Greenhouse offers both `Basic` and `OAuth` as authentication and Nango implements both. - [How to register an Application](https://developers.greenhouse.io/candidate-ingestion.html#authentication) - [OAuth-related docs](https://developers.greenhouse.io/candidate-ingestion.html#introduction) - [List of OAuth scopes](https://developers.greenhouse.io/candidate-ingestion.html#oauth-scopes) - [API](https://developers.greenhouse.io) +- [Harvest API authentication](https://developers.greenhouse.io/harvest.html#authentication) Need help getting started? Get help in the [community](https://nango.dev/slack). ## API gotchas +- For `Basic` auth pass Greenhouse API token as username and the password should be blank. - If you need to use the proxy, it is important to provide the resource that you will be calling in the config (as green house has many forms for API resource). Please see [here](https://developers.greenhouse.io/) for different types of resources. Once confirmed, you have to add `resource` as a config. Add Getting Started links and Gotchas by [editing this page](https://github.com/nangohq/nango/tree/master/docs-v2/integrations/all/gorgias.mdx) diff --git a/docs-v2/mint.json b/docs-v2/mint.json index ec1574dddc..64e8c13f8e 100644 --- a/docs-v2/mint.json +++ b/docs-v2/mint.json @@ -92,6 +92,7 @@ "integration-templates/github", "integration-templates/gmail", "integration-templates/google-workspace", + "integration-templates/greenhouse", "integration-templates/hackerrank-work", "integration-templates/hubspot", "integration-templates/intercom", diff --git a/integration-templates/greenhouse-basic/greenhouse-applications.ts b/integration-templates/greenhouse-basic/greenhouse-applications.ts new file mode 100644 index 0000000000..5c3156fe57 --- /dev/null +++ b/integration-templates/greenhouse-basic/greenhouse-applications.ts @@ -0,0 +1,55 @@ +import type { GreenhouseApplication, NangoSync } from './models'; + +export default async function fetchData(nango: NangoSync) { + let totalRecords = 0; + + try { + const endpoint = '/v1/applications'; + const config = { + ...(nango.lastSyncDate ? { params: { created_after: nango.lastSyncDate?.toISOString() } } : {}), + paginate: { + type: 'link', + link_path_in_response_body: 'links.next', + limit_name_in_request: 'per_page', + limit: 100 + } + }; + for await (const application of nango.paginate({ ...config, endpoint })) { + const mappedApplication: GreenhouseApplication[] = application.map(mapApplication) || []; + + const batchSize: number = mappedApplication.length; + totalRecords += batchSize; + await nango.log(`Saving batch of ${batchSize} application(s) (total application(s): ${totalRecords})`); + await nango.batchSave(mappedApplication, 'GreenhouseApplication'); + } + } catch (error: any) { + throw new Error(`Error in fetchData: ${error.message}`); + } +} + +function mapApplication(application: any): GreenhouseApplication { + return { + id: application.id, + candidate_id: application.candidate_id, + prospect: application.prospect, + applied_at: application.applied_at, + rejected_at: application.rejected_at, + last_activity_at: application.last_activity_at, + location: application.location, + source: application.source, + credited_to: application.credited_to, + rejection_reason: application.rejection_reason, + rejection_details: application.rejection_details, + jobs: application.jobs, + job_post_id: application.job_post_id, + status: application.status, + current_stage: application.current_stage, + answers: application.answers, + prospective_office: application.prospective_office, + prospective_department: application.prospective_department, + prospect_detail: application.prospect_detail, + custom_fields: application.custom_fields, + keyed_custom_fields: application.keyed_custom_fields, + attachments: application.attachments + }; +} diff --git a/integration-templates/greenhouse-basic/greenhouse-candidates.ts b/integration-templates/greenhouse-basic/greenhouse-candidates.ts new file mode 100644 index 0000000000..28b3145514 --- /dev/null +++ b/integration-templates/greenhouse-basic/greenhouse-candidates.ts @@ -0,0 +1,60 @@ +import type { GreenhouseCandidate, NangoSync } from './models'; + +export default async function fetchData(nango: NangoSync) { + let totalRecords = 0; + + try { + const endpoint = '/v1/candidates'; + const config = { + ...(nango.lastSyncDate ? { params: { created_after: nango.lastSyncDate?.toISOString() } } : {}), + paginate: { + type: 'link', + link_path_in_response_body: 'links.next', + limit_name_in_request: 'per_page', + limit: 100 + } + }; + for await (const candidate of nango.paginate({ ...config, endpoint })) { + const mappedCandidate: GreenhouseCandidate[] = candidate.map(mapCandidate) || []; + + const batchSize: number = mappedCandidate.length; + totalRecords += batchSize; + await nango.log(`Saving batch of ${batchSize} candidate(s) (total candidate(s): ${totalRecords})`); + await nango.batchSave(mappedCandidate, 'GreenhouseCandidate'); + } + } catch (error: any) { + throw new Error(`Error in fetchData: ${error.message}`); + } +} + +function mapCandidate(candidate: any): GreenhouseCandidate { + return { + id: candidate.id, + first_name: candidate.first_name, + last_name: candidate.last_name, + company: candidate.company, + title: candidate.title, + created_at: candidate.created_at, + updated_at: candidate.updated_at, + last_activity: candidate.last_activity, + is_private: candidate.is_private, + photo_url: candidate.photo_url, + attachments: candidate.attachments, + application_ids: candidate.application_ids, + phone_numbers: candidate.phone_numbers, + addresses: candidate.addresses, + email_addresses: candidate.email_addresses, + website_addresses: candidate.website_addresses, + social_media_addresses: candidate.social_media_addresses, + recruiter: candidate.recruiter, + coordinator: candidate.coordinator, + can_email: candidate.can_email, + tags: candidate.tags, + applications: candidate.applications, + educations: candidate.educations, + employments: candidate.employments, + linked_user_ids: candidate.linked_user_ids, + custom_fields: candidate.custom_fields, + keyed_custom_fields: candidate.keyed_custom_fields + }; +} diff --git a/integration-templates/greenhouse-basic/greenhouse-jobs.ts b/integration-templates/greenhouse-basic/greenhouse-jobs.ts new file mode 100644 index 0000000000..987f54b3ad --- /dev/null +++ b/integration-templates/greenhouse-basic/greenhouse-jobs.ts @@ -0,0 +1,51 @@ +import type { GreenhouseJob, NangoSync } from './models'; + +export default async function fetchData(nango: NangoSync) { + let totalRecords = 0; + + try { + const endpoint = '/v1/jobs'; + const config = { + ...(nango.lastSyncDate ? { params: { created_after: nango.lastSyncDate?.toISOString() } } : {}), + paginate: { + type: 'link', + link_path_in_response_body: 'links.next', + limit_name_in_request: 'per_page', + limit: 100 + } + }; + for await (const job of nango.paginate({ ...config, endpoint })) { + const mappedJob: GreenhouseJob[] = job.map(mapJob) || []; + + const batchSize: number = mappedJob.length; + totalRecords += batchSize; + await nango.log(`Saving batch of ${batchSize} job(s) (total job(s): ${totalRecords})`); + await nango.batchSave(mappedJob, 'GreenhouseJob'); + } + } catch (error: any) { + throw new Error(`Error in fetchData: ${error.message}`); + } +} + +function mapJob(job: any): GreenhouseJob { + return { + id: job.id, + name: job.name, + requisition_id: job.requisition_id, + notes: job.notes, + confidential: job.confidential, + status: job.status, + created_at: job.created_at, + opened_at: job.opened_at, + closed_at: job.closed_at, + updated_at: job.updated_at, + is_template: job.is_template, + copied_from_id: job.copied_from_id, + departments: job.departments, + offices: job.offices, + custom_fields: job.custom_fields, + keyed_custom_fields: job.keyed_custom_fields, + hiring_team: job.hiring_team, + openings: job.openings + }; +} diff --git a/integration-templates/greenhouse-basic/nango.yaml b/integration-templates/greenhouse-basic/nango.yaml new file mode 100644 index 0000000000..ba2595b467 --- /dev/null +++ b/integration-templates/greenhouse-basic/nango.yaml @@ -0,0 +1,147 @@ +integrations: + greenhouse-basic: + greenhouse-applications: + runs: every 6 hours + returns: + - GreenhouseApplication + description: | + Fetches all organization's applications from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. + greenhouse-candidates: + runs: every 6 hours + returns: + - GreenhouseCandidate + description: | + Fetches all organization's candidates from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. + greenhouse-jobs: + runs: every 6 hours + returns: + - GreenhouseJob + description: | + Fetches all organization's jobs from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. +models: + GreenhouseApplication: + id: string + candidate_id: string + prospect: boolean + applied_at: date + rejected_at: date + last_activity_at: date + location: + address: string + source: + id: string + public_name: string + credited_to: + id: string + first_name: string + last_name: string + name: string + employee_id: string + rejection_reason: + id: string + name: string + type: + id: string + name: string + rejection_details: + custom_fields: object + keyed_custom_fields: object + jobs: [] + job_post_id: string + status: string + current_stage: + id: string + name: string + answers: [] + prospective_office: + primary_contact_user_id: string + parent_id: string + name: string + location: + name: string + id: string + external_id: string + child_ids: [] + prospective_department: + parent_id: string + name: string + id: string + external_id: string + child_ids: [] + prospect_detail: + prospect_pool: + id: string + name: string + prospect_stage: + id: string + name: string + prospect_owner: + id: string + name: string + custom_fields: object + keyed_custom_fields: object + attachments: [] + GreenhouseCandidate: + id: string + first_name: string + last_name: string + company: string + title: string + created_at: date + updated_at: date + last_activity: date + is_private: boolean + photo_url: string + attachments: [] + application_ids: [] + phone_numbers: [] + addresses: [] + email_addresses: [] + website_addresses: [] + social_media_addresses: [] + recruiter: + id: string + first_name: string + last_name: string + name: string + employee_id: string + coordinator: + id: string + first_name: string + last_name: string + name: string + employee_id: string + can_email: boolean + tags: [] + applications: [] + educations: [] + employments: [] + linked_user_ids: string + custom_fields: object + keyed_custom_fields: object + GreenhouseJob: + id: string + name: string + requisition_id: string + notes: string + confidential: boolean + status: string + created_at: date + opened_at: date + closed_at: date + updated_at: date + is_template: boolean + copied_from_id: string + departments: [] + offices: [] + custom_fields: object + keyed_custom_fields: object + hiring_team: + hiring_managers: [] + recruiters: [] + coordinators: [] + sourcers: [] + openings: [] diff --git a/packages/shared/flows.yaml b/packages/shared/flows.yaml index c96cab1ea3..024488b163 100644 --- a/packages/shared/flows.yaml +++ b/packages/shared/flows.yaml @@ -486,6 +486,152 @@ integrations: anonymous_app: boolean scopes: string rawName: google-workspace + greenhouse-basic: + greenhouse-applications: + runs: every 6 hours + returns: + - GreenhouseApplication + description: | + Fetches all organization's applications from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. + greenhouse-candidates: + runs: every 6 hours + returns: + - GreenhouseCandidate + description: | + Fetches all organization's candidates from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. + greenhouse-jobs: + runs: every 6 hours + returns: + - GreenhouseJob + description: | + Fetches all organization's jobs from greenhouse. + Details: incremental sync, doesn't track deletes, metadata is not required. + models: + GreenhouseApplication: + id: string + candidate_id: string + prospect: boolean + applied_at: date + rejected_at: date + last_activity_at: date + location: + address: string + source: + id: string + public_name: string + credited_to: + id: string + first_name: string + last_name: string + name: string + employee_id: string + rejection_reason: + id: string + name: string + type: + id: string + name: string + rejection_details: + custom_fields: object + keyed_custom_fields: object + jobs: [] + job_post_id: string + status: string + current_stage: + id: string + name: string + answers: [] + prospective_office: + primary_contact_user_id: string + parent_id: string + name: string + location: + name: string + id: string + external_id: string + child_ids: [] + prospective_department: + parent_id: string + name: string + id: string + external_id: string + child_ids: [] + prospect_detail: + prospect_pool: + id: string + name: string + prospect_stage: + id: string + name: string + prospect_owner: + id: string + name: string + custom_fields: object + keyed_custom_fields: object + attachments: [] + GreenhouseCandidate: + id: string + first_name: string + last_name: string + company: string + title: string + created_at: date + updated_at: date + last_activity: date + is_private: boolean + photo_url: string + attachments: [] + application_ids: [] + phone_numbers: [] + addresses: [] + email_addresses: [] + website_addresses: [] + social_media_addresses: [] + recruiter: + id: string + first_name: string + last_name: string + name: string + employee_id: string + coordinator: + id: string + first_name: string + last_name: string + name: string + employee_id: string + can_email: boolean + tags: [] + applications: [] + educations: [] + employments: [] + linked_user_ids: string + custom_fields: object + keyed_custom_fields: object + GreenhouseJob: + id: string + name: string + requisition_id: string + notes: string + confidential: boolean + status: string + created_at: date + opened_at: date + closed_at: date + updated_at: date + is_template: boolean + copied_from_id: string + departments: [] + offices: [] + custom_fields: object + keyed_custom_fields: object + hiring_team: + hiring_managers: [] + recruiters: [] + coordinators: [] + sourcers: [] + openings: [] hackerrank-work: hackerrank-work-interviews: runs: every 6 hours diff --git a/packages/shared/providers.yaml b/packages/shared/providers.yaml index 1b361e3588..897e58e8a0 100644 --- a/packages/shared/providers.yaml +++ b/packages/shared/providers.yaml @@ -541,6 +541,10 @@ greenhouse: token_url: https://api.greenhouse.io/oauth/token proxy: base_url: https://${connectionConfig.resource}.greenhouse.io +greenhouse-basic: + auth_mode: BASIC + proxy: + base_url: https://${connectionConfig.resource}.greenhouse.io gumroad: auth_mode: OAUTH2 authorization_url: https://gumroad.com/oauth/authorize diff --git a/packages/webapp/public/images/template-logos/greenhouse-basic.svg b/packages/webapp/public/images/template-logos/greenhouse-basic.svg new file mode 100644 index 0000000000..211e161917 --- /dev/null +++ b/packages/webapp/public/images/template-logos/greenhouse-basic.svg @@ -0,0 +1,3 @@ + + +