integrations: Add Greenhouse integration.

This commit is contained in:
Jackson
2016-12-25 01:50:46 +11:00
committed by Tim Abbott
parent 18508ca02c
commit dcca54e8a9
12 changed files with 787 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -0,0 +1,112 @@
{
"action": "new_candidate_application",
"payload": {
"application": {
"id": 265293,
"rejected_at": null,
"prospect": false,
"status": "active",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-03-18T20:28:09Z",
"source": {
"id": 27,
"public_name": "LinkedIn"
},
"credited_to": null,
"rejection_reason": null,
"current_stage": {
"id": 678901,
"name": "Application Review",
"interviews": [
{
"id": 989099,
"name":"Application Review",
"status": "collect_feedback",
"interview_kit": {
"url": "http://app.greenhouse.io/guides/67656/people/265788",
"content": "",
"questions": []
},
"interviewers": []
}
]
},
"candidate": {
"id": 265788,
"created_at": "2013-10-04T01:24:48Z",
"first_name": "Hector",
"last_name": "Porter",
"title": null,
"company": null,
"phone_numbers": [
{
"value": "330-281-8004",
"type": "home"
}
],
"email_addresses": [
{
"value": "hector.porter.265788@example.com",
"type": "personal"
}
],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": null,
"coordinator": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"attachments": [
{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}
],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [
{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [
{
"id": 237,
"name": "Community"
}
],
"offices": [
{
"id": 54,
"name": "New York",
"location": "New York, NY"
}
],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}
]
}
}
}

View File

@@ -0,0 +1,184 @@
{
"action": "hire_candidate",
"payload": {
"application": {
"id": 20,
"opening": {
"opening_id": "1234-56"
},
"credited_to": {
"id": 57,
"email": "bob_johnson1@localhost.com",
"name": "Robert Johnson"
},
"source": {
"id": 25,
"public_name": "Monster"
},
"candidate": {
"id": 19,
"first_name": "Johnny",
"last_name": "Smith",
"title": "Previous Title",
"external_id": "00000",
"phone_numbers": [{
"value": "518-555-1212",
"type": "work"
}, {
"value": "212-555-1212",
"type": "home"
}],
"email_addresses": [{
"value": "personal@example.com",
"type": "personal"
}, {
"value": "work@example.com",
"type": "work"
}],
"addresses": [{
"value": "455 Broadway New York, NY 10280",
"type": "home"
}],
"recruiter": {
"id": 55,
"email": "bob_johnson@localhost.com",
"name": "Bob Johnson"
},
"coordinator": {
"id": 56,
"email": "bob_johnson_approver1@localhost.com",
"name": "Robert J Approver"
},
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}],
"custom_fields": {
"desired_level": {
"name": "Desired Level",
"type": "short_text",
"value": "Senior"
},
"favorite_programming_language": {
"name": "Favorite Programming Language",
"type": "short_text",
"value": "Rails"
}
}
},
"job": {
"id": 20,
"name": "Developer",
"open_date": "2014-11-20T22:49:14Z",
"close_date": "2014-11-25T22:49:14Z",
"requisition_id": null,
"departments": [{
"id": 7,
"name": "Technology"
}],
"offices": [{
"id": 13,
"name": "New York City",
"location": {
"name": "New York, NY"
}
}, {
"id": 14,
"name": "St. Louis",
"location": null
}],
"custom_fields": {
"approved": {
"name": "Approved",
"type": "boolean",
"value": true
},
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full-time"
},
"salary_range": {
"name": "SalaryRange",
"type": "currency_range",
"value": {
"unit": "USD",
"min_value": "10000.0",
"max_value": "20000.0"
}
}
}
},
"jobs": [{
"id": 20,
"name": "Developer",
"opened_at": "2014-11-20T22:49:14Z",
"closed_at": "2014-11-25T22:49:14Z",
"requisition_id": null,
"departments": [{
"id": 7,
"name": "Technology"
}],
"offices": [{
"id": 13,
"name": "New York City",
"location": {
"name": "New York, NY"
}
}, {
"id": 14,
"name": "St. Louis",
"location": null
}],
"custom_fields": {
"approved": {
"name": "Approved",
"type": "boolean",
"value": true
},
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full-time"
},
"salary_range": {
"name": "SalaryRange",
"type": "currency_range",
"value": {
"unit": "USD",
"min_value": "10000.0",
"max_value": "20000.0"
}
}
}
}],
"offer": {
"id": 13,
"version": 2,
"created_at": "2014-11-20T22:49:14Z",
"sent_at": "2014-11-10",
"resolved_at": "2014-11-20T22:49:14Z",
"starts_at": "2015-01-23",
"custom_fields": {
"salary": {
"name": "Salary",
"type": "currency",
"value": {
"amount": 80000,
"unit": "USD"
}
},
"seasons": {
"name": "Seasons",
"type": "multi_select",
"value": [
"Season 1",
"Season 2"
]
}
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
{
"action": "reject_candidate",
"payload": {
"application": {
"id": 265293,
"rejected_at": "2015-02-11T15:50:41Z",
"prospect": false,
"status": "rejected",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-02-11T15:50:41Z",
"source": {
"id": 27,
"public_name": "LinkedIn"
},
"credited_to": null,
"rejection_reason": {
"id": 14,
"name": "Too Junior",
"type": {
"id": 3,
"name": "Wrong skill set"
}
},
"candidate": {
"id": 265788,
"created_at": "2013-10-04T01:24:48Z",
"first_name": "Hector",
"last_name": "Porter",
"title": null,
"company": null,
"phone_numbers": [{
"value": "330-281-8004",
"type": "home"
}],
"email_addresses": [{
"value": "hector.porter.265788@example.com",
"type": "personal"
}],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": null,
"coordinator": null,
"photo_url": "www.example.com/photo.png",
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}],
"tags": [
"Imported"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [{
"id": 237,
"name": "Community"
}],
"offices": [{
"id": 54,
"name": "New York",
"location": "New York, NY"
}],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}]
}
}
}

View File

@@ -0,0 +1,130 @@
{
"action": "candidate_stage_change",
"payload": {
"application": {
"id": 265277,
"rejected_at": null,
"prospect": false,
"status": "active",
"applied_at": "2013-03-22T00:00:00Z",
"last_activity_at": "2015-02-09T16:38:36Z",
"source": {
"id": 31,
"name": "Agency"
},
"credited_to": {
"id": 15,
"email": "ada@example.com",
"name": "Ada Lacey"
},
"rejection_reason": null,
"current_stage": {
"id": 71416,
"name": "Assessment",
"interviews": [{
"id": 113101,
"name": "Assessment",
"status": "to_be_scheduled",
"interview_kit": {
"url": "https://app.greenhouse.io/guides/113153/people/265772",
"content": "Assess their skills",
"questions": []
},
"interviewers": [{
"id": 2622,
"display_name": "Carl Buddha",
"status": "tentative"
}]
}]
},
"candidate": {
"id": 265772,
"created_at": "2013-10-04T01:24:44Z",
"first_name": "Giuseppe",
"last_name": "Hurley",
"external_id": "241b399ce4b0fd1c84e5528d",
"title": "Great Person",
"company": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"phone_numbers": [{
"value": "330-281-8004",
"type": "home"
}],
"email_addresses": [{
"value": "giuseppe.hurley@example.com",
"type": "personal"
}],
"addresses": [{
"value": "123 Fake St.",
"type": "home"
}],
"website_addresses": [{
"value": "ghurley.example.com",
"type": "personal"
}],
"social_media_addresses": [{
"value": "linkedin.example.com/ghurley"
}],
"recruiter": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"coordinator": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"attachments": [{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}, {
"filename": "cover_letter.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "cover_letter"
}, {
"filename": "portfolio.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "attachment"
}],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [{
"id": 237,
"name": "Community"
}],
"offices": [{
"id": 54,
"name": "New York",
"location": "New York, NY"
}],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}]
}
}
}

View File

@@ -0,0 +1,104 @@
{
"action": "new_prospect_application",
"payload": {
"application": {
"id": 979554,
"rejected_at": null,
"prospect": true,
"status": "active",
"applied_at": "2014-12-02T23:10:16Z",
"last_activity_at": "2014-12-02T23:10:16Z",
"current_stage": null,
"source": {
"id": 13,
"public_name": "Referral"
},
"credited_to": {
"id": 2622,
"email": "carl.buddha.2622@example.com",
"name": "Carl Buddha"
},
"rejection_reason": null,
"candidate": {
"id": 968190,
"created_at": "2014-12-02T23:10:16Z",
"first_name": "Trisha",
"last_name": "Troy",
"title": null,
"company": null,
"phone_numbers": [
{
"value": "123456",
"type": "other"
}
],
"email_addresses": [
{
"value": "t.troy@example.com",
"type": "personal"
}
],
"addresses": [],
"website_addresses": [],
"social_media_addresses": [],
"recruiter": {
"id": 3128,
"email": "alicia.flopple.3128@example.com",
"name": "Alicia Flopple"
},
"coordinator": null,
"photo_url": "https://prod-heroku.s3.amazonaws.com/...",
"attachments": [
{
"filename": "resume.pdf",
"url": "https://prod-heroku.s3.amazonaws.com/...",
"type": "resume"
}
],
"tags": [
"Import from Previous ATS"
],
"custom_fields": {
"favorite_color": {
"name": "Favorite Color",
"type": "short_text",
"value": "Blue"
}
}
},
"jobs": [
{
"id": 3485,
"name": "Designer",
"requisition_id": null,
"notes": "Digital and print",
"job_post_id": 54321,
"status": "open",
"created_at": "2013-10-02T22:59:29Z",
"opened_at": "2015-01-23T00:25:04Z",
"closed_at": null,
"departments": [
{
"id": 237,
"name": "Community"
}
],
"offices": [
{
"id": 54,
"name": "New York",
"location": "New York, NY"
}
],
"custom_fields": {
"employment_type": {
"name": "Employment Type",
"type": "single_select",
"value": "Full Time"
}
}
}
]
}
}
}

View File

@@ -133,6 +133,7 @@ WEBHOOK_INTEGRATIONS = [
),
WebhookIntegration('gitlab', display_name='GitLab'),
WebhookIntegration('gosquared', display_name='GoSquared'),
WebhookIntegration('greenhouse', display_name='Greenhouse'),
WebhookIntegration('hellosign', display_name='HelloSign'),
WebhookIntegration('helloworld', display_name='Hello World'),
WebhookIntegration('heroku', display_name='Heroku'),

View File

View File

@@ -0,0 +1,32 @@
<p>
First, create a stream where you would like to receive Greenhouse
notifications and subscribe all interested parties to the
stream. The integration will automatically use the default
stream <code>greenhouse</code> if no stream is supplied, though you
will still need to create the stream manually even though it's the
default.
</p>
<p>
Next, go to the Zulip settings page and create a bot named
Greenhouse. Go to the account settings page of your Greenhouse
account and under Webhooks, add the below URL and name the
integration, Zulip.
</p>
<p><code>{{ external_api_uri_subdomain }}/v1/external/greenhouse?api_key=abcdefgh&amp;stream=greenhouse</code></p>
<p>
Note: <code>api_key</code> must be reconfigured to be the API key of your Zulip bot.<br />
If you want to change the stream that receives notifications, change <code>stream=</code> in the URL. <br />
To change the topic displayed by the bot, simply append <code>&amp;topic=name</code> to the end of the above URL.
Where <code>name</code> is your topic.
</p>
<p>
<b>Congratulations! You're all set</b><br />
Your messages should look like this:
</p>
<p><img class="screenshot" src="/static/images/integrations/greenhouse/000.png" /></p>

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from typing import Text
from zerver.lib.test_classes import WebhookTestCase
class GreenhouseHookTests(WebhookTestCase):
STREAM_NAME = 'greenhouse'
URL_TEMPLATE = "/api/v1/external/greenhouse?stream={stream}&api_key={api_key}"
FIXTURE_DIR_NAME = 'greenhouse'
CONTENT_TYPE = "application/x-www-form-urlencoded"
def test_message_candidate_hired(self):
# type: () -> None
expected_subject = "Hire Candidate - 19"
expected_message = ("Hire Candidate\n>Johnny Smith\nID: 19"
"\nApplying for role:\nDeveloper\n**Emails:**"
"\nPersonal\npersonal@example.com\nWork\nwork@example.com\n\n\n>"
"**Attachments:**\n[Resume](https://prod-heroku.s3.amazonaws.com/...)\n")
self.send_and_test_stream_message('candidate_hired',
expected_subject,
expected_message,
content_type=self.CONTENT_TYPE)
def test_message_candidate_rejected(self):
# type: () -> None
expected_subject = "Reject Candidate - 265788"
expected_message = ("Reject Candidate\n>Hector Porter\nID: "
"265788\nApplying for role:\nDesigner"
"\n**Emails:**\nPersonal\n"
"hector.porter.265788@example.com\n\n\n>"
"**Attachments:**\n[Resume](https://prod-heroku.s3.amazonaws.com/...)\n")
self.send_and_test_stream_message('candidate_rejected',
expected_subject,
expected_message,
content_type=self.CONTENT_TYPE)
def test_message_candidate_stage_change(self):
# type: () -> None
expected_subject = "Candidate Stage Change - 265772"
expected_message = ("Candidate Stage Change\n>Giuseppe Hurley"
"\nID: 265772\nApplying for role:\n"
"Designer\n**Emails:**\nPersonal"
"\ngiuseppe.hurley@example.com\n\n\n>"
"**Attachments:**\n[Resume](https://prod-heroku.s3.amazonaws.com/...)"
"\n[Cover_Letter](https://prod-heroku.s3.amazonaws.com/...)"
"\n[Attachment](https://prod-heroku.s3.amazonaws.com/...)\n")
self.send_and_test_stream_message('candidate_stage_change',
expected_subject,
expected_message,
content_type=self.CONTENT_TYPE)
def test_message_prospect_created(self):
# type: () -> None
expected_subject = "New Prospect Application - 968190"
expected_message = ("New Prospect Application\n>Trisha Troy"
"\nID: 968190\nApplying for role:\n"
"Designer\n**Emails:**\nPersonal"
"\nt.troy@example.com\n\n\n>**Attachments:**"
"\n[Resume](https://prod-heroku.s3.amazonaws.com/...)\n")
self.send_and_test_stream_message('prospect_created',
expected_subject,
expected_message,
content_type=self.CONTENT_TYPE)
def get_body(self, fixture_name):
# type: (Text) -> Text
return self.fixture_data("greenhouse", fixture_name, file_type="json")

