From 28ea90d53765af12c167702e2ac960f5270595f3 Mon Sep 17 00:00:00 2001 From: Abdellahi Mezid <135601200+Abdellahitech@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:19:49 +0100 Subject: [PATCH] feat: connector jobology --- README.md | 1 + manifest.json | 204 ++++++++++++++++++ src/hrflow_connectors/__init__.py | 2 + .../connectors/jobology/README.md | 72 +++++++ .../connectors/jobology/__init__.py | 1 + .../connectors/jobology/connector.py | 83 +++++++ .../connectors/jobology/docs/catch_profile.md | 61 ++++++ .../connectors/jobology/logo.jpeg | Bin 0 -> 4813 bytes .../connectors/jobology/schemas.py | 20 ++ .../connectors/jobology/test-config.yaml | 96 +++++++++ .../connectors/jobology/warehouse.py | 55 +++++ 11 files changed, 595 insertions(+) create mode 100644 src/hrflow_connectors/connectors/jobology/README.md create mode 100644 src/hrflow_connectors/connectors/jobology/__init__.py create mode 100644 src/hrflow_connectors/connectors/jobology/connector.py create mode 100644 src/hrflow_connectors/connectors/jobology/docs/catch_profile.md create mode 100644 src/hrflow_connectors/connectors/jobology/logo.jpeg create mode 100644 src/hrflow_connectors/connectors/jobology/schemas.py create mode 100644 src/hrflow_connectors/connectors/jobology/test-config.yaml create mode 100644 src/hrflow_connectors/connectors/jobology/warehouse.py diff --git a/README.md b/README.md index 732448708..6cd1af05b 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ We invite developers to join us in our mission to bring AI and data integration | **Indeed** | Job Board | 🎯 | | | | **Inzojob** | Job Board | 🎯 | | | | **Jobijoba** | Job Board | 🎯 | | | +| [**Jobology**](./src/hrflow_connectors/connectors/jobology/README.md) | Job Board | :white_check_mark: | *21/12/2022* | *08/01/2024* | :x: | :x: | :x: | :x: | | **Jobrapido** | Job Board | 🎯 | | | | **JobTeaser** | Job Board | 🎯 | | | | **Jobtransport** | Job Board | 🎯 | | | diff --git a/manifest.json b/manifest.json index 7bb5eb904..bad921f3e 100644 --- a/manifest.json +++ b/manifest.json @@ -25049,6 +25049,210 @@ ], "type": "ATS", "logo": "https://raw.githubusercontent.com/Riminder/hrflow-connectors/master/src/hrflow_connectors/connectors/digitalrecruiters/logo.png" + }, + { + "name": "Jobology", + "actions": [ + { + "name": "catch_profile", + "action_type": "inbound", + "action_parameters": { + "title": "TriggerViewActionParameters", + "type": "object", + "properties": { + "read_mode": { + "description": "If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read.", + "default": "sync", + "allOf": [ + { + "$ref": "#/definitions/ReadMode" + } + ] + }, + "logics": { + "title": "logics", + "description": "List of logic functions. Each function should have the following signature typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]. The final list should be exposed in a variable named 'logics'.", + "template": "\nimport typing as t\n\ndef logic_1(item: t.Dict) -> t.Union[t.Dict, None]:\n return None\n\ndef logic_2(item: t.Dict) -> t.Uniont[t.Dict, None]:\n return None\n\nlogics = [logic_1, logic_2]\n", + "type": "code_editor" + }, + "format": { + "title": "format", + "description": "Formatting function. You should expose a function named 'format' with following signature typing.Callable[[typing.Dict], typing.Dict]", + "template": "\nimport typing as t\n\ndef format(item: t.Dict) -> t.Dict:\n return item\n", + "type": "code_editor" + }, + "event_parser": { + "title": "event_parser", + "description": "Event parsing function for **CATCH** integrations. You should expose a function named 'event_parser' with following signature typing.Callable[[typing.Dict], typing.Dict]", + "template": "\nimport typing as t\n\ndef event_parser(event: t.Dict) -> t.Dict:\n parsed = dict()\n parsed[\"user_id\"] = event[\"email\"]\n parsed[\"thread_id\"] = event[\"subscription_id\"]\n return parsed\n", + "type": "code_editor" + } + }, + "additionalProperties": false, + "definitions": { + "ReadMode": { + "title": "ReadMode", + "description": "An enumeration.", + "enum": [ + "sync", + "incremental" + ] + } + } + }, + "data_type": "profile", + "trigger_type": "hook", + "origin": "Jobology Candidate", + "origin_parameters": { + "title": "ReadProfilesParameters", + "type": "object", + "properties": { + "profile": { + "title": "Profile", + "description": "Event object recieved from the Webhook", + "field_type": "Other", + "type": "object" + } + }, + "additionalProperties": false + }, + "origin_data_schema": { + "title": "BaseModel", + "type": "object", + "properties": {} + }, + "supports_incremental": false, + "target": "HrFlow.ai Profile Parsing", + "target_parameters": { + "title": "WriteProfileParsingParameters", + "type": "object", + "properties": { + "api_secret": { + "title": "Api Secret", + "description": "X-API-KEY used to access HrFlow.ai API", + "field_type": "Auth", + "type": "string" + }, + "api_user": { + "title": "Api User", + "description": "X-USER-EMAIL used to access HrFlow.ai API", + "field_type": "Auth", + "type": "string" + }, + "source_key": { + "title": "Source Key", + "description": "HrFlow.ai source key", + "field_type": "Other", + "type": "string" + }, + "only_insert": { + "title": "Only Insert", + "description": "When enabled the profile is written only if it doesn't exist in the source", + "default": false, + "field_type": "Other", + "type": "boolean" + } + }, + "required": [ + "api_secret", + "api_user", + "source_key" + ], + "additionalProperties": false + }, + "target_data_schema": { + "title": "HrFlowProfileParsing", + "type": "object", + "properties": { + "reference": { + "title": "Reference", + "description": "Custom identifier of the Profile.", + "type": "string" + }, + "created_at": { + "title": "Created At", + "description": "type: datetime ISO8601, Creation date of the Profile.", + "type": "string" + }, + "resume": { + "$ref": "#/definitions/ResumeToParse" + }, + "tags": { + "title": "Tags", + "description": "List of tags of the Profile.", + "type": "array", + "items": { + "$ref": "#/definitions/GeneralEntitySchema" + } + }, + "metadatas": { + "title": "Metadatas", + "description": "List of metadatas of the Profile.", + "type": "array", + "items": { + "$ref": "#/definitions/GeneralEntitySchema" + } + } + }, + "required": [ + "reference", + "created_at", + "resume", + "tags", + "metadatas" + ], + "definitions": { + "ResumeToParse": { + "title": "ResumeToParse", + "type": "object", + "properties": { + "raw": { + "title": "Raw", + "type": "string", + "format": "binary" + }, + "content_type": { + "title": "Content Type", + "type": "string" + } + }, + "required": [ + "raw", + "content_type" + ] + }, + "GeneralEntitySchema": { + "title": "GeneralEntitySchema", + "type": "object", + "properties": { + "name": { + "title": "Name", + "description": "Identification name of the Object", + "type": "string" + }, + "value": { + "title": "Value", + "description": "Value associated to the Object's name", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } + }, + "workflow_code": "import typing as t\n\nfrom hrflow_connectors import Jobology\nfrom hrflow_connectors.core.connector import ActionInitError, Reason\n\nORIGIN_SETTINGS_PREFIX = \"origin_\"\nTARGET_SETTINGS_PREFIX = \"target_\"\n\n# << format_placeholder >>\n\n# << logics_placeholder >>\n\n# << event_parser_placeholder >>\n\n\ndef workflow(\n \n _request: t.Dict,\n \n settings: t.Dict\n ) -> None:\n actions_parameters = dict()\n try:\n format\n except NameError:\n pass\n else:\n actions_parameters[\"format\"] = format\n\n try:\n logics\n except NameError:\n pass\n else:\n actions_parameters[\"logics\"] = logics\n\n if \"__workflow_id\" not in settings:\n return Jobology.catch_profile(\n workflow_id=\"\",\n action_parameters=dict(),\n origin_parameters=dict(),\n target_parameters=dict(),\n init_error=ActionInitError(\n reason=Reason.workflow_id_not_found,\n data=dict(error=\"__workflow_id not found in settings\", settings_keys=list(settings.keys())),\n )\n )\n workflow_id = settings[\"__workflow_id\"]\n\n \n try:\n event_parser\n _event_parser = event_parser\n except NameError as e:\n action = Jobology.model.action_by_name(\"catch_profile\")\n # Without this trick event_parser is always only fetched from the local scope\n # meaning that try block always raises NameError even if the function is\n # defined in the placeholder\n _event_parser = action.parameters.__fields__[\"event_parser\"].default\n\n if _event_parser is not None:\n try:\n _request = _event_parser(_request)\n except Exception as e:\n return Jobology.catch_profile(\n workflow_id=workflow_id,\n action_parameters=dict(),\n origin_parameters=dict(),\n target_parameters=dict(),\n init_error=ActionInitError(\n reason=Reason.event_parsing_failure,\n data=dict(error=e, event=_request),\n )\n )\n \n\n origin_parameters = dict()\n for parameter in ['profile']:\n if \"{}{}\".format(ORIGIN_SETTINGS_PREFIX, parameter) in settings:\n origin_parameters[parameter] = settings[\"{}{}\".format(ORIGIN_SETTINGS_PREFIX, parameter)]\n \n if parameter in _request:\n origin_parameters[parameter] = _request[parameter]\n \n\n target_parameters = dict()\n for parameter in ['api_secret', 'api_user', 'source_key', 'only_insert']:\n if \"{}{}\".format(TARGET_SETTINGS_PREFIX, parameter) in settings:\n target_parameters[parameter] = settings[\"{}{}\".format(TARGET_SETTINGS_PREFIX, parameter)]\n \n if parameter in _request:\n target_parameters[parameter] = _request[parameter]\n \n\n return Jobology.catch_profile(\n workflow_id=workflow_id,\n action_parameters=actions_parameters,\n origin_parameters=origin_parameters,\n target_parameters=target_parameters,\n )", + "workflow_code_format_placeholder": "# << format_placeholder >>", + "workflow_code_logics_placeholder": "# << logics_placeholder >>", + "workflow_code_event_parser_placeholder": "# << event_parser_placeholder >>", + "workflow_code_workflow_id_settings_key": "__workflow_id", + "workflow_code_origin_settings_prefix": "origin_", + "workflow_code_target_settings_prefix": "target_" + } + ], + "type": "Job Board", + "logo": "https://raw.githubusercontent.com/Riminder/hrflow-connectors/master/src/hrflow_connectors/connectors/jobology/logo.jpeg" } ] } \ No newline at end of file diff --git a/src/hrflow_connectors/__init__.py b/src/hrflow_connectors/__init__.py index 57adaf247..55ad43fc7 100644 --- a/src/hrflow_connectors/__init__.py +++ b/src/hrflow_connectors/__init__.py @@ -5,6 +5,7 @@ from hrflow_connectors.connectors.digitalrecruiters import DigitalRecruiters from hrflow_connectors.connectors.greenhouse.connector import Greenhouse from hrflow_connectors.connectors.hubspot import Hubspot +from hrflow_connectors.connectors.jobology import Jobology from hrflow_connectors.connectors.lever import Lever from hrflow_connectors.connectors.poleemploi import PoleEmploi from hrflow_connectors.connectors.recruitee import Recruitee @@ -40,6 +41,7 @@ Lever, Salesforce, DigitalRecruiters, + Jobology, ] # This makes sure that connector are in module namespace diff --git a/src/hrflow_connectors/connectors/jobology/README.md b/src/hrflow_connectors/connectors/jobology/README.md new file mode 100644 index 000000000..9aff45bc0 --- /dev/null +++ b/src/hrflow_connectors/connectors/jobology/README.md @@ -0,0 +1,72 @@ +# πŸ“– Summary +- [πŸ“– Summary](#-summary) +- [πŸ’Ό About Jobology](#-about-jobology) + - [😍 Why is it a big deal for Jobology customers & partners?](#-why-is-it-a-big-deal-for-jobology-customers--partners) +- [πŸ”§ How does it work?](#-how-does-it-work) + - [πŸ“Š Data integration capabilities:](#-data-integration-capabilities) +- [πŸ”Œ Connector Actions](#-connector-actions) +- [πŸ’ Quick Start Examples](#-quick-start-examples) +- [πŸ”— Useful Links](#-useful-links) +- [πŸ‘ Special Thanks](#-special-thanks) + + +# πŸ’Ό About Jobology + +> La mission de jobology est de faciliter le processus de recrutement pour les entreprises + + +## 😍 Why is it a big deal for Jobology customers & partners? + +This new connector will enable: +- ⚑ A Fastlane Talent & Workforce data integration for Jobology customers & partners +- πŸ€– Cutting-edge AI-powered Talent Experiences & Recruiter Experiences for Jobology customers + +# πŸ”§ How does it work? +## πŸ“Š Data integration capabilities: +- ⬅️ Send Profiles data from Jobology to a Destination of your choice: + + + + + +# πŸ”Œ Connector Actions +

+ +| Action | Description | +| ------- | ----------- | +| [**Catch profile**](docs/catch_profile.md) | Imports candidates, in synchronization with jobology | + + +

+ +

+ +

+ + +# πŸ’ Quick Start Examples + +To make sure you can successfully run the latest versions of the example scripts, you have to **install the package from PyPi**. + + +To browse the examples of actions corresponding to released versions of πŸ€— this connector, you just need to import the module like this : + +

+ +

+ + +Once the connector module is imported, you can leverage all the different actions that it offers. + +For more code details checkout connector code. + + +# πŸ”— Useful Links + +- πŸ“„Visit [Jobology](https://www.jobology.com/) to learn more. +- πŸ’» [Connector code](https://github.com/Riminder/hrflow-connectors/tree/master/src/hrflow_connectors/connectors/jobology) on our Github. + + +# πŸ‘ Special Thanks +- πŸ’» HrFlow.ai : Abdellahi Mezid - Software Engineer +- 🀝 Jobology : Jobology Team \ No newline at end of file diff --git a/src/hrflow_connectors/connectors/jobology/__init__.py b/src/hrflow_connectors/connectors/jobology/__init__.py new file mode 100644 index 000000000..a077e33fd --- /dev/null +++ b/src/hrflow_connectors/connectors/jobology/__init__.py @@ -0,0 +1 @@ +from hrflow_connectors.connectors.jobology.connector import Jobology # noqa diff --git a/src/hrflow_connectors/connectors/jobology/connector.py b/src/hrflow_connectors/connectors/jobology/connector.py new file mode 100644 index 000000000..0f20c139a --- /dev/null +++ b/src/hrflow_connectors/connectors/jobology/connector.py @@ -0,0 +1,83 @@ +import typing as t + +from hrflow_connectors.connectors.hrflow.warehouse import HrFlowProfileParsingWarehouse +from hrflow_connectors.connectors.jobology.warehouse import JobologyProfilesWarehouse +from hrflow_connectors.core import ( + ActionName, + ActionType, + BaseActionParameters, + Connector, + ConnectorAction, + ConnectorType, + WorkflowType, +) + + +def rename_profile_fields(jobology_profile: t.Dict) -> t.Dict: + return { + "job-key": jobology_profile["jobkey"], + "first_name": jobology_profile.get("firstName"), + "last_name": jobology_profile.get("lastName"), + "phone": jobology_profile.get("phone"), + "email": jobology_profile.get("email"), + "coverText": jobology_profile.get("coverText"), + "profile-country": jobology_profile.get("profilecountry"), + "profile-regions": jobology_profile.get("profileregions"), + "profile-domains": jobology_profile.get("profiledomains"), + "job-lien_annonce_site_carriere": jobology_profile.get( + "joblien_annonce_site_carriere" + ), + "statistic-source": jobology_profile.get("statisticsource"), + "statistic-jbsource": jobology_profile.get("statisticjbsource"), + } + + +def add_tags(profile_tags: t.Dict) -> t.List[t.Dict]: + return [dict(name=key, value=value) for key, value in profile_tags.items() if value] + + +def format_jobology_profile(jobology_profile: t.List) -> t.Dict: + profile_tags = rename_profile_fields(jobology_profile) + tags = add_tags(profile_tags) + resume_dict = dict( + raw=jobology_profile["cv"], + content_type=jobology_profile["content_type"], + ) + return dict( + reference=jobology_profile["email"], + resume=resume_dict, + tags=tags, + metadatas=[], + created_at=None, + ) + + +def event_parser(event: t.Dict) -> t.Dict: + return dict(profile=event) + + +DESCRIPTION = ( + "La mission de jobology est de faciliter le processus de recrutement pour les" + " entreprises " +) +Jobology = Connector( + name="Jobology", + type=ConnectorType.JobBoard, + description=DESCRIPTION, + url="https://www.jobology.fr/", + actions=[ + ConnectorAction( + name=ActionName.catch_profile, + trigger_type=WorkflowType.catch, + description="Imports candidates, in synchronization with jobology", + parameters=BaseActionParameters.with_defaults( + "TriggerViewActionParameters", + format=format_jobology_profile, + event_parser=event_parser, + ), + origin=JobologyProfilesWarehouse, + target=HrFlowProfileParsingWarehouse, + action_type=ActionType.inbound, + ) + ], +) diff --git a/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md b/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md new file mode 100644 index 000000000..86a79e4ce --- /dev/null +++ b/src/hrflow_connectors/connectors/jobology/docs/catch_profile.md @@ -0,0 +1,61 @@ +# Catch profile +`Jobology Candidate` :arrow_right: `HrFlow.ai Profile Parsing` + +Imports candidates, in synchronization with jobology + + + +## Action Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `logics` | `typing.List[typing.Callable[[typing.Dict], typing.Optional[typing.Dict]]]` | [] | List of logic functions | +| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_jobology_profile`](../connector.py#L39) | Formatting function | +| `read_mode` | `str` | ReadMode.sync | If 'incremental' then `read_from` of the last run is given to Origin Warehouse during read. **The actual behavior depends on implementation of read**. In 'sync' mode `read_from` is neither fetched nor given to Origin Warehouse during read. | + +## Source Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `profile` | `typing.Dict` | None | Event object recieved from the Webhook | + +## Destination Parameters + +| Field | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `api_secret` :red_circle: | `str` | None | X-API-KEY used to access HrFlow.ai API | +| `api_user` :red_circle: | `str` | None | X-USER-EMAIL used to access HrFlow.ai API | +| `source_key` :red_circle: | `str` | None | HrFlow.ai source key | +| `only_insert` | `bool` | False | When enabled the profile is written only if it doesn't exist in the source | + +:red_circle: : *required* + +## Example + +```python +import logging +from hrflow_connectors import Jobology +from hrflow_connectors.core import ReadMode + + +logging.basicConfig(level=logging.INFO) + + +Jobology.catch_profile( + workflow_id="some_string_identifier", + action_parameters=dict( + logics=[], + format=lambda *args, **kwargs: None # Put your code logic here, + read_mode=ReadMode.sync, + ), + origin_parameters=dict( + profile=***, + ), + target_parameters=dict( + api_secret="your_api_secret", + api_user="your_api_user", + source_key="your_source_key", + only_insert=False, + ) +) +``` \ No newline at end of file diff --git a/src/hrflow_connectors/connectors/jobology/logo.jpeg b/src/hrflow_connectors/connectors/jobology/logo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..bdafbe3cf99cd2da5a1303853c6a2e4d287de6bb GIT binary patch literal 4813 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 t.Iterable[t.Dict]: + result = {**parameters.profile} + cv_url = result["cvUrl"] + response = requests.get(cv_url) + if response.status_code == 200: + result["cv"] = response.content + result["content_type"] = response.headers["Content-Type"] + elif response.status_code == 400: + raise Exception(f"Bad Request {response.text}") + else: + raise Exception( + f"request failed with status code {response.status_code} and message" + f" {response.text}" + ) + + return [result] + + +JobologyProfilesWarehouse = Warehouse( + name="Jobology Candidate", + data_type=DataType.profile, + read=WarehouseReadAction( + parameters=ReadProfilesParameters, + function=read, + ), +)