diff --git a/components/kiwihr/actions/create-employee/create-employee.mjs b/components/kiwihr/actions/create-employee/create-employee.mjs new file mode 100644 index 0000000000000..c4ba441c558e1 --- /dev/null +++ b/components/kiwihr/actions/create-employee/create-employee.mjs @@ -0,0 +1,192 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { parseError } from "../../common/utils.mjs"; +import kiwihr from "../../kiwihr.app.mjs"; + +export default { + key: "kiwihr-create-employee", + name: "Create Employee", + description: "Add a new employee to kiwiHR. [See the documentation](https://api.kiwihr.com/api/docs/mutation.doc.html)", + version: "0.0.1", + type: "action", + props: { + kiwihr, + firstName: { + propDefinition: [ + kiwihr, + "firstName", + ], + }, + lastName: { + propDefinition: [ + kiwihr, + "lastName", + ], + }, + email: { + propDefinition: [ + kiwihr, + "email", + ], + }, + workPhones: { + propDefinition: [ + kiwihr, + "workPhones", + ], + optional: true, + }, + employmentStartDate: { + propDefinition: [ + kiwihr, + "employmentStartDate", + ], + optional: true, + }, + aboutMe: { + propDefinition: [ + kiwihr, + "aboutMe", + ], + optional: true, + }, + gender: { + propDefinition: [ + kiwihr, + "gender", + ], + optional: true, + }, + managerId: { + propDefinition: [ + kiwihr, + "managerId", + ], + optional: true, + }, + nationality: { + propDefinition: [ + kiwihr, + "nationality", + ], + optional: true, + }, + teamIds: { + propDefinition: [ + kiwihr, + "teamIds", + ], + optional: true, + }, + positionId: { + propDefinition: [ + kiwihr, + "positionId", + ], + optional: true, + }, + locationId: { + propDefinition: [ + kiwihr, + "locationId", + ], + optional: true, + }, + birthDate: { + propDefinition: [ + kiwihr, + "birthDate", + ], + optional: true, + }, + personalPhone: { + propDefinition: [ + kiwihr, + "personalPhone", + ], + optional: true, + }, + personalEmail: { + propDefinition: [ + kiwihr, + "personalEmail", + ], + optional: true, + }, + addressStreet: { + propDefinition: [ + kiwihr, + "addressStreet", + ], + optional: true, + }, + addressCity: { + propDefinition: [ + kiwihr, + "addressCity", + ], + optional: true, + }, + addressState: { + propDefinition: [ + kiwihr, + "addressState", + ], + optional: true, + }, + addressPostalCode: { + propDefinition: [ + kiwihr, + "addressPostalCode", + ], + optional: true, + }, + addressCountry: { + propDefinition: [ + kiwihr, + "addressCountry", + ], + optional: true, + }, + pronouns: { + propDefinition: [ + kiwihr, + "pronouns", + ], + optional: true, + }, + }, + async run({ $ }) { + try { + const { + kiwihr, + addressStreet, + addressCity, + addressState, + addressPostalCode, + addressCountry, + ...user + } = this; + + const address = {}; + if (addressStreet) address.street = addressStreet; + if (addressCity) address.city = addressCity; + if (addressState) address.state = addressState; + if (addressPostalCode) address.postalCode = addressPostalCode; + if (addressCountry) address.country = addressCountry; + + if (Object.keys(address).length > 0) { + user.address = address; + } + + const response = await kiwihr.createEmployee({ + user, + }); + + $.export("$summary", `Successfully created employee ${this.firstName} ${this.lastName}`); + return response; + } catch ({ response }) { + const error = parseError(response); + throw new ConfigurationError(error); + } + }, +}; diff --git a/components/kiwihr/actions/update-employee-record/update-employee-record.mjs b/components/kiwihr/actions/update-employee-record/update-employee-record.mjs new file mode 100644 index 0000000000000..64f4e8bdd8afc --- /dev/null +++ b/components/kiwihr/actions/update-employee-record/update-employee-record.mjs @@ -0,0 +1,196 @@ +import { ConfigurationError } from "@pipedream/platform"; +import { parseError } from "../../common/utils.mjs"; +import kiwihr from "../../kiwihr.app.mjs"; + +export default { + key: "kiwihr-update-employee-record", + name: "Update Employee Record", + description: "Update an existing employee's record in kiwiHR. [See the documentation](https://api.kiwihr.it/api/docs/mutation.doc.html)", + version: "0.0.1", + type: "action", + props: { + kiwihr, + employeeId: { + propDefinition: [ + kiwihr, + "employeeId", + ], + }, + firstName: { + propDefinition: [ + kiwihr, + "firstName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + kiwihr, + "lastName", + ], + optional: true, + }, + workPhones: { + propDefinition: [ + kiwihr, + "workPhones", + ], + optional: true, + }, + employmentStartDate: { + propDefinition: [ + kiwihr, + "employmentStartDate", + ], + optional: true, + }, + aboutMe: { + propDefinition: [ + kiwihr, + "aboutMe", + ], + optional: true, + }, + gender: { + propDefinition: [ + kiwihr, + "gender", + ], + optional: true, + }, + managerId: { + propDefinition: [ + kiwihr, + "managerId", + ], + optional: true, + }, + nationality: { + propDefinition: [ + kiwihr, + "nationality", + ], + optional: true, + }, + teamIds: { + propDefinition: [ + kiwihr, + "teamIds", + ], + optional: true, + }, + positionId: { + propDefinition: [ + kiwihr, + "positionId", + ], + optional: true, + }, + locationId: { + propDefinition: [ + kiwihr, + "locationId", + ], + optional: true, + }, + birthDate: { + propDefinition: [ + kiwihr, + "birthDate", + ], + optional: true, + }, + personalPhone: { + propDefinition: [ + kiwihr, + "personalPhone", + ], + optional: true, + }, + personalEmail: { + propDefinition: [ + kiwihr, + "personalEmail", + ], + optional: true, + }, + addressStreet: { + propDefinition: [ + kiwihr, + "addressStreet", + ], + optional: true, + }, + addressCity: { + propDefinition: [ + kiwihr, + "addressCity", + ], + optional: true, + }, + addressState: { + propDefinition: [ + kiwihr, + "addressState", + ], + optional: true, + }, + addressPostalCode: { + propDefinition: [ + kiwihr, + "addressPostalCode", + ], + optional: true, + }, + addressCountry: { + propDefinition: [ + kiwihr, + "addressCountry", + ], + optional: true, + }, + pronouns: { + propDefinition: [ + kiwihr, + "pronouns", + ], + optional: true, + }, + }, + async run({ $ }) { + try { + const { + kiwihr, + employeeId, + addressStreet, + addressCity, + addressState, + addressPostalCode, + addressCountry, + ...user + } = this; + + const address = {}; + if (addressStreet) address.street = addressStreet; + if (addressCity) address.city = addressCity; + if (addressState) address.state = addressState; + if (addressPostalCode) address.postalCode = addressPostalCode; + if (addressCountry) address.country = addressCountry; + + if (Object.keys(address).length > 0) { + user.address = address; + } + + const response = await kiwihr.updateEmployee({ + id: employeeId, + user, + }); + + $.export("$summary", `Successfully updated employee record for ID: ${this.employeeId}`); + return response; + } catch ({ response }) { + const error = parseError(response); + throw new ConfigurationError(error); + } + }, +}; diff --git a/components/kiwihr/common/constants.mjs b/components/kiwihr/common/constants.mjs new file mode 100644 index 0000000000000..40128a95544bf --- /dev/null +++ b/components/kiwihr/common/constants.mjs @@ -0,0 +1,7 @@ +export const LIMIT = 100; + +export const GENDER_OPTIONS = [ + "MALE", + "FEMALE", + "DIVERSE", +]; diff --git a/components/kiwihr/common/mutations.mjs b/components/kiwihr/common/mutations.mjs new file mode 100644 index 0000000000000..04b679303dbb4 --- /dev/null +++ b/components/kiwihr/common/mutations.mjs @@ -0,0 +1,56 @@ +import { gql } from "graphql-request"; + +const createEmployee = gql` + mutation createEmployee($user: CreateUserInput!) { + createUser(user: $user) { + id + firstName + lastName + email + gender + workPhones + employmentStartDate + employmentEndDate + aboutMe + employeeNumber + birthDate + personalEmail + invitationStatus + language + isActive + nationality + personalPhone + pronouns + } + } +`; + +const updateEmployee = gql` + mutation updateEmployee($id: ID!, $user: UpdateUserInput!) { + updateUser(id: $id, user: $user) { + id + firstName + lastName + email + gender + workPhones + employmentStartDate + employmentEndDate + aboutMe + employeeNumber + birthDate + personalEmail + invitationStatus + language + isActive + nationality + personalPhone + pronouns + } + } +`; + +export default { + createEmployee, + updateEmployee, +}; diff --git a/components/kiwihr/common/queries.mjs b/components/kiwihr/common/queries.mjs new file mode 100644 index 0000000000000..870768a878ccb --- /dev/null +++ b/components/kiwihr/common/queries.mjs @@ -0,0 +1,82 @@ +import { gql } from "graphql-request"; + +const listUsers = gql` + query listUsers($offset: Int, $limit: Int, $sort: [SortArgInput!], $filter: UserFilterInput) { + users(offset: $offset, limit: $limit, sort: $sort, filter: $filter) { + items { + id + firstName + lastName + email + gender + workPhones + employmentStartDate + employmentEndDate + aboutMe + birthDate + personalEmail + invitationStatus + language + isActive + nationality + personalPhone + pronouns + } + } + } +`; + +const listManagers = gql` + query listManagers($offset: Int, $limit: Int, $sort: [SortArgInput!], $filter: AvailableManagersFilterInput) { + availableManagers(offset: $offset, limit: $limit, sort: $sort, filter: $filter) { + items { + id + firstName + lastName + email + isActive + } + } + } +`; + +const listTeams = gql` + query listTeams($offset: Int, $limit: Int, $sort: [SortArgInput!]) { + teams(offset: $offset, limit: $limit, sort: $sort) { + items { + id + name + } + } + } +`; + +const listPositions = gql` + query listPositions($offset: Int, $limit: Int, $sort: [SortArgInput!]) { + positions(offset: $offset, limit: $limit, sort: $sort) { + items { + id + name + } + } + } +`; + +const listLocations = gql` + query listLocations($offset: Int, $limit: Int, $sort: [SortArgInput!]) { + locations(offset: $offset, limit: $limit, sort: $sort) { + items { + id + name + } + } + } +`; + +export default { + listUsers, + listManagers, + listTeams, + listPositions, + listLocations, +}; diff --git a/components/kiwihr/common/utils.mjs b/components/kiwihr/common/utils.mjs new file mode 100644 index 0000000000000..bc708574b1c2d --- /dev/null +++ b/components/kiwihr/common/utils.mjs @@ -0,0 +1,6 @@ +export const parseError = (response) => { + const errors = response.errors[0].data; + const errorsArray = Object.entries(errors); + const error = errorsArray[0]; + return `${error[0]} ${error[1].message}`; +}; diff --git a/components/kiwihr/kiwihr.app.mjs b/components/kiwihr/kiwihr.app.mjs index e013be46e0b4f..2bf9b792e3c76 100644 --- a/components/kiwihr/kiwihr.app.mjs +++ b/components/kiwihr/kiwihr.app.mjs @@ -1,11 +1,282 @@ +import { GraphQLClient } from "graphql-request"; +import { + GENDER_OPTIONS, LIMIT, +} from "./common/constants.mjs"; +import mutations from "./common/mutations.mjs"; +import queries from "./common/queries.mjs"; + export default { type: "app", app: "kiwihr", - propDefinitions: {}, + propDefinitions: { + firstName: { + type: "string", + label: "First Name", + description: "The first name of the employee", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the employee", + }, + email: { + type: "string", + label: "Email", + description: "The email of the employee", + }, + workPhones: { + type: "string[]", + label: "Work Phones", + description: "A list of employee's work phone numbers", + }, + employmentStartDate: { + type: "string", + label: "Employment Start Date", + description: "User's work started date. **Format YYYY-MM-DD**", + }, + aboutMe: { + type: "string", + label: "About Me", + description: "Short info about the user", + }, + gender: { + type: "string", + label: "Gender", + description: "The gender of the employee", + options: GENDER_OPTIONS, + }, + managerId: { + type: "string", + label: "Manager ID", + description: "ID of the user's manager", + async options({ page }) { + const { availableManagers: { items } } = await this.listManagers({ + sort: [ + { + "field": "firstName", + "direction": "asc", + }, + ], + limit: LIMIT, + offset: LIMIT * page, + }); + + return items.map(({ + id: value, firstName, lastName, email, + }) => ({ + label: `${firstName} ${lastName} (${email})`, + value, + })); + }, + }, + nationality: { + type: "string", + label: "Nationality", + description: "2-digit Employee's nationality in [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)", + }, + teamIds: { + type: "string[]", + label: "Team IDs", + description: "List of IDs of teams user belongs to", + async options({ page }) { + const { teams: { items } } = await this.listTeams({ + limit: LIMIT, + offset: LIMIT * page, + }); + + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + positionId: { + type: "string", + label: "Position ID", + description: "Employee's position ID", + async options({ page }) { + const { positions: { items } } = await this.listPositions({ + limit: LIMIT, + offset: LIMIT * page, + }); + + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + locationId: { + type: "string", + label: "Location ID", + description: "Employee's location ID", + async options({ page }) { + const { locations: { items } } = await this.listLocations({ + limit: LIMIT, + offset: LIMIT * page, + }); + + return items.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + birthDate: { + type: "string", + label: "Birth Date", + description: "Employee's date of birth", + }, + personalPhone: { + type: "string", + label: "Personal Phone", + description: "Employee's personal phone number", + }, + personalEmail: { + type: "string", + label: "Personal Email", + description: "Employee's personal email address", + }, + addressStreet: { + type: "string", + label: "Street", + description: "The employee's street address", + }, + addressCity: { + type: "string", + label: "City", + description: "The employee's city address", + }, + addressState: { + type: "string", + label: "State", + description: "The employee's state address", + }, + addressPostalCode: { + type: "string", + label: "Postal Code", + description: "The employee's postal code address", + }, + addressCountry: { + type: "string", + label: "Country", + description: "The employee's country address", + }, + pronouns: { + type: "string", + label: "pronouns", + description: "The employee's pronouns", + }, + employeeId: { + type: "string", + label: "Employee ID", + description: "ID of the employee you want to update", + async options({ page }) { + const { users: { items } } = await this.listUsers({ + limit: LIMIT, + offset: LIMIT * page, + }); + + return items.map(({ + id: value, firstName, lastName, email, + }) => ({ + label: `${firstName} ${lastName} (${email})`, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return `${this.$auth.api_url}/api/graphql`; + }, + _headers() { + return { + "X-Api-Key": `${this.$auth.api_key}`, + }; + }, + getClient() { + const url = this._baseUrl(); + const options = { + headers: this._headers(), + }; + return new GraphQLClient(url, options); + }, + _makeRequest({ + query, variables, + } = {}) { + return this.getClient().request(query, variables); + }, + createEmployee(variables) { + return this._makeRequest({ + query: mutations.createEmployee, + variables, + }); + }, + updateEmployee(variables) { + return this._makeRequest({ + query: mutations.updateEmployee, + variables, + }); + }, + listUsers(variables) { + return this._makeRequest({ + query: queries.listUsers, + variables, + }); + }, + listManagers(variables) { + return this._makeRequest({ + query: queries.listManagers, + variables, + }); + }, + listTeams(variables) { + return this._makeRequest({ + query: queries.listTeams, + variables, + }); + }, + listPositions(variables) { + return this._makeRequest({ + query: queries.listPositions, + variables, + }); + }, + listLocations(variables) { + return this._makeRequest({ + query: queries.listLocations, + variables, + }); + }, + async *paginate({ + fn, variables = {}, fieldName, maxResults = null, + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + variables.limit = LIMIT; + variables.offset = LIMIT * page++; + const data = await fn(variables); + for (const d of data[fieldName].items) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = data[fieldName].items.length; + + } while (hasMore); }, }, }; diff --git a/components/kiwihr/package.json b/components/kiwihr/package.json index 1bb1546c948e9..e864a91de979a 100644 --- a/components/kiwihr/package.json +++ b/components/kiwihr/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/kiwihr", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream kiwiHR Components", "main": "kiwihr.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/kiwihr/sources/common/base.mjs b/components/kiwihr/sources/common/base.mjs new file mode 100644 index 0000000000000..817d1939b0620 --- /dev/null +++ b/components/kiwihr/sources/common/base.mjs @@ -0,0 +1,59 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import kiwihr from "../../kiwihr.app.mjs"; + +export default { + props: { + kiwihr, + 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); + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + + const response = this.kiwihr.paginate({ + fn: this.getFunction(), + fieldName: this.getFieldName(), + variables: this.getVariables(), + maxResults, + }); + + let responseArray = []; + for await (const item of response) { + if (item.id <= lastId) break; + responseArray.push(item); + } + + if (responseArray.length) { + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.now(), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/kiwihr/sources/new-employee/new-employee.mjs b/components/kiwihr/sources/new-employee/new-employee.mjs new file mode 100644 index 0000000000000..22cd6bd18f1c1 --- /dev/null +++ b/components/kiwihr/sources/new-employee/new-employee.mjs @@ -0,0 +1,35 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "kiwihr-new-employee", + name: "New Employee", + description: "Emit new event when a new employee is added to KiwiHR.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.kiwihr.listUsers; + }, + getFieldName() { + return "users"; + }, + getVariables() { + return { + sort: [ + { + "field": "id", + "direction": "desc", + }, + ], + }; + }, + getSummary(item) { + return `New employee: ${item.firstName} ${item.lastName}`; + }, + }, + sampleEmit, +}; diff --git a/components/kiwihr/sources/new-employee/test-event.mjs b/components/kiwihr/sources/new-employee/test-event.mjs new file mode 100644 index 0000000000000..b91aec335a337 --- /dev/null +++ b/components/kiwihr/sources/new-employee/test-event.mjs @@ -0,0 +1,29 @@ +export default { + "id": 123, + "firstName": "String", + "lastName": "String", + "email": "String", + "gender": "String", + "workPhones": ["String"], + "employmentStartDate": "String", + "employmentEndDate": "String", + "aboutMe": "String", + "employeeNumber": "String", + "birthDate": "String", + "personalEmail": "String", + "address": "String", + "invitationStatus": "String", + "language": "String", + "position": "String", + "team": "String", + "teams": ["String"], + "location": "String", + "isActive": true, + "workSchedule": "String", + "manager": "String", + "nationality": "String", + "personalPhone": "String", + "socialAccounts": ["String"], + "pronouns": "String", + "customFieldValues": "String", +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0cafdf2882d35..1fc6a87c8073d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3176,8 +3176,7 @@ importers: components/danny_test_app: {} - components/dappier: - specifiers: {} + components/dappier: {} components/darksky_api: dependencies: @@ -6924,7 +6923,11 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/kiwihr: {} + components/kiwihr: + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/kizeo_forms: dependencies: