diff --git a/components/niceboard/.gitignore b/components/niceboard/.gitignore deleted file mode 100644 index ec761ccab7595..0000000000000 --- a/components/niceboard/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.js -*.mjs -dist \ No newline at end of file diff --git a/components/niceboard/actions/create-category/create-category.mjs b/components/niceboard/actions/create-category/create-category.mjs new file mode 100644 index 0000000000000..52ab575b5e428 --- /dev/null +++ b/components/niceboard/actions/create-category/create-category.mjs @@ -0,0 +1,34 @@ +import niceboard from "../../niceboard.app.mjs"; + +export default { + key: "niceboard-create-category", + name: "Create Category", + description: "Creates a new job category within Niceboard.", + version: "0.0.1", + type: "action", + props: { + niceboard, + niceboardUrl: { + propDefinition: [ + niceboard, + "niceboardUrl", + ], + }, + name: { + type: "string", + label: "Category Name", + description: "The name of the job category to be created", + }, + }, + async run({ $ }) { + const response = await this.niceboard.createCategory({ + $, + niceboardUrl: this.niceboardUrl, + data: { + name: this.name, + }, + }); + $.export("$summary", `Successfully created category with name "${this.name}"`); + return response; + }, +}; diff --git a/components/niceboard/actions/create-job/create-job.mjs b/components/niceboard/actions/create-job/create-job.mjs new file mode 100644 index 0000000000000..9b9d7d8a90889 --- /dev/null +++ b/components/niceboard/actions/create-job/create-job.mjs @@ -0,0 +1,114 @@ +import niceboard from "../../niceboard.app.mjs"; +import { ConfigurationError } from "@pipedream/platform"; + +export default { + key: "niceboard-create-job", + name: "Create Job", + description: "Creates a new job posting within the Niceboard app.", + version: "0.0.1", + type: "action", + props: { + niceboard, + niceboardUrl: { + propDefinition: [ + niceboard, + "niceboardUrl", + ], + }, + title: { + propDefinition: [ + niceboard, + "title", + ], + }, + description: { + propDefinition: [ + niceboard, + "description", + ], + }, + companyId: { + propDefinition: [ + niceboard, + "companyId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + }, + jobTypeId: { + propDefinition: [ + niceboard, + "jobTypeId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + }, + categoryId: { + propDefinition: [ + niceboard, + "categoryId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + locationId: { + propDefinition: [ + niceboard, + "locationId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + minSalary: { + propDefinition: [ + niceboard, + "minSalary", + ], + }, + maxSalary: { + propDefinition: [ + niceboard, + "maxSalary", + ], + }, + salaryTimeframe: { + propDefinition: [ + niceboard, + "salaryTimeframe", + ], + }, + }, + async run({ $ }) { + if ((this.minSalary || this.maxSalary) && !this.salaryTimeframe) { + throw new ConfigurationError("Salary Timeframe is required if Minimum Salary or Maximum Salary is entered"); + } + + const response = await this.niceboard.createJob({ + $, + niceboardUrl: this.niceboardUrl, + data: { + title: this.title, + description_html: this.description, + company_id: this.companyId, + jobtype_id: this.jobTypeId, + category_id: this.categoryId, + location_id: this.locationId, + salary_min: this.minSalary, + salary_max: this.maxSalary, + salary_timeframe: this.salaryTimeframe, + apply_by_form: true, + }, + }); + + if (response?.job?.id) { + $.export("$summary", `Successfully created job with ID: ${response.job.id}`); + } + return response; + }, +}; diff --git a/components/niceboard/actions/update-job/update-job.mjs b/components/niceboard/actions/update-job/update-job.mjs new file mode 100644 index 0000000000000..d64c15a3dffbb --- /dev/null +++ b/components/niceboard/actions/update-job/update-job.mjs @@ -0,0 +1,122 @@ +import niceboard from "../../niceboard.app.mjs"; + +export default { + key: "niceboard-update-job", + name: "Update Job", + description: "Updates an existing job posting within the Niceboard app.", + version: "0.0.1", + type: "action", + props: { + niceboard, + niceboardUrl: { + propDefinition: [ + niceboard, + "niceboardUrl", + ], + }, + jobId: { + propDefinition: [ + niceboard, + "jobId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + }, + title: { + propDefinition: [ + niceboard, + "title", + ], + optional: true, + }, + description: { + propDefinition: [ + niceboard, + "description", + ], + optional: true, + }, + companyId: { + propDefinition: [ + niceboard, + "companyId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + jobTypeId: { + propDefinition: [ + niceboard, + "jobTypeId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + categoryId: { + propDefinition: [ + niceboard, + "categoryId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + locationId: { + propDefinition: [ + niceboard, + "locationId", + (c) => ({ + niceboardUrl: c.niceboardUrl, + }), + ], + optional: true, + }, + minSalary: { + propDefinition: [ + niceboard, + "minSalary", + ], + }, + maxSalary: { + propDefinition: [ + niceboard, + "maxSalary", + ], + }, + salaryTimeframe: { + propDefinition: [ + niceboard, + "salaryTimeframe", + ], + }, + }, + async run({ $ }) { + const response = await this.niceboard.updateJob({ + $, + niceboardUrl: this.niceboardUrl, + jobId: this.jobId, + data: { + title: this.title, + description_html: this.description, + company_id: this.companyId, + jobtype_id: this.jobTypeId, + category_id: this.categoryId, + location_id: this.locationId, + salary_min: this.minSalary, + salary_max: this.maxSalary, + salary_timeframe: this.salaryTimeframe, + }, + }); + + if (response?.job?.id) { + $.export("$summary", `Successfully updated job with ID: ${response.job.id}`); + } + return response; + }, +}; diff --git a/components/niceboard/app/niceboard.app.ts b/components/niceboard/app/niceboard.app.ts deleted file mode 100644 index 8c032b44f16a7..0000000000000 --- a/components/niceboard/app/niceboard.app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineApp } from "@pipedream/types"; - -export default defineApp({ - type: "app", - app: "niceboard", - propDefinitions: {}, - methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); - }, - }, -}); diff --git a/components/niceboard/niceboard.app.mjs b/components/niceboard/niceboard.app.mjs new file mode 100644 index 0000000000000..5d292f4ba0e0c --- /dev/null +++ b/components/niceboard/niceboard.app.mjs @@ -0,0 +1,219 @@ +import { axios } from "@pipedream/platform"; + +export default { + type: "app", + app: "niceboard", + propDefinitions: { + niceboardUrl: { + type: "string", + label: "Niceboard URL", + description: "If your Niceboard account uses a custom domain or subdomain, enter it here. Otherwise, enter your Niceboard URL (Manage Board -> General -> Base Settings), and include the full domain. Example: `myboard.niceboard.co`", + }, + companyId: { + type: "string", + label: "Company ID", + description: "Identifier of the company of the job posting", + async options({ niceboardUrl }) { + const { results: { companies } } = await this.listCompanies({ + niceboardUrl, + }); + return companies?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + jobTypeId: { + type: "string", + label: "Job Type ID", + description: "Identifier of the type of the job posting", + async options({ niceboardUrl }) { + const { results: { jobtypes } } = await this.listJobTypes({ + niceboardUrl, + }); + return jobtypes?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + categoryId: { + type: "string", + label: "Category ID", + description: "Identifier of the category of the job posting", + async options({ niceboardUrl }) { + const { results: { categories } } = await this.listCategories({ + niceboardUrl, + }); + return categories?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + locationId: { + type: "string", + label: "Location ID", + description: "Identifier of the location of the job posting", + async options({ niceboardUrl }) { + const { results: { locations } } = await this.listLocations({ + niceboardUrl, + }); + return locations?.map(({ + id: value, name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + jobId: { + type: "string", + label: "Job ID", + description: "The ID of the job", + async options({ + niceboardUrl, page, + }) { + const { results: { jobs } } = await this.listJobs({ + niceboardUrl, + params: { + page: page + 1, + }, + }); + return jobs?.map(({ + id: value, title: label, + }) => ({ + value, + label, + })) || []; + }, + }, + title: { + type: "string", + label: "Title", + description: "The title of the job posting", + }, + description: { + type: "string", + label: "Description", + description: "The description of the job posting", + }, + minSalary: { + type: "integer", + label: "Minimum Salary", + description: "Minimum salary value", + optional: true, + }, + maxSalary: { + type: "integer", + label: "Maximum Salary", + description: "Maximum salary value", + optional: true, + }, + salaryTimeframe: { + type: "string", + label: "Salary Timeframe", + description: "Required if minimum or maximum salary values submitted", + options: [ + "annually", + "monthly", + "hourly", + "weekly", + ], + optional: true, + }, + }, + methods: { + _baseUrl(niceboardUrl) { + return `https://${niceboardUrl}/api/v1`; + }, + _makeRequest(opts = {}) { + const { + $ = this, + path, + params, + niceboardUrl, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + url: `${this._baseUrl(niceboardUrl)}${path}`, + params: { + ...params, + key: this.$auth.secret_key, + }, + }); + }, + listCompanies(opts = {}) { + return this._makeRequest({ + path: "/companies", + ...opts, + }); + }, + listJobTypes(opts = {}) { + return this._makeRequest({ + path: "/jobtypes", + ...opts, + }); + }, + listCategories(opts = {}) { + return this._makeRequest({ + path: "/categories", + ...opts, + }); + }, + listLocations(opts = {}) { + return this._makeRequest({ + path: "/locations", + ...opts, + }); + }, + listJobs(opts = {}) { + return this._makeRequest({ + path: "/jobs", + ...opts, + }); + }, + listJobAlerts(opts = {}) { + return this._makeRequest({ + path: "/jobalerts", + ...opts, + }); + }, + listJobSeekers(opts = {}) { + return this._makeRequest({ + path: "/jobseekers", + ...opts, + }); + }, + createJob(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/jobs", + ...opts, + }); + }, + createCategory(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/categories", + ...opts, + }); + }, + updateJob({ + jobId, ...opts + }) { + return this._makeRequest({ + method: "PATCH", + path: `/jobs/${jobId}`, + ...opts, + }); + }, + }, +}; diff --git a/components/niceboard/package.json b/components/niceboard/package.json index ea46c229ddded..a1e9895f9e2e9 100644 --- a/components/niceboard/package.json +++ b/components/niceboard/package.json @@ -1,18 +1,18 @@ { "name": "@pipedream/niceboard", - "version": "0.0.3", + "version": "0.1.0", "description": "Pipedream Niceboard Components", - "main": "dist/app/niceboard.app.mjs", + "main": "niceboard.app.mjs", "keywords": [ "pipedream", "niceboard" ], - "files": [ - "dist" - ], "homepage": "https://pipedream.com/apps/niceboard", "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/niceboard/sources/common/base.mjs b/components/niceboard/sources/common/base.mjs new file mode 100644 index 0000000000000..1d8f1f5e49ea8 --- /dev/null +++ b/components/niceboard/sources/common/base.mjs @@ -0,0 +1,85 @@ +import niceboard from "../../niceboard.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + niceboard, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + niceboardUrl: { + propDefinition: [ + niceboard, + "niceboardUrl", + ], + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + async *paginate({ + fn, max, + }) { + const lastId = this._getLastId(); + const params = { + page: 1, + }; + let hasMore, count = 0; + do { + const response = await fn({ + niceboardUrl: this.niceboardUrl, + params, + }); + const results = this.getResults(response); + const totalPages = this.getTotalPages(response); + for (const item of results) { + if (+item.id > lastId) { + yield item; + if (max && ++count >= max) { + return; + } + } + } + hasMore = params.pages < totalPages; + params.page++; + } while (hasMore); + }, + getMaxResults(results, max) { + if (max && results.length > max) { + return results.slice(-1 * max); + } + return results; + }, + emitEvents(events) { + events.forEach((event) => { + const meta = this.generateMeta(event); + this.$emit(event, meta); + }); + }, + getResults() { + throw new Error("getResults is not implemented"); + }, + getTotalPages() { + throw new Error("getTotalPages is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/niceboard/sources/new-job-alert/new-job-alert.mjs b/components/niceboard/sources/new-job-alert/new-job-alert.mjs new file mode 100644 index 0000000000000..8668f95d0dd50 --- /dev/null +++ b/components/niceboard/sources/new-job-alert/new-job-alert.mjs @@ -0,0 +1,39 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "niceboard-new-job-alert", + name: "New Job Alert", + description: "Emit new event when a new job alert email subscription is created", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + generateMeta(jobalert) { + return { + id: jobalert.id, + summary: `New Job Alert ID: ${jobalert.id}`, + ts: Date.now(), + }; + }, + async processEvent(max) { + let lastId = this._getLastId(); + const { results } = await this.niceboard.listJobAlerts({ + niceboardUrl: this.niceboardUrl, + }); + let jobAlerts = []; + for (const jobAlert of results) { + if (+jobAlert.id > lastId) { + jobAlerts.push(jobAlert); + } + } + if (!jobAlerts?.length) { + return; + } + jobAlerts = this.getMaxResults(jobAlerts, max); + this._setLastId(+jobAlerts[jobAlerts.length - 1].id); + this.emitEvents(jobAlerts); + }, + }, +}; diff --git a/components/niceboard/sources/new-job-seeker/new-job-seeker.mjs b/components/niceboard/sources/new-job-seeker/new-job-seeker.mjs new file mode 100644 index 0000000000000..271b8e79b0e65 --- /dev/null +++ b/components/niceboard/sources/new-job-seeker/new-job-seeker.mjs @@ -0,0 +1,42 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "niceboard-new-job-seeker", + name: "New Job Seeker", + description: "Emit new event when a new job seeker account is registered", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + async processEvent(max) { + let jobSeekers = []; + const newJobSeekers = this.paginate({ + fn: this.niceboard.listJobSeekers, + }); + for await (const jobseeker of newJobSeekers) { + jobSeekers.push(jobseeker); + } + if (!jobSeekers?.length) { + return; + } + jobSeekers = this.getMaxResults(jobSeekers, max); + this._setLastId(+jobSeekers[jobSeekers.length - 1].id); + this.emitEvents(jobSeekers); + }, + getResults(response) { + return response.jobs; + }, + getTotalPages(response) { + return response.total_pages; + }, + generateMeta(jobseeker) { + return { + id: jobseeker.id, + summary: `New Job Seeker: ${jobseeker.first_name} ${jobseeker.last_name}`, + ts: Date.parse(jobseeker.last_indexed_at), + }; + }, + }, +}; diff --git a/components/niceboard/sources/new-job/new-job.mjs b/components/niceboard/sources/new-job/new-job.mjs new file mode 100644 index 0000000000000..b4a1afc1158f9 --- /dev/null +++ b/components/niceboard/sources/new-job/new-job.mjs @@ -0,0 +1,42 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "niceboard-new-job", + name: "New Job Published", + description: "Emit new event each time a new job is published in Niceboard", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + async processEvent(max) { + const jobs = []; + const newJobs = this.paginate({ + fn: this.niceboard.listJobs, + max, + }); + for await (const job of newJobs) { + jobs.push(job); + } + if (!jobs?.length) { + return; + } + this._setLastId(+jobs[0].id); + this.emitEvents(jobs); + }, + getResults({ results }) { + return results.jobs; + }, + getTotalPages({ results }) { + return results.pages.total; + }, + generateMeta(job) { + return { + id: job.id, + summary: `New Job: ${job.title}`, + ts: Date.parse(job.published_at), + }; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c48a19d33f6b8..802c35922fb7c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6529,7 +6529,10 @@ importers: '@pipedream/platform': 3.0.3 components/niceboard: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/nicereply: specifiers: