diff --git a/components/talenthr/actions/create-employee/create-employee.mjs b/components/talenthr/actions/create-employee/create-employee.mjs new file mode 100644 index 0000000000000..f4db509832bf0 --- /dev/null +++ b/components/talenthr/actions/create-employee/create-employee.mjs @@ -0,0 +1,182 @@ +import { + OVERTIME_STATUS, + PAY_RATE_PERIOD_OPTIONS, + PAY_RATE_SCHEDULE_OPTIONS, +} from "../../common/constants.mjs"; +import talenthr from "../../talenthr.app.mjs"; + +export default { + key: "talenthr-create-employee", + name: "Create Employee", + description: "Hires a new employee and registers them in the system. [See the documentation](https://apidocs.talenthr.io/#2950f0ba-b27b-4d4b-855f-4b79b667767c)", + version: "0.0.1", + type: "action", + props: { + talenthr, + firstName: { + propDefinition: [ + talenthr, + "firstName", + ], + }, + lastName: { + propDefinition: [ + talenthr, + "lastName", + ], + }, + email: { + propDefinition: [ + talenthr, + "email", + ], + }, + hireDate: { + propDefinition: [ + talenthr, + "hireDate", + ], + }, + employmentStatusId: { + propDefinition: [ + talenthr, + "employmentStatusId", + ], + }, + reportsToEmployeeId: { + propDefinition: [ + talenthr, + "employeeId", + ], + optional: true, + }, + jobTitleId: { + propDefinition: [ + talenthr, + "jobTitleId", + ], + optional: true, + }, + jobLocationId: { + propDefinition: [ + talenthr, + "jobLocationId", + ], + optional: true, + }, + divisionId: { + propDefinition: [ + talenthr, + "divisionId", + ], + optional: true, + }, + departmentId: { + propDefinition: [ + talenthr, + "departmentId", + ], + optional: true, + }, + payRate: { + type: "string", + label: "Pay Rate", + description: "Employee's wage and must have 2 decimals. E.g 1255.38", + }, + payRatePeriod: { + type: "string", + label: "Pay Rate Period", + description: "The period over which money is earned.", + options: PAY_RATE_PERIOD_OPTIONS, + }, + payRateSchedule: { + type: "string", + label: "Pay Rate Schedule", + description: "Frequency of the wage.", + options: PAY_RATE_SCHEDULE_OPTIONS, + optional: true, + }, + overtimeStatus: { + type: "string", + label: "Overtime Status", + description: "Determining whether an employee is exempt or non-exempt from overtime regulations.", + options: OVERTIME_STATUS, + optional: true, + }, + preventEmail: { + type: "boolean", + label: "Prevent Email", + description: "Opt for 'true', if you don't want to send an invitation email to the hiring employee, else 'false'.", + optional: true, + }, + isExisting: { + type: "boolean", + label: "Is Existing", + description: "Opt for 'false' if the employee is a new hire and you want to run the Automatic Onboarding process, else 'true'.", + optional: true, + }, + whoId: { + type: "integer", + label: "Who Id", + description: "The employee who will meet the newly hired employee. Required if **When Time** and address is present.", + optional: true, + }, + address: { + propDefinition: [ + talenthr, + "address", + ], + optional: true, + }, + whenTime: { + type: "string", + label: "When Time", + description: "The date time that the meeting will take place. Required if **Who Id** and address is present. The hire date must be formatted as 'YYYY-MM-DD HH:II'.", + optional: true, + }, + instructions: { + type: "string", + label: "Instructions", + description: "Important Instructions for the newly hired employee.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.talenthr.createEmployee({ + $, + data: { + first_name: this.firstName, + last_name: this.lastName, + email: this.email, + hire_date: this.hireDate, + employment_status: { + employment_status_id: this.employmentStatusId, + }, + reports_to_employee_id: this.reportsToEmployeeId, + job_record: { + job_title_id: this.jobTitleId, + location_id: this.jobLocationId, + division_id: this.divisionId, + department_id: this.departmentId, + }, + compensation_record: { + pay_rate: parseFloat(this.payRate), + pay_rate_period: this.payRatePeriod, + pay_rate_schedule: this.payRateSchedule, + overtime_status: this.overtimeStatus, + }, + prevent_email: this.preventEmail, + is_existing: this.isExisting, + hire_packet: { + who_id: this.whoId, + address: this.address, + when_time: this.whenTime, + instructions: this.instrwuctions, + }, + }, + }); + + $.export("$summary", `Successfully created employee: ${response.data.id}`); + return response; + }, +}; diff --git a/components/talenthr/actions/respond-time-off-request/respond-time-off-request.mjs b/components/talenthr/actions/respond-time-off-request/respond-time-off-request.mjs new file mode 100644 index 0000000000000..92d8b665fa33d --- /dev/null +++ b/components/talenthr/actions/respond-time-off-request/respond-time-off-request.mjs @@ -0,0 +1,44 @@ +import talenthr from "../../talenthr.app.mjs"; + +export default { + key: "talenthr-respond-time-off-request", + name: "Respond to Time Off Request", + description: "Responds to an employee's time off request. This action requires the request ID and the response status as props. [See the documentation](https://apidocs.talenthr.io/)", + version: "0.0.1", + type: "action", + props: { + talenthr, + employeeId: { + propDefinition: [ + talenthr, + "employeeId", + ], + }, + timeOffRequestId: { + propDefinition: [ + talenthr, + "timeOffRequestId", + ({ employeeId }) => ({ + employeeId, + }), + ], + }, + accept: { + type: "boolean", + label: "Accept", + description: "Approve 'true' or Reject 'false' the specified time off request. If the time off request has been answered or it has been cancelled then you cannot appove or reject it.", + }, + }, + async run({ $ }) { + const response = await this.talenthr.respondToTimeOffRequest({ + timeOffRequestId: this.timeOffRequestId, + employeeId: this.employeeId, + data: { + accept: this.accept, + }, + }); + + $.export("$summary", `Successfully responded to time off request with ID ${this.timeOffRequestId}`); + return response; + }, +}; diff --git a/components/talenthr/actions/update-employee/update-employee.mjs b/components/talenthr/actions/update-employee/update-employee.mjs new file mode 100644 index 0000000000000..d545c83e07dcd --- /dev/null +++ b/components/talenthr/actions/update-employee/update-employee.mjs @@ -0,0 +1,410 @@ +import { + CITIZENSHIP_OPTIONS, + GENDER_OPTIONS, + LANGUAGE_OPTIONS, + MARITAL_STATUS_OPTIONS, +} from "../../common/constants.mjs"; +import { + clearObj, + parseObject, +} from "../../common/utils.mjs"; +import talenthr from "../../talenthr.app.mjs"; + +export default { + key: "talenthr-update-employee", + name: "Update Employee", + description: "Allows updating an existing employee's data in the system. [See the documentation](https://apidocs.talenthr.io/)", + version: "0.0.1", + type: "action", + props: { + talenthr, + employeeId: { + propDefinition: [ + talenthr, + "employeeId", + ], + }, + firstName: { + propDefinition: [ + talenthr, + "firstName", + ], + }, + lastName: { + propDefinition: [ + talenthr, + "lastName", + ], + }, + email: { + propDefinition: [ + talenthr, + "email", + ], + }, + reportsToEmployeeId: { + propDefinition: [ + talenthr, + "employeeId", + ], + label: "Reports To Employee Id", + description: "If the **Reports To Employee Id** is 'null' then the current employee will be the head of the company.", + optional: true, + }, + + ssn: { + type: "string", + label: "SSN", + description: "The social security number of the employee", + optional: true, + }, + birthDate: { + type: "string", + label: "Birth Date", + description: "The birth date must be formatted as 'YYYY-MM-DD' and must be between now and 1930-01-01.", + optional: true, + }, + personalEmail: { + type: "string", + label: "Personal Email", + description: "The email has to be unique (among personal emails).", + optional: true, + }, + maritalStatus: { + type: "string", + label: "Marital Status", + description: "The marital status of the employee", + options: MARITAL_STATUS_OPTIONS, + optional: true, + }, + gender: { + type: "string", + label: "Gender", + description: "The gender of the employee", + options: GENDER_OPTIONS, + optional: true, + }, + nationality: { + propDefinition: [ + talenthr, + "nationality", + ], + optional: true, + }, + citizenship: { + type: "string", + label: "Citizenship", + description: "The citizenship of the employee", + options: CITIZENSHIP_OPTIONS, + optional: true, + }, + workPhone: { + type: "string", + label: "Work Phone", + description: "The phone number of the employee's work", + optional: true, + }, + mobilePhone: { + type: "string", + label: "Mobile Phone", + description: "The mobile phone numbe of the employee", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "The phone numbe of the employee", + optional: true, + }, + address: { + propDefinition: [ + talenthr, + "address", + ], + optional: true, + }, + country: { + propDefinition: [ + talenthr, + "country", + ], + optional: true, + }, + postalCode: { + type: "string", + label: "Postal Code", + description: "The postal code where the employee lives", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "The city where the employee lives", + optional: true, + }, + emergencyContactFullName: { + type: "string", + label: "Emergency Contact Name", + description: "The emergency contact's full name", + optional: true, + }, + emergencyContactRelationshipTypeId: { + propDefinition: [ + talenthr, + "emergencyContactRelationshipTypeId", + ], + optional: true, + }, + emergencyContactPhone: { + type: "string", + label: "Emergency Contact Phone", + description: "The emergency contact's phone number", + optional: true, + }, + emergencyContactAddress: { + type: "string", + label: "Emergency Contact Address", + description: "The emergency contact's address", + optional: true, + }, + linkedInUrl: { + type: "string", + label: "LinkedIn", + description: "The URL of the linkedIn", + optional: true, + }, + employeeNumber: { + type: "string", + label: "Employee Number", + description: "An external number of the employee", + optional: true, + }, + passportNumber: { + type: "string", + label: "Passport Number", + description: "The number of the employee's passport", + optional: true, + }, + passportIssuedDate: { + type: "string", + label: "Passport Issued Date", + description: "The issued date of the employee's passport. **Format YYYY-MM-DD**", + optional: true, + }, + passportExpiryDate: { + type: "string", + label: "Passport Expiry Date", + description: "The expiry date of the employee's passport. **Format YYYY-MM-DD**", + optional: true, + }, + passportIssuingCountry: { + type: "string", + label: "Passport Issue Country", + description: "The issuing country of the employee's passport", + optional: true, + }, + visaType: { + type: "string", + label: "Visa Type", + description: "The type of the employee's visa", + optional: true, + }, + visaNumber: { + type: "string", + label: "Visa Number", + description: "The number of the employee's visa", + optional: true, + }, + visaExpiryDate: { + type: "string", + label: "Visa Expiry Date", + description: "The expiry date of the employee's visa. **Format YYYY-MM-DD**", + optional: true, + }, + driverLicenseNumber: { + type: "string", + label: "Driver License Number", + description: "The number of the employee's driver license", + optional: true, + }, + driverLicenseIssuedDate: { + type: "string", + label: "Driver License Issued Date", + description: "The issued date of the employee's driver license. **Format YYYY-MM-DD**", + optional: true, + }, + driverLicenseExpiryDate: { + type: "string", + label: "Driver License Expiry Date", + description: "The expiry date of the employee's driver license. **Format YYYY-MM-DD**", + optional: true, + }, + driverLicenseIssuingCountry: { + type: "string", + label: "Driver License Issue Country", + description: "The issuing country of the employee's driver license", + optional: true, + }, + secAddress: { + type: "string", + label: "Second Address", + description: "An employee's aditional address", + optional: true, + }, + secCity: { + type: "string", + label: "Second City", + description: "An employee's aditional city", + optional: true, + }, + secPostalCode: { + type: "string", + label: "Second Postal Code", + description: "An employee's aditional postal code", + optional: true, + }, + secCountry: { + type: "string", + label: "Second Country", + description: "An employee's aditional country", + optional: true, + }, + twitterUrl: { + type: "string", + label: "Twitter", + description: "The employee's twitter URL", + optional: true, + }, + facebookUrl: { + type: "string", + label: "Facebook", + description: "The employee's facebook URL", + optional: true, + }, + instagramUrl: { + type: "string", + label: "Instagram", + description: "The employee's instagram URL", + optional: true, + }, + pinterestUrl: { + type: "string", + label: "Pinterest", + description: "The employee's pinterest URL", + optional: true, + }, + githubUrl: { + type: "string", + label: "Github", + description: "The employee's github URL", + optional: true, + }, + behanceUrl: { + type: "string", + label: "Behance", + description: "The employee's behance URL", + optional: true, + }, + skypeName: { + type: "string", + label: "Skype Name", + description: "The employee's skype name", + optional: true, + }, + shirtSize: { + type: "string", + label: "Shirt Size", + description: "The size of the shirt the employee wears", + optional: true, + }, + tShirtSize: { + type: "string", + label: "T-Shirt Size", + description: "The size of the t-shirt the employee wears", + optional: true, + }, + hrLanguages: { + type: "integer[]", + label: "HR Languages", + description: "A list of language ids", + options: LANGUAGE_OPTIONS, + optional: true, + }, + hrFamily: { + type: "string[]", + label: "HR Family", + description: "An array of family objects. Example: `{\"name\": \"Jhon Doe\", \"gender\": \"Male\", \"birth_date\": \"2001-10-10\", \"family_member_relationship_id\": \"1\" }`", + optional: true, + }, + allergies: { + type: "string", + label: "Allergies", + description: "The employee's allegies", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.talenthr.updateEmployee({ + $, + employeeId: this.employeeId, + data: clearObj({ + first_name: this.firstName, + last_name: this.lastName, + email: this.email, + ssn: this.ssn, + birth_date: this.birthDate, + personal_email: this.personalEmail, + marital_status: this.maritalStatus, + gender: this.gender, + nationality: this.nationality, + citizenship: this.citizenship, + work_phone: this.workPhone, + mobile_phone: this.mobilePhone, + phone: this.phone, + address: this.address, + country: this.country, + postal_code: this.postalCode, + city: this.city, + reports_to_employee_id: this.reportsToEmployeeId, + emergency_contact: { + full_name: this.emergencyContactFullName, + relationship_type_id: this.emergencyContactRelationshipTypeId, + phone: this.emergencyContactPhone, + address: this.emergencyContactAddress, + }, + linked_in_url: this.linkedInUrl, + employee_number: this.employeeNumber, + passport_number: this.passportNumber, + passport_issued_date: this.passportIssuedDate, + passport_expiry_date: this.passportExpiryDate, + passport_issuing_country: this.passportIssuingCountry, + visa_type: this.visaType, + visa_number: this.visaNumber, + visa_expiry_date: this.visaExpiryDate, + driver_license_number: this.driverLicenseNumber, + driver_license_issued_date: this.driverLicenseIssuedDate, + driver_license_expiry_date: this.driverLicenseExpiryDate, + driver_license_issuing_country: this.driverLicenseIssuingCountry, + sec_address: this.secAddress, + sec_city: this.secCity, + sec_postal_code: this.secPostalCode, + sec_country: this.secCountry, + twitter_url: this.twitterUrl, + facebook_url: this.facebookUrl, + instagram_url: this.instagramUrl, + pinterest_url: this.pinterestUrl, + github_url: this.githubUrl, + behance_url: this.behanceUrl, + skype_name: this.skypeName, + shirt_size: this.shirtSize, + t_shirt_size: this.tShirtSize, + hr_languages: parseObject(this.hrLanguages), + hr_family: parseObject(this.hrFamily), + allergies: this.allergies, + }), + }); + + $.export("$summary", `Successfully updated employee ID ${this.employeeId}`); + return response; + }, +}; diff --git a/components/talenthr/common/constants.mjs b/components/talenthr/common/constants.mjs new file mode 100644 index 0000000000000..e58739af2d695 --- /dev/null +++ b/components/talenthr/common/constants.mjs @@ -0,0 +1,674 @@ +export const LIMIT = 100; + +export const PAY_RATE_PERIOD_OPTIONS = [ + "hour", + "day", + "week", + "month", + "quarter", + "year", +]; + +export const PAY_RATE_SCHEDULE_OPTIONS = [ + "once-per-month", + "twice-per-month", + "every-other-week", +]; + +export const OVERTIME_STATUS = [ + "exempt", + "non-exempt", +]; + +export const MARITAL_STATUS_OPTIONS = [ + "Single", + "Separated", + "Married", + "Widowed", +]; + +export const GENDER_OPTIONS = [ + "Male", + "Female", + "Non-binary", +]; + +export const CITIZENSHIP_OPTIONS = [ + "Afghan", + "Albanian", + "Algerian", + "American", + "Andorran", + "Angolan", + "Antiguans", + "Argentinean", + "Armenian", + "Australian", + "Austrian", + "Azerbaijani", + "Bahamian", + "Bahraini", + "Bangladeshi", + "Barbadian", + "Barbudans", + "Batswana", + "Belarusian", + "Belgian", + "Belizean", + "Beninese", + "Bhutanese", + "Bolivian", + "Bosnian", + "Brazilian", + "British", + "Bruneian", + "Bulgarian", + "Burkinabe", + "Burmese", + "Burundian", + "Cambodian", + "Cameroonian", + "Canadian", + "Cape Verdean", + "Central African", + "Chadian", + "Chilean", + "Chinese", + "Colombian", + "Comoran", + "Congolese", + "Costa Rican", + "Croatian", + "Cuban", + "Cypriot", + "Czech", + "Danish", + "Djibouti", + "Dominican", + "Dutch", + "East Timorese", + "Ecuadorean", + "Egyptian", + "Emirian", + "Equatorial Guinean", + "Eritrean", + "Estonian", + "Ethiopian", + "Fijian", + "Filipino", + "Finnish", + "French", + "Gabonese", + "Gambian", + "Georgian", + "German", + "Ghanaian", + "Greek", + "Grenadian", + "Guatemalan", + "Guinea-Bissauan", + "Guinean", + "Guyanese", + "Haitian", + "Herzegovinian", + "Honduran", + "Hungarian", + "I-Kiribati", + "Icelander", + "Indian", + "Indonesian", + "Iranian", + "Iraqi", + "Irish", + "Israeli", + "Italian", + "Ivorian", + "Jamaican", + "Japanese", + "Jordanian", + "Kazakhstani", + "Kenyan", + "Kittian and Nevisian", + "Kuwaiti", + "Kyrgyz", + "Laotian", + "Latvian", + "Lebanese", + "Liberian", + "Libyan", + "Liechtensteiner", + "Lithuanian", + "Luxembourger", + "Malagasy", + "Malawian", + "Malaysian", + "Maldivan", + "Malian", + "Maltese", + "Marshallese", + "Mauritanian", + "Mauritian", + "Mexican", + "Micronesian", + "Moldovan", + "Monacan", + "Mongolian", + "Moroccan", + "Mosotho", + "Motswana", + "Mozambican", + "Namibian", + "Nauruan", + "Nepalese", + "New Zealander", + "Nicaraguan", + "Nigerian", + "Nigerien", + "North Korean", + "North Macedonian", + "Northern Irish", + "Norwegian", + "Omani", + "Pakistani", + "Palauan", + "Palestinian", + "Panamanian", + "Papua New Guinean", + "Paraguayan", + "Peruvian", + "Polish", + "Portuguese", + "Qatari", + "Romanian", + "Russian", + "Rwandan", + "Saint Lucian", + "Salvadoran", + "Samoan", + "San Marinese", + "Sao Tomean", + "Saudi", + "Scottish", + "Senegalese", + "Serbian", + "Seychellois", + "Sierra Leonean", + "Singaporean", + "Slovakian", + "Slovenian", + "Solomon Islander", + "Somali", + "South African", + "South Korean", + "Spanish", + "Sri Lankan", + "Sudanese", + "Surinamer", + "Swazi", + "Swedish", + "Swiss", + "Syrian", + "Taiwanese", + "Tajik", + "Tanzanian", + "Thai", + "Togolese", + "Tongan", + "Trinidadian/Tobagonian", + "Tunisian", + "Turkish", + "Tuvaluan", + "Ugandan", + "Ukrainian", + "Uruguayan", + "Uzbekistani", + "Venezuelan", + "Vietnamese", + "Welsh", + "Yemenite", + "Zambian", + "Zimbabwean", +]; + +export const LANGUAGE_OPTIONS = [ + { + label: "Akan", + value: 123, + }, + { + label: "Amharic", + value: 50, + }, + { + label: "Arabic", + value: 22, + }, + { + label: "Arabic, Algerian", + value: 60, + }, + { + label: "Arabic, Egyptian", + value: 42, + }, + { + label: "Arabic, Moroccan", + value: 77, + }, + { + label: "Arabic, Najdi", + value: 101, + }, + { + label: "Arabic, Saidi", + value: 78, + }, + { + label: "Arabic, Sanaani", + value: 116, + }, + { + label: "Arabic, Sudanese", + value: 58, + }, + { + label: "Arabic, Tunisian", + value: 105, + }, + { + label: "Assamese", + value: 86, + }, + { + label: "Awadhi", + value: 74, + }, + { + label: "Azerbaijani, North", + value: 119, + }, + { + label: "Azerbaijani, South", + value: 68, + }, + { + label: "Belarusan", + value: 99, + }, + { + label: "Bengali", + value: 23, + }, + { + label: "Bhojpuri", + value: 51, + }, + { + label: "Bulgarian", + value: 108, + }, + { + label: "Burmese", + value: 57, + }, + { + label: "Cebuano", + value: 84, + }, + { + label: "Chhattisgarhi", + value: 96, + }, + { + label: "Chinese, Gan", + value: 73, + }, + { + label: "Chinese, Hakka", + value: 56, + }, + { + label: "Chinese, Jin", + value: 54, + }, + { + label: "Chinese, Mandarin", + value: 19, + }, + { + label: "Chinese, Min Bei", + value: 98, + }, + { + label: "Chinese, Min Nan", + value: 53, + }, + { + label: "Chinese, Wu", + value: 38, + }, + { + label: "Chinese, Xiang", + value: 63, + }, + { + label: "Chinese, Yue", + value: 35, + }, + { + label: "Chittagonian", + value: 88, + }, + { + label: "Croatian", + value: 72, + }, + { + label: "Czech", + value: 93, + }, + { + label: "Deccan", + value: 97, + }, + { + label: "Dutch", + value: 75, + }, + { + label: "English", + value: 17, + }, + { + label: "Farsi", + value: 69, + }, + { + label: "Farsi, Eastern", + value: 122, + }, + { + label: "French", + value: 21, + }, + { + label: "German", + value: 28, + }, + { + label: "Greek", + value: 94, + }, + { + label: "Gujarati", + value: 48, + }, + { + label: "Haitian Creole French", + value: 118, + }, + { + label: "Haryanvi", + value: 91, + }, + { + label: "Hausa", + value: 41, + }, + { + label: "Hiligaynon", + value: 124, + }, + { + label: "Hindi", + value: 20, + }, + { + label: "Hungarian", + value: 87, + }, + { + label: "Igbo", + value: 81, + }, + { + label: "Ilocano", + value: 114, + }, + { + label: "Indonesian", + value: 27, + }, + { + label: "Iranian Persian", + value: 40, + }, + { + label: "Italian", + value: 45, + }, + { + label: "Japanese", + value: 29, + }, + { + label: "Javanese", + value: 44, + }, + { + label: "Kannada", + value: 47, + }, + { + label: "Kazakh", + value: 113, + }, + { + label: "Khmer, Central", + value: 121, + }, + { + label: "Korean", + value: 39, + }, + { + label: "Kurmanji", + value: 125, + }, + { + label: "Lingala", + value: 61, + }, + { + label: "Lombard", + value: 110, + }, + { + label: "Madura", + value: 89, + }, + { + label: "Magahi", + value: 95, + }, + { + label: "Maithili", + value: 70, + }, + { + label: "Malagasy", + value: 104, + }, + { + label: "Malay", + value: 80, + }, + { + label: "Malayalam", + value: 64, + }, + { + label: "Marathi", + value: 31, + }, + { + label: "Marwari", + value: 92, + }, + { + label: "Napoletano-Calabrese", + value: 120, + }, + { + label: "Nepali", + value: 82, + }, + { + label: "Nigerian", + value: 30, + }, + { + label: "Oriya", + value: 65, + }, + { + label: "Oromo, West-Central", + value: 111, + }, + { + label: "Pashto, Northern", + value: 102, + }, + { + label: "Pashto, Southern", + value: 112, + }, + { + label: "Polish", + value: 59, + }, + { + label: "Portuguese", + value: 25, + }, + { + label: "Punjabi, Eastern", + value: 52, + }, + { + label: "Punjabi, Western", + value: 46, + }, + { + label: "Romanian", + value: 67, + }, + { + label: "Russian", + value: 24, + }, + { + label: "Rwanda", + value: 106, + }, + { + label: "Saraiki", + value: 83, + }, + { + label: "Serbian", + value: 71, + }, + { + label: "Shonda", + value: 126, + }, + { + label: "Sindhi", + value: 76, + }, + { + label: "Sinhala", + value: 90, + }, + { + label: "Somali", + value: 103, + }, + { + label: "Spanish", + value: 18, + }, + { + label: "Sunda", + value: 66, + }, + { + label: "Swahili", + value: 43, + }, + { + label: "Swedish", + value: 109, + }, + { + label: "Tagalog", + value: 37, + }, + { + label: "Tamil", + value: 34, + }, + { + label: "Tatar", + value: 115, + }, + { + label: "Telugu", + value: 32, + }, + { + label: "Thai", + value: 49, + }, + { + label: "Thai, Northeastern", + value: 85, + }, + { + label: "Turkish", + value: 33, + }, + { + label: "Ukrainian", + value: 62, + }, + { + label: "Urdu", + value: 26, + }, + { + label: "Uyghur", + value: 117, + }, + { + label: "Uzbek, Northern", + value: 79, + }, + { + label: "Vietnamese", + value: 36, + }, + { + label: "Yoruba", + value: 55, + }, + { + label: "Zhuang, Northern", + value: 100, + }, + { + label: "Zulu", + value: 107, + }, +]; diff --git a/components/talenthr/common/utils.mjs b/components/talenthr/common/utils.mjs new file mode 100644 index 0000000000000..9ad67c1c54e64 --- /dev/null +++ b/components/talenthr/common/utils.mjs @@ -0,0 +1,35 @@ +export const parseObject = (obj) => { + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + return JSON.parse(obj); + } + return obj; +}; + +export const clearObj = (obj) => { + return Object.entries(obj) + .filter(([ + , + v, + ]) => (v != null && v != "" && JSON.stringify(v) != "{}")) + .reduce((acc, [ + k, + v, + ]) => ({ + ...acc, + [k]: (!Array.isArray(v) && v === Object(v)) + ? clearObj(v) + : v, + }), {}); +}; diff --git a/components/talenthr/package.json b/components/talenthr/package.json index 2ac853f867aef..d4f35e87d85c9 100644 --- a/components/talenthr/package.json +++ b/components/talenthr/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/talenthr", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream TalentHR Components", "main": "talenthr.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.1" } -} \ No newline at end of file +} diff --git a/components/talenthr/sources/common/base.mjs b/components/talenthr/sources/common/base.mjs new file mode 100644 index 0000000000000..d347a2eaa065e --- /dev/null +++ b/components/talenthr/sources/common/base.mjs @@ -0,0 +1,69 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import talenthr from "../../talenthr.app.mjs"; + +export default { + props: { + talenthr, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + getConfig() { + return {}; + }, + sortData(data) { + return data; + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + + const response = this.talenthr.paginate({ + fn: this.getFunction(), + maxResults, + ...this.getConfig(), + }); + + let responseArray = []; + for await (const item of response) { + responseArray.push(item); + } + + responseArray = this.sortData(responseArray); + responseArray = responseArray.filter((item) => item.id > lastId); + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(new Date()), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/talenthr/sources/new-ats-application/new-ats-application.mjs b/components/talenthr/sources/new-ats-application/new-ats-application.mjs new file mode 100644 index 0000000000000..2745d20bbb61e --- /dev/null +++ b/components/talenthr/sources/new-ats-application/new-ats-application.mjs @@ -0,0 +1,41 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "talenthr-new-ats-application", + name: "New Job Application Submitted", + description: "Emit new event when a new job application is submitted. [See the documentation](https://apidocs.talenthr.io/)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + jobPositionId: { + propDefinition: [ + common.props.talenthr, + "jobPositionId", + ], + optional: true, + }, + }, + methods: { + ...common.methods, + getConfig() { + return { + jobPositionId: this.jobPositionId, + params: { + order: "desc", + sort: "created_at", + }, + }; + }, + getFunction() { + return this.talenthr.listNewJobApplications; + }, + getSummary(item) { + return `New Job Application: ${item.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/talenthr/sources/new-ats-application/test-event.mjs b/components/talenthr/sources/new-ats-application/test-event.mjs new file mode 100644 index 0000000000000..77d4a8001ddf3 --- /dev/null +++ b/components/talenthr/sources/new-ats-application/test-event.mjs @@ -0,0 +1,14 @@ +export default { + "id": 2, + "first_name": "John", + "last_name": "Doe", + "email": "jdoe@example.com", + "phone": "0030 6985*******", + "address": "address-name", + "created_at": "2023-12-11 11:16:50", + "updated_at": "2023-12-11 11:16:51", + "deleted_at": null, + "starred": false, + "applications_count": 1, + "added_at": "2023-12-11" +} \ No newline at end of file diff --git a/components/talenthr/sources/new-employee/new-employee.mjs b/components/talenthr/sources/new-employee/new-employee.mjs new file mode 100644 index 0000000000000..332f0a864a9f0 --- /dev/null +++ b/components/talenthr/sources/new-employee/new-employee.mjs @@ -0,0 +1,27 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "talenthr-new-employee", + name: "New Employee Created", + description: "Emit new event whenever a new employee is created. [See the documentation](https://apidocs.talenthr.io/)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.talenthr.listEmployees; + }, + getSummary({ + first_name: fName, last_name: lName, email, + }) { + return `New Employee: ${fName} ${lName} (${email})`; + }, + sortData(data) { + return data.sort((a, b) => b.id - a.id); + }, + }, + sampleEmit, +}; diff --git a/components/talenthr/sources/new-employee/test-event.mjs b/components/talenthr/sources/new-employee/test-event.mjs new file mode 100644 index 0000000000000..11ba1f439bb87 --- /dev/null +++ b/components/talenthr/sources/new-employee/test-event.mjs @@ -0,0 +1,29 @@ +export default { + "id": 19, + "user_id": 19, + "reports_to_employee_id": 1, + "photo_url": "https://d2spisfw2i7npf.cloudfront.net/public-assets/people/default-avatar.png...", + "u_id": 19, + "first_name": "John", + "last_name": "Doe", + "email": "jdoe@example.com", + "division_id": 13, + "division": "Europe", + "employment_status_id": 1, + "employment_status_name": "Full-Time", + "department_id": 6, + "department": "Administration & Operations", + "job_title_id": 26, + "job_title": "Business Analyst", + "location_id": 36, + "location": "Athens", + "termination_date": null, + "hire_date": "2023-11-28", + "linked_in_url": null, + "work_phone": null, + "user_role": { + "id": 3, + "slug": "employee", + "name": "Employee" + } +} \ No newline at end of file diff --git a/components/talenthr/talenthr.app.mjs b/components/talenthr/talenthr.app.mjs index 9844f307ba24b..a9f961530cba9 100644 --- a/components/talenthr/talenthr.app.mjs +++ b/components/talenthr/talenthr.app.mjs @@ -1,11 +1,375 @@ +import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; + export default { type: "app", app: "talenthr", - propDefinitions: {}, + propDefinitions: { + firstName: { + type: "string", + label: "First Name", + description: "The first name of the new employee", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the new employee", + }, + email: { + type: "string", + label: "Email", + description: "The email address of the new employee", + }, + hireDate: { + type: "string", + label: "Hire Date", + description: "The date of the new employee's hire. **Format YYYY-MM-DD**", + }, + employmentStatusId: { + type: "string", + label: "Employment Status Id", + description: "The employment status Id", + async options() { + const { data } = await this.listEmploymentStatuses(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + employeeId: { + type: "string", + label: "Employee ID", + description: "The ID of the employee", + async options({ page }) { + const { data: { rows } } = await this.listEmployees({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return rows.map(({ + id: value, first_name: fName, last_name: lName, email, + }) => ({ + label: `${fName} ${lName} (${email})`, + value, + })); + }, + }, + timeOffRequestId: { + type: "string", + label: "Time Off Request ID", + description: "The ID of the employee's time off request", + async options({ + page, employeeId, + }) { + const { data: { rows } } = await this.listTimeOffRequests({ + employeeId, + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return rows.map(({ + id: value, timeoff_type_name: tName, start_date: sDate, end_date: eDate, + }) => ({ + label: `(${tName}) start: ${sDate} / end: ${eDate}`, + value, + })); + }, + }, + jobTitleId: { + type: "string", + label: "Job Title Id", + description: "The Id of the job title.", + async options() { + const { data } = await this.listJobTitles(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + jobLocationId: { + type: "string", + label: "Job Location Id", + description: "The Id of the job location.", + async options() { + const { data } = await this.listJobLocations(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + divisionId: { + type: "string", + label: "Division Id", + description: "The division for which the application has been submitted", + async options() { + const { data } = await this.listDivisions(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + departmentId: { + type: "string", + label: "Department Id", + description: "The department for which the application has been submitted", + async options() { + const { data } = await this.listDepartments(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + address: { + type: "string", + label: "Address", + description: "The address where the meeting will take place. Required if **Who Id** and **When Time** is present.", + }, + nationality: { + type: "string", + label: "Nationality", + description: "The nationality of the employee", + async options() { + const { data } = await this.listNationalities(); + return data; + }, + }, + country: { + type: "string", + label: "Country", + description: "The country where the employee lives", + async options() { + const { data } = await this.listCountries(); + return data; + }, + }, + emergencyContactRelationshipTypeId: { + type: "string", + label: "Emergency Contact Relationship Type", + description: "The type of the emergency contact's relationship", + async options() { + const { data } = await this.listRelationshipTypes(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + jobPositionId: { + type: "string", + label: "Job Position", + description: "The job position for which the application has been submitted", + async options() { + const { data } = await this.listJobPositions(); + return data.map(({ + job_position_title: label, id: value, + }) => ({ + label, + value, + })); + }, + }, + dataFields: { + type: "object", + label: "Data Fields", + description: "The data fields to be updated for the employee", + }, + requestId: { + type: "string", + label: "Request ID", + description: "The ID of the time off request", + }, + responseStatus: { + type: "string", + label: "Response Status", + description: "The response status for the time off request", + options: [ + "approved", + "pending", + "declined", + ], + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://pubapi.talenthr.io/v1"; + }, + _auth() { + return { + username: `${this.$auth.api_key}`, + password: "c", + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + auth: this._auth(), + ...opts, + }); + }, + listEmploymentStatuses(opts = {}) { + return this._makeRequest({ + path: "/employment-statuses", + ...opts, + }); + }, + listJobTitles(opts = {}) { + return this._makeRequest({ + path: "/job-titles", + ...opts, + }); + }, + listJobLocations(opts = {}) { + return this._makeRequest({ + path: "/locations", + ...opts, + }); + }, + listDivisions(opts = {}) { + return this._makeRequest({ + path: "/divisions", + ...opts, + }); + }, + listDepartments(opts = {}) { + return this._makeRequest({ + path: "/departments", + ...opts, + }); + }, + listEmployees(opts = {}) { + return this._makeRequest({ + path: "/directory", + ...opts, + }); + }, + listNationalities(opts = {}) { + return this._makeRequest({ + path: "/nationalities", + ...opts, + }); + }, + listCountries(opts = {}) { + return this._makeRequest({ + path: "/countries", + ...opts, + }); + }, + listRelationshipTypes(opts = {}) { + return this._makeRequest({ + path: "/relationship-types", + ...opts, + }); + }, + listLanguages(opts = {}) { + return this._makeRequest({ + path: "/languages", + ...opts, + }); + }, + listJobPositions(opts = {}) { + return this._makeRequest({ + path: "/job-positions", + ...opts, + }); + }, + listTimeOffRequests({ + employeeId, ...opts + }) { + return this._makeRequest({ + path: `/employees/${employeeId}/time-off-requests`, + ...opts, + }); + }, + listNewJobApplications(opts = {}) { + const { + jobPositionId, ...otherOpts + } = opts; + return this._makeRequest({ + path: `${jobPositionId + ? `/job-positions/${jobPositionId}` + : ""}/ats-applicants`, + ...otherOpts, + }); + }, + createEmployee(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/employees/hire", + ...opts, + }); + }, + updateEmployee({ + employeeId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/employees/${employeeId}`, + ...opts, + }); + }, + respondToTimeOffRequest({ + timeOffRequestId, employeeId, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/employees/${employeeId}/time-off-requests/${timeOffRequestId}/reply`, + ...opts, + }); + }, + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.limit = LIMIT; + params.offset = LIMIT * page++; + const { data: { rows } } = await fn({ + params, + ...opts, + }); + for (const d of rows) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = rows.length; + + } while (hasMore); }, }, -}; \ No newline at end of file +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 846a2b4eb41ca..23ed7b5a3dec2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9678,7 +9678,10 @@ importers: specifiers: {} components/talenthr: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.1 + dependencies: + '@pipedream/platform': 3.0.3 components/talentlms: specifiers: {} @@ -12833,6 +12836,55 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: + resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + dev: false + /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13068,7 +13120,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13110,55 +13162,6 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: - resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - dev: false - /@aws-sdk/core/3.556.0: resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} @@ -17485,7 +17488,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6