View File

@@ -0,0 +1,64 @@
from __future__ import absolute_import
from django.utils.translation import ugettext as _
from django.http import HttpRequest, HttpResponse
from typing import Any
from zerver.lib.actions import check_send_message
from zerver.lib.response import json_success, json_error
from zerver.decorator import REQ, has_request_variables, api_key_only_webhook_view
from zerver.models import UserProfile, Client
import ujson
MESSAGE_TEMPLATE = "Applying for role:\n{}\n**Emails:**\n{}\n\n>**Attachments:**\n{}"
def dict_list_to_string(some_list):
# type: (List[Any]) -> str
internal_template = ''
for item in some_list:
item_type = item.get('type', '').title()
item_value = item.get('value')
item_url = item.get('url')
if item_type and item_value:
internal_template += "{}\n{}\n".format(item_type, item_value)
elif item_type and item_url:
internal_template += "[{}]({})\n".format(item_type, item_url)
return internal_template
def message_creator(action, application):
# type: (str, Dict[str, Any]) -> str
message = MESSAGE_TEMPLATE.format(
application['jobs'][0]['name'],
dict_list_to_string(application['candidate']['email_addresses']),
dict_list_to_string(application['candidate']['attachments']))
return message
@api_key_only_webhook_view('Greenhouse')
@has_request_variables
def api_greenhouse_webhook(request, user_profile, client,
payload=REQ(argument_type='body'),
stream=REQ(default='greenhouse'), topic=REQ(default=None)):
# type: (HttpRequest, UserProfile, Client, Dict[str, Any], str, str) -> HttpResponse
try:
if payload['action'] == 'update_candidate':
candidate = payload['payload']['candidate']
else:
candidate = payload['payload']['application']['candidate']
action = payload['action'].replace('_', ' ').title()
body = "{}\n>{} {}\nID: {}\n{}".format(
action,
candidate['first_name'],
candidate['last_name'],
str(candidate['id']),
message_creator(payload['action'],
payload['payload']['application']))
if topic is None:
topic = "{} - {}".format(action, str(candidate['id']))
except KeyError as e:
return json_error(_("Missing key {} in JSON").format(str(e)))
check_send_message(user_profile, client, 'stream', [stream], topic, body)
return json_success()