diff --git a/package-lock.json b/package-lock.json index abb1ddcb82..45179aed02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -276,6 +276,7 @@ "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "17.7.0", "@faker-js/faker": "8.2.0", + "@hubspot/api-client": "12.0.1", "@nx/esbuild": "20.0.1", "@nx/eslint": "20.0.1", "@nx/eslint-plugin": "20.0.1", @@ -5905,6 +5906,81 @@ "react-hook-form": "^7.0.0" } }, + "node_modules/@hubspot/api-client": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@hubspot/api-client/-/api-client-12.0.1.tgz", + "integrity": "sha512-lMxDEuhaP1KDxo0Z/t+3xAT/wzVaQCZ9ThSewj9qCMkhBMYA2ABWLOY/I+huQCERuMqkqwmMBx/NOCspBlGQGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/node-fetch": "^2.5.7", + "bottleneck": "^2.19.5", + "es6-promise": "^4.2.4", + "form-data": "^2.5.0", + "lodash.get": "^4.4.2", + "lodash.merge": "^4.6.2", + "node-fetch": "^2.6.0", + "url-parse": "^1.4.3" + } + }, + "node_modules/@hubspot/api-client/node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@hubspot/api-client/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@hubspot/api-client/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -23857,6 +23933,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.19.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz", diff --git a/package.json b/package.json index bb1eb93706..be980631da 100644 --- a/package.json +++ b/package.json @@ -292,6 +292,7 @@ "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "17.7.0", "@faker-js/faker": "8.2.0", + "@hubspot/api-client": "12.0.1", "@nx/esbuild": "20.0.1", "@nx/eslint": "20.0.1", "@nx/eslint-plugin": "20.0.1", @@ -435,4 +436,4 @@ "rollup": "npm:@rollup/wasm-node" } } -} \ No newline at end of file +} diff --git a/packages/pieces/community/hubspot/package.json b/packages/pieces/community/hubspot/package.json index c15d2690ca..955f9ddfec 100644 --- a/packages/pieces/community/hubspot/package.json +++ b/packages/pieces/community/hubspot/package.json @@ -1,4 +1,4 @@ { "name": "@activepieces/piece-hubspot", - "version": "0.5.8" + "version": "0.6.0" } diff --git a/packages/pieces/community/hubspot/src/index.ts b/packages/pieces/community/hubspot/src/index.ts index d19b17cae3..29404fdafa 100644 --- a/packages/pieces/community/hubspot/src/index.ts +++ b/packages/pieces/community/hubspot/src/index.ts @@ -2,17 +2,70 @@ import { createCustomApiCallAction } from '@activepieces/pieces-common'; import { OAuth2PropertyValue, PieceAuth, createPiece } from '@activepieces/pieces-framework'; import { PieceCategory } from '@activepieces/shared'; import { hubSpotListsAddContactAction } from './lib/actions/add-contact-to-list-action'; -import { createHubspotContact } from './lib/actions/create-contact.action'; -import { hubSpotContactsCreateOrUpdateAction } from './lib/actions/create-or-update-contact-action'; -import { hubSpotGetOwnerByEmailAction } from './lib/actions/search-owner-by-email'; -import { newCompanyAdded } from './lib/triggers/new-company-added'; -import { newContactAdded } from './lib/triggers/new-contact-added'; -import { newDealAdded } from './lib/triggers/new-deal-added'; -import { newTaskAdded } from './lib/triggers/new-task-added'; -import { newTicketAdded } from './lib/triggers/new-ticket-added'; +import { newCompanyTrigger } from './lib/triggers/new-company'; +import { newContactTrigger } from './lib/triggers/new-contact'; +import { newDealTrigger } from './lib/triggers/new-deal'; +import { newTaskTrigger } from './lib/triggers/new-task'; +import { newTicketTrigger } from './lib/triggers/new-ticket'; import { createDealAction } from './lib/actions/create-deal'; import { updateDealAction } from './lib/actions/update-deal'; import { dealStageUpdatedTrigger } from './lib/triggers/deal-stage-updated'; +import { getContactAction } from './lib/actions/get-contact'; +import { getDealAction } from './lib/actions/get-deal'; +import { getTicketAction } from './lib/actions/get-ticket'; +import { getCompanyAction } from './lib/actions/get-company'; +import { getPipelineStageDeatilsAction } from './lib/actions/get-pipeline-stage-details'; +import { getProductAction } from './lib/actions/get-product'; +import { addContactToWorkflowAction } from './lib/actions/add-contact-to-workflow'; +import { createTicketAction } from './lib/actions/create-ticket'; +import { updateTicketAction } from './lib/actions/update-ticket'; +import { findTicketAction } from './lib/actions/find-ticket'; +import { createContactAction } from './lib/actions/create-contact'; +import { updateContactAction } from './lib/actions/update-contact'; +import { findContactAction } from './lib/actions/find-contact'; +import { createOrUpdateContactAction } from './lib/actions/create-or-update-contact'; +import { createProductAction } from './lib/actions/create-product'; +import { updateProductAction } from './lib/actions/update-product'; +import { findProductAction } from './lib/actions/find-product'; +import { createCompanyAction } from './lib/actions/create-company'; +import { findCompanyAction } from './lib/actions/find-company'; +import { updateCompanyAction } from './lib/actions/update-company'; +import { createCustomObjectAction } from './lib/actions/create-custom-object'; +import { updateCustomObjectAction } from './lib/actions/update-custom-object'; +import { getCustomObjectAction } from './lib/actions/get-custom-object'; +import { findCustomObjectAction } from './lib/actions/find-custom-object'; +import { getOwnerByEmailAction } from './lib/actions/get-owner-by-email'; +import { getOwnerByIdAction } from './lib/actions/get-owner-by-id'; +import { findDealAction } from './lib/actions/find-deal'; +import { createLineItemAction } from './lib/actions/create-line-item'; +import { getLineItemAction } from './lib/actions/get-line-item'; +import { updateLineItemAction } from './lib/actions/update-line-item'; +import { findLineItemAction } from './lib/actions/find-line-item'; +import { removeContactFromListAction } from './lib/actions/remove-contact-from-list'; +import { uploadFileAction } from './lib/actions/upload-file'; +import { removeEmailSubscriptionAction } from './lib/actions/remove-email-subscription'; +import { createAssociationsAction } from './lib/actions/create-associations'; +import { removeAssociationsAction } from './lib/actions/remove-associations'; +import { findAssociationsAction } from './lib/actions/find-associations'; +import { newOrUpdatedCompanyTrigger } from './lib/triggers/new-or-updated-company'; +import { newOrUpdatedContactTrigger } from './lib/triggers/new-or-updated-contact'; +import { newOrUpdatedProductTrigger } from './lib/triggers/new-or-updated-product'; +import { newOrUpdatedLineItemTrigger } from './lib/triggers/new-or-updated-line-item'; +import { newContactPropertyChangeTrigger } from './lib/triggers/new-contact-property-change'; +import { newTicketPropertyChangeTrigger } from './lib/triggers/new-ticket-property-change'; +import { newCompanyPropertyChangeTrigger } from './lib/triggers/new-company-propety-change'; +import { newDealPropertyChangeTrigger } from './lib/triggers/new-deal-property-change'; +import { newCustomObjectPropertyChangeTrigger } from './lib/triggers/new-custom-object-property-change'; +import { newLineItemTrigger } from './lib/triggers/new-line-item'; +import { newProductTrigger } from './lib/triggers/new-product'; +import { newCustomObjectTrigger } from './lib/triggers/new-custom-object'; +import { newFormSubmissionTrigger } from './lib/triggers/new-form-submission'; +import { newEmailEventTrigger } from './lib/triggers/new-email-event'; +import { newBlogArticleTrigger } from './lib/triggers/new-blog-article'; +import { newContactInListTrigger } from './lib/triggers/new-contact-in-list'; +import { newEngagementTrigger } from './lib/triggers/new-engagement'; +import { newEmailSubscriptionsTimelineTrigger } from './lib/triggers/email-subscriptions-timeline'; +import { createBlogPostAction } from './lib/actions/create-blog-post'; export const hubspotAuth = PieceAuth.OAuth2({ authUrl: 'https://app.hubspot.com/oauth/authorize', @@ -21,19 +74,31 @@ export const hubspotAuth = PieceAuth.OAuth2({ scope: [ 'crm.lists.read', 'crm.lists.write', - 'crm.objects.contacts.read', - 'crm.objects.contacts.write', - 'crm.objects.owners.read', 'crm.objects.companies.read', 'crm.objects.companies.write', + 'crm.objects.contacts.read', + 'crm.objects.contacts.write', + 'crm.objects.custom.read', + 'crm.objects.custom.write', 'crm.objects.deals.read', 'crm.objects.deals.write', 'crm.objects.line_items.read', - 'crm.schemas.line_items.read', + 'crm.objects.owners.read', 'crm.schemas.companies.read', 'crm.schemas.contacts.read', + 'crm.schemas.custom.read', 'crm.schemas.deals.read', + 'crm.schemas.line_items.read', + 'automation', + 'e-commerce', 'tickets', + 'content', + 'settings.currencies.read', + 'settings.users.read', + 'settings.users.teams.read', + 'files', + 'forms' + // 'business_units_view.read' ], }); @@ -46,12 +111,47 @@ export const hubspot = createPiece({ categories: [PieceCategory.SALES_AND_CRM], auth: hubspotAuth, actions: [ - createHubspotContact, - hubSpotContactsCreateOrUpdateAction, hubSpotListsAddContactAction, - hubSpotGetOwnerByEmailAction, + addContactToWorkflowAction, + createAssociationsAction, + createCompanyAction, + createContactAction, + createBlogPostAction, + createCustomObjectAction, createDealAction, + createLineItemAction, + createOrUpdateContactAction, + createProductAction, + createTicketAction, + getCompanyAction, + getContactAction, + getCustomObjectAction, + getDealAction, + getLineItemAction, + getProductAction, + getTicketAction, + removeAssociationsAction, + removeContactFromListAction, + removeEmailSubscriptionAction, + updateCompanyAction, + updateContactAction, + updateCustomObjectAction, updateDealAction, + updateLineItemAction, + updateProductAction, + updateTicketAction, + uploadFileAction, + findAssociationsAction, + findCompanyAction, + findContactAction, + findCustomObjectAction, + findDealAction, + findLineItemAction, + findProductAction, + findTicketAction, + getOwnerByEmailAction, + getOwnerByIdAction, + getPipelineStageDeatilsAction, createCustomApiCallAction({ baseUrl: () => 'https://api.hubapi.com', auth: hubspotAuth, @@ -61,11 +161,29 @@ export const hubspot = createPiece({ }), ], triggers: [ - newTaskAdded, - newCompanyAdded, - newContactAdded, - newDealAdded, - newTicketAdded, + newOrUpdatedCompanyTrigger, + newOrUpdatedContactTrigger, + newDealPropertyChangeTrigger, + newEmailSubscriptionsTimelineTrigger, + newOrUpdatedLineItemTrigger, + newCompanyTrigger, + newCompanyPropertyChangeTrigger, + newContactTrigger, + newContactInListTrigger, + newContactPropertyChangeTrigger, + newBlogArticleTrigger, + newCustomObjectTrigger, + newCustomObjectPropertyChangeTrigger, + newDealTrigger, + newEmailEventTrigger, + newEngagementTrigger, + newFormSubmissionTrigger, + newLineItemTrigger, + newProductTrigger, + newTicketTrigger, + newTicketPropertyChangeTrigger, + newOrUpdatedProductTrigger, + newTaskTrigger, dealStageUpdatedTrigger, ], }); diff --git a/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts index 27623c9e43..04d850588b 100644 --- a/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts +++ b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-list-action.ts @@ -1,35 +1,36 @@ -import { hubSpotListIdDropdown } from '../common/props'; -import { hubSpotClient } from '../common/client'; +import { staticListsDropdown } from '../common/props'; import { createAction, Property } from '@activepieces/pieces-framework'; -import { assertNotNullOrUndefined } from '@activepieces/shared'; import { hubspotAuth } from '../../'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; export const hubSpotListsAddContactAction = createAction({ - auth: hubspotAuth, - name: 'add_contact_to_list', - displayName: 'Add contact To List', - description: 'Add contact to list', - props: { - listId: hubSpotListIdDropdown, - email: Property.ShortText({ - displayName: 'Email', - description: 'Contact email', - required: true, - }), - }, + auth: hubspotAuth, + name: 'add_contact_to_list', + displayName: 'Add contact To List', + description: 'Add contact to list', + props: { + listId: staticListsDropdown, + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + }, - async run(context) { - const token = context.auth.access_token; - const { listId, email } = context.propsValue; + async run(context) { + const { listId, email } = context.propsValue; - assertNotNullOrUndefined(token, 'token'); - assertNotNullOrUndefined(listId, 'list'); - assertNotNullOrUndefined(email, 'email'); + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/contacts/v1/lists/${listId}/add`, + body: { + emails: [email], + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }) - return await hubSpotClient.lists.addContact({ - token, - listId, - email, - }); - }, + return response.body; + }, }); diff --git a/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts new file mode 100644 index 0000000000..c28ff74a6a --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/add-contact-to-workflow.ts @@ -0,0 +1,34 @@ +import { hubspotAuth } from "../../"; +import { createAction, Property } from "@activepieces/pieces-framework"; +import { workflowIdDropdown } from "../common/props"; +import { AuthenticationType, httpClient, HttpMethod } from "@activepieces/pieces-common"; + +export const addContactToWorkflowAction = createAction({ + auth:hubspotAuth, + name:'add-contact-to-workflow', + displayName:'Add Contact to Workflow', + description:'Adds a contact to a specified workflow.', + props:{ + workflowId : workflowIdDropdown, + email:Property.ShortText({ + displayName:"Contact's Email", + description:'The email of the contact to add to the workflow.', + required:true + }), + }, + async run(context) { + const contactEmail = context.propsValue.email; + const workflowId = context.propsValue.workflowId; + + const response = await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/automation/v2/workflows/${workflowId}/enrollments/contacts/${contactEmail}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + }) + + return response; + }, +}) \ No newline at end of file diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts new file mode 100644 index 0000000000..89dc51b3ad --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-associations.ts @@ -0,0 +1,108 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + fromObjectTypeAssociationDropdown, + associationTypeDropdown, + toObjectIdsDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { AssociationSpecAssociationCategoryEnum } from '../common/types'; + +export const createAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'create-associations', + displayName: 'Create Associations', + description: 'Creates assosiations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object being associated.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object being associated.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: 'Type of the objects the from object is being associated with.', + }), + associationType: associationTypeDropdown, + toObjectIds: toObjectIdsDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object IDs', + required: true, + description: 'The ID\'sof the objects the from object is being associated with', + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType, associationType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + if(context.propsValue.toObjectIds === undefined) { + throw new Error('Please provide To Object IDs'); + } + + let toObjectIds: any[]; + if (Array.isArray(context.propsValue.toObjectIds)) { + toObjectIds = context.propsValue.toObjectIds; + } else { + try { + toObjectIds = JSON.parse(context.propsValue.toObjectIds); + } catch { + throw new Error( + `Please provide To Object IDs in a valid format. Provided : ${JSON.stringify( + context.propsValue.toObjectIds, + )}`, + ); + } + } + + // find the association category + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + const association = associationLabels.results.find( + (associationLabel) => associationLabel.typeId === associationType, + ); + if (!association) { + throw new Error( + `Association type ${associationType} not found for ${fromObjectType} to ${toObjectType}`, + ); + } + const associationCategory = association.category; + + const response = await client.crm.associations.v4.batchApi.create( + fromObjectType as string, + toObjectType as string, + { + inputs: toObjectIds.map((objectId) => { + return { + _from: { + id: fromObjectId, + }, + to: { + id: objectId, + }, + types: [ + { + associationCategory: + associationCategory as unknown as AssociationSpecAssociationCategoryEnum, + associationTypeId: associationType, + }, + ], + }; + }), + }, + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts b/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts new file mode 100644 index 0000000000..adecb98c2a --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-blog-post.ts @@ -0,0 +1,93 @@ +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { blogAuthorDropdown, blogUrlDropdown } from '../common/props'; + +export const createBlogPostAction = createAction({ + auth: hubspotAuth, + name: 'create-blog-post', + displayName: 'Create COS Blog Post', + description: 'Creates a blog post in you Hubspot COS blog.', + props: { + contentGroupId: blogUrlDropdown, + authorId: blogAuthorDropdown, + status: Property.StaticDropdown({ + displayName: 'Publish This Post?', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Leave As Draft', + value: 'DRAFT', + }, + { + label: 'Publish Immediately', + value: 'PUBLISHED', + }, + ], + }, + }), + slug: Property.ShortText({ + displayName: 'Slug', + required: true, + description: 'The slug of the blog post. This is the URL of the post on your COS blog.', + }), + title: Property.ShortText({ + displayName: 'Blog Post Title', + required: true, + }), + body: Property.LongText({ + displayName: 'Blog Post Content', + required: true, + }), + meta: Property.LongText({ + displayName: 'Meta Description', + required: true, + }), + imageUrl: Property.ShortText({ + displayName: 'Featured Image URL', + required: true, + }), + }, + async run(context) { + const { contentGroupId, authorId, status, slug, title, body, meta, imageUrl } = + context.propsValue; + + const createdPost = await httpClient.sendRequest>({ + method: HttpMethod.POST, + url: 'https://api.hubapi.com/content/api/v2/blog-posts', + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + body: { + blog_author_id: authorId, + content_group_id: contentGroupId, + featured_image: imageUrl, + use_featured_image: true, + name: title, + slug: slug, + meta_description: meta, + post_body: body, + publish_immediately: status === 'PUBLISHED' ? true : undefined, + }, + }); + + if (status === 'PUBLISHED') { + await httpClient.sendRequest({ + method: HttpMethod.POST, + url: `https://api.hubapi.com/content/api/v2/blog-posts/${createdPost.body['id']}/publish-action`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + body: { + action: 'schedule-publish', + }, + }); + } + + const postDeatils = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/content/api/v2/blog-posts/${createdPost.body['id']}`, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: context.auth.access_token }, + }); + + return postDeatils.body + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-company.ts b/packages/pieces/community/hubspot/src/lib/actions/create-company.ts new file mode 100644 index 0000000000..b8bef6dc27 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-company.ts @@ -0,0 +1,56 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectDynamicProperties, standardObjectPropertiesDropdown} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const createCompanyAction = createAction({ + auth: hubspotAuth, + name: 'create-company', + displayName: 'Create Company', + description: 'Creates a company in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.COMPANY, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const companyProperties: Record = {}; + + // Add additional properties to the companyProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + companyProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdCompany = await client.crm.companies.basicApi.create({ + properties: companyProperties, + }); + // Retrieve default properties for the comapny and merge with additional properties to retrieve + const defaultcompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const companyDetails = await client.crm.companies.basicApi.getById(createdCompany.id, [ + ...defaultcompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-contact.action.ts b/packages/pieces/community/hubspot/src/lib/actions/create-contact.action.ts deleted file mode 100644 index c29174b12a..0000000000 --- a/packages/pieces/community/hubspot/src/lib/actions/create-contact.action.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { createAction, Property } from '@activepieces/pieces-framework'; -import { - AuthenticationType, - httpClient, - HttpMethod, - HttpRequest, -} from '@activepieces/pieces-common'; -import { hubspotAuth } from '../../'; -import { hubspotCommon } from '../common'; - -export const createHubspotContact = createAction({ - auth: hubspotAuth, - name: 'create_contact', - displayName: 'Create Contact', - description: 'Fails on duplicate email addresses', - props: { - firstName: Property.ShortText({ - displayName: 'First Name', - description: 'First name of the new contact', - required: true, - }), - lastName: Property.ShortText({ - displayName: 'Last Name', - description: 'Last name of the new contact', - required: true, - }), - zip: Property.ShortText({ - displayName: 'Zip Code', - description: 'Zip code of the new contact', - required: false, - }), - email: Property.ShortText({ - displayName: 'Email', - description: 'Email of the new contact', - required: false, - }), - choose_props: hubspotCommon.choose_props, - dynamicProperties: hubspotCommon.dynamicProperties, - }, - async run(context) { - const dynamicProperties = context.propsValue.dynamicProperties as Record< - string, - any - >; - - const configsWithoutAuthentication: Record = { - firstName: context.propsValue.firstName, - lastName: context.propsValue.lastName, - }; - if (context.propsValue.zip) { - configsWithoutAuthentication['zip'] = context.propsValue.zip; - } - if (context.propsValue.email) { - configsWithoutAuthentication['email'] = context.propsValue.email; - } - Object.entries(dynamicProperties).forEach((f) => { - configsWithoutAuthentication[f[0]] = f[1]; - }); - - const body = { - properties: Object.entries(configsWithoutAuthentication).map((f) => { - return { - property: f[0] as string, - value: f[1], - }; - }), - }; - const request: HttpRequest<{ - properties: { property: string; value: any }[]; - }> = { - method: HttpMethod.POST, - url: 'https://api.hubapi.com/contacts/v1/contact/', - body: body, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: context.auth.access_token, - }, - queryParams: {}, - }; - const result = await httpClient.sendRequest(request); - - return { - success: true, - request_body: body, - response_body: result.body, - }; - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts new file mode 100644 index 0000000000..29367f4392 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-contact.ts @@ -0,0 +1,57 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; +import { getDefaultPropertiesForObject, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const createContactAction = createAction({ + auth: hubspotAuth, + name: 'create-contact', + displayName: 'Create Contact', + description: 'Creates a contact in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdContact = await client.crm.contacts.basicApi.create({ + properties: contactProperties, + }); + // Retrieve default properties for the contact and merge with additional properties to retrieve + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const contactDetails = await client.crm.contacts.basicApi.getById(createdContact.id, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts new file mode 100644 index 0000000000..f2c23d2c12 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-custom-object.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../..'; +import { + customObjectDropdown, + customObjectDynamicProperties, + customObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const createCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'create-custome-object', + displayName: 'Create Custom Object', + description: 'Creates a custom object in Hubspot.', + props: { + customObjectType: customObjectDropdown, + objectProperties: customObjectDynamicProperties, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const customObjectProperties: Record = {}; + + // Add additional properties to the customObjectProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + customObjectProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdCustomObject = await client.crm.objects.basicApi.create(customObjectType, { + properties: customObjectProperties, + associations: [], + }); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + createdCustomObject.id, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts index edafb3661f..7b3e29737a 100644 --- a/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts +++ b/packages/pieces/community/hubspot/src/lib/actions/create-deal.ts @@ -1,288 +1,89 @@ -import { - AuthenticationType, - HttpMethod, - HttpRequest, - httpClient, -} from '@activepieces/pieces-common'; - import { hubspotAuth } from '../../'; -import { - DynamicPropsValue, - PiecePropValueSchema, - Property, - createAction, -} from '@activepieces/pieces-framework'; +import { Property, createAction } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; import { - ListDealPipelinesResponse, - ListOwnersResponse, - ListPipelineStagesResponse, - ListPropertiesResponse, - PropertyResponse, -} from '../common/models'; + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; export const createDealAction = createAction({ - auth: hubspotAuth, - name: 'create_deal', - displayName: 'Create Deal', - description: 'Creates a new deal in hubspot.', - props: { - dealname: Property.ShortText({ - displayName: 'Deal Name', - required: true, - }), - amount: Property.Number({ - displayName: 'Deal Amount', - required: false, - }), - pipelineId: Property.Dropdown({ - displayName: 'Deal Pipeline', - refreshers: [], - required: true, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/pipelines/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = - await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((pipeline) => { - return { - label: pipeline.label, - value: pipeline.id, - }; - }), - }; - }, - }), - dealstageId: Property.Dropdown({ - displayName: 'Deal Stage', - refreshers: ['pipelineId'], - required: true, - options: async ({ auth, pipelineId }) => { - if (!auth || !pipelineId) { - return { - disabled: true, - placeholder: - 'Please connect your account first and select pipeline.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: `https://api.hubapi.com/crm/v3/pipelines/deals/${pipelineId}/stages`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = - await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((stage) => { - return { - label: stage.label, - value: stage.id, - }; - }), - }; - }, - }), - hubspot_owner_id: Property.Dropdown({ - displayName: 'Deal Owner', - refreshers: [], - required: false, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/owners', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest( - request - ); - return { - disabled: false, - options: response.body.results.map((owner) => { - return { - label: owner.email, - value: owner.id, - }; - }), - }; - }, - }), - additionalFields: Property.DynamicProperties({ - displayName: 'Additional Fields', - refreshers: [], - required: true, - props: async ({ auth }) => { - if (!auth) return {}; + auth: hubspotAuth, + name: 'create-deal', + displayName: 'Create Deal', + description: 'Creates a new deal in Hubspot.', + props: { + dealname: Property.ShortText({ + displayName: 'Deal Name', + required: true, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Pipeline', + required: true, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Deal Stage', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.DEAL, [ + 'dealname', + 'pipeline', + 'dealstage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { dealname, pipelineId, pipelineStageId } = context.propsValue; + const objectProperites = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const dealProperties: Record = { + dealname, + pipeline: pipelineId!, + dealstage: pipelineStageId!, + }; + + // Add additional properties to the dealProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + dealProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); - const fields: DynamicPropsValue = {}; - const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: context.auth.access_token }); - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/properties/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; + const createdDeal = await client.crm.deals.basicApi.create({ + properties: dealProperties, + }); + // Retrieve default properties for the deal and merge with additional properties to retrieve + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); - const response = await httpClient.sendRequest( - request - ); + const dealDetails = await client.crm.deals.basicApi.getById(createdDeal.id, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); - for (const property of response.body.results) { - if (isRelevantProperty(property)) { - switch (property.fieldType) { - case 'booleancheckbox': - case 'radio': - case 'select': - fields[property.name] = Property.StaticDropdown({ - displayName: property.label, - description: property.description ?? '', - required: false, - options: { - disabled: false, - options: property.options.map((option) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - break; - case 'checkbox': - fields[property.name] = Property.StaticMultiSelectDropdown({ - displayName: property.label, - description: property.description ?? '', - required: false, - options: { - disabled: false, - options: property.options.map((option) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - break; - case 'date': - fields[property.name] = Property.DateTime({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'file': - fields[property.name] = Property.File({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'text': - case 'phonenumber': - case 'html': - fields[property.name] = Property.ShortText({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'textarea': - fields[property.name] = Property.LongText({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - default: - break; - } - } - } - return fields; - }, - }), - }, - async run(context) { - const additionalFields = context.propsValue.additionalFields; - const properties: DynamicPropsValue = {}; - Object.entries(additionalFields).forEach(([key, value]) => { - if (Array.isArray(value)) { - properties[key] = value.join(';'); - } else { - properties[key] = value; - } - }); - const request: HttpRequest = { - method: HttpMethod.POST, - url: 'https://api.hubapi.com/crm/v3/objects/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: context.auth.access_token, - }, - body: { - properties: { - dealname: context.propsValue.dealname, - amount: context.propsValue.amount, - pipeline: context.propsValue.pipelineId, - dealstage: context.propsValue.dealstageId, - hubspot_owner_id: context.propsValue.hubspot_owner_id, - ...properties, - }, - }, - }; - const response = await httpClient.sendRequest(request); - return response.body; - }, + return dealDetails; + }, }); -function isRelevantProperty(property: PropertyResponse) { - return ( - !property.modificationMetadata.readOnlyValue && - !property.hidden && - !property.externalOptions && - ![ - 'dealname', - 'pipeline', - 'dealstage', - 'hubspot_owner_id', - 'amount', - ].includes(property.name) - ); -} diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts new file mode 100644 index 0000000000..7cf5e7a3d7 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-line-item.ts @@ -0,0 +1,72 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + productDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { Client } from '@hubspot/api-client'; + +export const createLineItemAction = createAction({ + auth: hubspotAuth, + name: 'create-line-item', + displayName: 'Create Line Item', + description: 'Creates a line item in Hubspot.', + props: { + productId: productDropdown({ + displayName: 'Line Item Information: Product ID', + required: true, + objectType: OBJECT_TYPE.PRODUCT, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.LINE_ITEM, ['hs_product_id']), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const productId = context.propsValue.productId; + + const objectProperites = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const lineItemProperties: Record = { + hs_product_id: productId!, + }; + + // Add additional properties to the lineItemProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + lineItemProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdLineItem = await client.crm.lineItems.basicApi.create({ + associations: [], + properties: lineItemProperties, + }); + // Retrieve default properties for the line item and merge with additional properties to retrieve + const defaultlineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const lineItemDeatils = await client.crm.lineItems.basicApi.getById(createdLineItem.id, [ + ...defaultlineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDeatils; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact-action.ts b/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact-action.ts deleted file mode 100644 index 52f6590e59..0000000000 --- a/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact-action.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { hubSpotClient } from '../common/client'; -import { createAction, Property } from '@activepieces/pieces-framework'; -import { assertNotNullOrUndefined } from '@activepieces/shared'; -import { hubspotAuth } from '../../'; - -export const hubSpotContactsCreateOrUpdateAction = createAction({ - auth: hubspotAuth, - name: 'create_or_update_contact', - displayName: 'Create or Update Contact', - description: 'Updates contact if email address already exists', - props: { - email: Property.ShortText({ - displayName: 'Email', - description: 'Contact email', - required: true, - }), - firstName: Property.ShortText({ - displayName: 'First Name', - description: 'contact first name', - required: false, - }), - lastName: Property.ShortText({ - displayName: 'Last Name', - description: 'contact last name', - required: false, - }), - zip: Property.ShortText({ - displayName: 'Zip Code', - description: 'contact zip code', - required: false, - }), - }, - - async run(context) { - const token = context.auth.access_token; - const { email, firstName, lastName, zip } = context.propsValue; - - assertNotNullOrUndefined(token, 'token'); - assertNotNullOrUndefined(email, 'email'); - - return await hubSpotClient.contacts.createOrUpdate({ - token, - email, - contact: { - firstname: firstName, - lastname: lastName, - zip, - }, - }); - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts new file mode 100644 index 0000000000..16bca38cef --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-or-update-contact.ts @@ -0,0 +1,58 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { standardObjectDynamicProperties } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + + +export const createOrUpdateContactAction = createAction({ + auth: hubspotAuth, + name: 'create-or-update-contact', + displayName: 'Create or Update Contact', + description: 'Creates a new contact or updates an existing contact based on email address.', + props: { + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, ['email']), + }, + async run(context) { + const email = context.propsValue.email; + const objectProperties = context.propsValue.objectProperties ?? {}; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const searchResponse = await client.crm.contacts.searchApi.doSearch({ + limit: 1, + filterGroups: [ + { filters: [{ propertyName: 'email', operator: FilterOperatorEnum.Eq, value: email }] }, + ], + }); + + if (searchResponse.results.length > 0) { + const updatedContact = await client.crm.contacts.basicApi.update( + searchResponse.results[0].id, + { + properties: contactProperties, + }, + ); + return updatedContact; + } else { + const createdContact = await client.crm.contacts.basicApi.create({ + properties: { ...contactProperties, email }, + }); + return createdContact; + } + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-product.ts b/packages/pieces/community/hubspot/src/lib/actions/create-product.ts new file mode 100644 index 0000000000..d081fc3552 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-product.ts @@ -0,0 +1,60 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const createProductAction = createAction({ + auth: hubspotAuth, + name: 'create-product', + displayName: 'Create Product', + description: 'Creates a product in Hubspot.', + props: { + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.PRODUCT,[]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const productProperties: Record = {}; + + // Add additional properties to the productProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + productProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdProduct = await client.crm.products.basicApi.create({ + properties: productProperties, + }); + // Retrieve default properties for the product and merge with additional properties to retrieve + const defaultproductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const productDetails = await client.crm.products.basicApi.getById(createdProduct.id, [ + ...defaultproductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts new file mode 100644 index 0000000000..e465cebb00 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/create-ticket.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, pipelineDropdown, pipelineStageDropdown, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const createTicketAction = createAction({ + auth: hubspotAuth, + name: 'create-ticket', + displayName: 'Create Ticket', + description: 'Creates a ticket in HubSpot.', + props: { + ticketName: Property.ShortText({ + displayName: 'Ticket Name', + description: 'The name of the ticket to create.', + required: true, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline', + required: true, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline Stage', + required: true, + }), + objectProperties : standardObjectDynamicProperties(OBJECT_TYPE.TICKET, [ + 'subject', + 'hs_pipeline', + 'hs_pipeline_stage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + ticketName, + pipelineId, + pipelineStageId, + } = context.propsValue; + const objectProperites = context.propsValue.objectProperties??{}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const ticketProperties: Record = { + subject: ticketName, + hs_pipeline: pipelineId!, + hs_pipeline_stage: pipelineStageId!, + }; + + // Add additional properties to the ticketProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + ticketProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdTicket = await client.crm.tickets.basicApi.create({ + properties: ticketProperties, + }); + + // Retrieve default properties for the ticket and merge with additional properties to retrieve + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const ticketDetails = await client.crm.tickets.basicApi.getById(createdTicket.id, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts new file mode 100644 index 0000000000..f153377938 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-associations.ts @@ -0,0 +1,56 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { fromObjectTypeAssociationDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; + +export const findAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'find-associations', + displayName: 'Find Associations', + description: 'Finds associations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object you want to search the association.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object you want to search the association.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: 'Type of the object the from object is being associated with.', + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + const results = []; + const limit = 100; + let after: string | undefined; + + do { + const reponse = await client.crm.associations.v4.basicApi.getPage( + fromObjectType as string, + fromObjectId as string, + toObjectType as string, + after, + limit, + ); + for(const association of reponse.results) { + results.push(association); + } + after = reponse.paging?.next?.after; + } while (after); + + return results; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-company.ts b/packages/pieces/community/hubspot/src/lib/actions/find-company.ts new file mode 100644 index 0000000000..795ba99cf6 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-company.ts @@ -0,0 +1,94 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +export const findCompanyAction = createAction({ + auth: hubspotAuth, + name: 'find-company', + displayName: 'Find Company', + description: 'Finds a company by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const response = await client.crm.companies.searchApi.doSearch({ + limit: 100, + properties: [...defaultCompanyProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts new file mode 100644 index 0000000000..a28769c399 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-contact.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findContactAction = createAction({ + auth: hubspotAuth, + name: 'find-contact', + displayName: 'Find Contact', + description: 'Finds a contact by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const response = client.crm.contacts.searchApi.doSearch({ + limit: 100, + properties: [...defaultContactProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts new file mode 100644 index 0000000000..986dacee88 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-custom-object.ts @@ -0,0 +1,98 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; +import { FilterOperatorEnum } from '../common/types'; + +export const findCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'find-custom-object', + displayName: 'Find Custom Object', + description: 'Finds a custom object by searching.', + props: { + customObjectType: customObjectDropdown, + firstSearchPropertyName: customObjectPropertiesDropdown( + 'First search property name', + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: customObjectPropertiesDropdown( + 'Second search property name', + false, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const { firstSearchPropertyValue, secondSearchPropertyValue } = context.propsValue; + const firstSearchPropertyName = context.propsValue.firstSearchPropertyName?.[ + 'values' + ] as string; + const secondSearchPropertyName = context.propsValue.secondSearchPropertyName?.[ + 'values' + ] as string; + + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const filters = [ + { + propertyName: firstSearchPropertyName as unknown as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as unknown as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 100, + properties: propertiesToRetrieve, + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts new file mode 100644 index 0000000000..14d30cab17 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-deal.ts @@ -0,0 +1,93 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { FilterOperatorEnum } from '../common/types'; +import { Client } from '@hubspot/api-client'; + +export const findDealAction = createAction({ + auth: hubspotAuth, + name: 'find-deal', + displayName: 'Find Deal', + description: 'Finds a deal by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const response = client.crm.deals.searchApi.doSearch({ + limit: 100, + properties: [...defaultDealProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts new file mode 100644 index 0000000000..7a5e78c9b8 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-line-item.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findLineItemAction = createAction({ + auth: hubspotAuth, + name: 'find-line-item', + displayName: 'Find Line Item', + description: 'Finds a line item by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const response = client.crm.lineItems.searchApi.doSearch({ + limit: 100, + properties: [...defaultLineItemProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-product.ts b/packages/pieces/community/hubspot/src/lib/actions/find-product.ts new file mode 100644 index 0000000000..313aca0671 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-product.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown + + } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +export const findProductAction = createAction({ + auth: hubspotAuth, + name: 'find-product', + displayName: 'Find Product', + description: 'Finds a product by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const response = await client.crm.products.searchApi.doSearch({ + limit: 100, + properties: [...defaultProductProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts new file mode 100644 index 0000000000..d4bbddd78f --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/find-ticket.ts @@ -0,0 +1,93 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { FilterOperatorEnum } from '../common/types'; + +export const findTicketAction = createAction({ + auth: hubspotAuth, + name: 'find-ticket', + displayName: 'Find Ticket', + description: 'Finds a ticket by searching.', + props: { + firstSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'First search property name', + required: true, + }, + true, + true, + ), + firstSearchPropertyValue: Property.ShortText({ + displayName: 'First search property value', + required: true, + }), + secondSearchPropertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'Second search property name', + required: false, + }, + true, + true, + ), + secondSearchPropertyValue: Property.ShortText({ + displayName: 'Second search property value', + required: false, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + firstSearchPropertyName, + firstSearchPropertyValue, + secondSearchPropertyName, + secondSearchPropertyValue, + } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const filters = [ + { + propertyName: firstSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: firstSearchPropertyValue, + }, + ]; + + if (secondSearchPropertyName && secondSearchPropertyValue) { + filters.push({ + propertyName: secondSearchPropertyName as string, + operator: FilterOperatorEnum.Eq, + value: secondSearchPropertyValue, + }); + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const response = client.crm.tickets.searchApi.doSearch({ + limit: 100, + properties: [...defaultTicketProperties, ...additionalPropertiesToRetrieve], + filterGroups: [{ filters }], + }); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-company.ts b/packages/pieces/community/hubspot/src/lib/actions/get-company.ts new file mode 100644 index 0000000000..0274948f79 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-company.ts @@ -0,0 +1,52 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; + +export const getCompanyAction = createAction({ + auth: hubspotAuth, + name: 'get-company', + displayName: 'Get Company', + description: 'Gets a company.', + props: { + companyId: Property.ShortText({ + displayName: 'Company ID', + description: 'The ID of the company to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { companyId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const client = new Client({ accessToken: context.auth.access_token }); + + const companyDetails = await client.crm.companies.basicApi.getById(companyId, [ + ...defaultCompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts new file mode 100644 index 0000000000..c65918be30 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-contact.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getContactAction = createAction({ + auth: hubspotAuth, + name: 'get-contact', + displayName: 'Get Contact', + description: 'Gets a contact.', + props: { + contactId: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { contactId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const client = new Client({ accessToken: context.auth.access_token }); + + const contactDetails = await client.crm.contacts.basicApi.getById(contactId, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts new file mode 100644 index 0000000000..89d4765317 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-custom-object.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; + +export const getCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'get-custom-object', + displayName: 'Get Custom Object', + description: 'Gets a custom object.', + props: { + customObjectType: customObjectDropdown, + customObjectId: Property.ShortText({ + displayName: 'Custom Object ID', + description: 'The ID of the custom object to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown('Additional Properties to Retrieve', false), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const customObjectId = context.propsValue.customObjectId as string; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const client = new Client({ accessToken: context.auth.access_token }); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + customObjectId, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts new file mode 100644 index 0000000000..adc4b1a18e --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-deal.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getDealAction = createAction({ + auth: hubspotAuth, + name: 'get-deal', + displayName: 'Get Deal', + description: 'Gets a deal.', + props: { + dealId: Property.ShortText({ + displayName: 'Deal ID', + description: 'The ID of the deal to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { dealId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const client = new Client({ accessToken: context.auth.access_token }); + + const dealDeatils = await client.crm.deals.basicApi.getById(dealId, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); + return dealDeatils; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts new file mode 100644 index 0000000000..a28860011e --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-line-item.ts @@ -0,0 +1,48 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; + +export const getLineItemAction = createAction({ + auth: hubspotAuth, + name: 'get-line-item', + displayName: 'Get Line Item', + description: 'Gets a line item.', + props: { + lineItemId: Property.ShortText({ + displayName: 'Line Item ID', + description: 'The ID of the line item to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { lineItemId } = context.propsValue; + + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const client = new Client({ accessToken: context.auth.access_token }); + + const lineItemDetails = await client.crm.lineItems.basicApi.getById(lineItemId, [ + ...defaultLineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts new file mode 100644 index 0000000000..c34b2df0c0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-email.ts @@ -0,0 +1,23 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; + +export const getOwnerByEmailAction = createAction({ + auth: hubspotAuth, + name: 'get-owner-by-email', + displayName: 'Get Owner by Email', + description: 'Gets an existing owner by email.', + props: { + email: Property.ShortText({ + displayName: 'Owner Email', + required: true, + }), + }, + async run(context) { + const { email } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.owners.ownersApi.getPage(email); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts new file mode 100644 index 0000000000..9be64f0bc6 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-owner-by-id.ts @@ -0,0 +1,23 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; + +export const getOwnerByIdAction = createAction({ + auth: hubspotAuth, + name: 'get-owner-by-id', + displayName: 'Get Owner by ID', + description: 'Gets an existing owner by ID.', + props: { + ownerId: Property.ShortText({ + displayName: 'Owner ID', + required: true, + }), + }, + async run(context) { + const { ownerId } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.owners.ownersApi.getById(Number(ownerId)); + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts b/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts new file mode 100644 index 0000000000..890d1417f7 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-pipeline-stage-details.ts @@ -0,0 +1,52 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +export const getPipelineStageDeatilsAction = createAction({ + auth: hubspotAuth, + name: 'get-pipeline-stage-details', + displayName: 'Get Pipeline Stage Details', + description: 'Finds and retrives CRM object pipeline stage details.', + props: { + objectType: Property.StaticDropdown({ + displayName: 'Object Type', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Tickets', + value: 'ticket', + }, + { + label: 'Deal', + value: 'deal', + }, + ], + }, + }), + pipelineId: Property.ShortText({ + displayName: 'Pipeline ID', + required: true, + }), + stageId: Property.ShortText({ + displayName: 'Stage ID', + required: true, + }), + }, + async run(context) { + const objectType = context.propsValue.objectType; + const pipelineId = context.propsValue.pipelineId; + const stageId = context.propsValue.stageId; + + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.crm.pipelines.pipelineStagesApi.getById( + objectType, + pipelineId, + stageId, + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-product.ts b/packages/pieces/community/hubspot/src/lib/actions/get-product.ts new file mode 100644 index 0000000000..3165917804 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-product.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const getProductAction = createAction({ + auth: hubspotAuth, + name: 'get-product', + displayName: 'Get Product', + description: 'Gets a product.', + props: { + productId: Property.ShortText({ + displayName: 'Product ID', + description: 'The ID of the product to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { productId } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const client = new Client({ accessToken: context.auth.access_token }); + + const productDetails = await client.crm.products.basicApi.getById(productId, [ + ...defaultProductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts new file mode 100644 index 0000000000..2c891b1fd1 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/get-ticket.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; + +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../../'; + +export const getTicketAction = createAction({ + auth: hubspotAuth, + name: 'get-ticket', + displayName: 'Get Ticket', + description: 'Gets a ticket.', + props: { + ticketId: Property.ShortText({ + displayName: 'Ticket ID', + description: 'The ID of the ticket to get.', + required: true, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { ticketId, } = context.propsValue; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const client = new Client({ accessToken: context.auth.access_token }); + + const ticketDeatils = await client.crm.tickets.basicApi.getById(ticketId, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDeatils; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts new file mode 100644 index 0000000000..798a94b9a9 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-associations.ts @@ -0,0 +1,108 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + fromObjectTypeAssociationDropdown, + associationTypeDropdown, + toObjectIdsDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { AssociationSpecAssociationCategoryEnum } from '../common/types'; + +export const removeAssociationsAction = createAction({ + auth: hubspotAuth, + name: 'remove-associations', + displayName: 'Remove Associations', + description: 'Removes assosiations between objects', + props: { + fromObjectId: Property.ShortText({ + displayName: 'From Object ID', + description: 'The ID of the object you want to remove the association from.', + required: true, + }), + fromObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'From Object Type', + required: true, + description: 'The type of the object you want to remove the association from.', + }), + toObjectType: fromObjectTypeAssociationDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object Type', + required: true, + description: "Type of the currently associated objects that you're removing the association from.", + }), + associationType: associationTypeDropdown, + toObjectIds: toObjectIdsDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'To Object IDs', + description: 'The IDs of the currently associated objects that you\'re removing the association from.', + required: true, + }), + }, + async run(context) { + const { fromObjectId, fromObjectType, toObjectType, associationType } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + if(context.propsValue.toObjectIds === undefined) { + throw new Error('Please provide To Object IDs'); + } + + let toObjectIds: any[]; + if (Array.isArray(context.propsValue.toObjectIds)) { + toObjectIds = context.propsValue.toObjectIds; + } else { + try { + toObjectIds = JSON.parse(context.propsValue.toObjectIds); + } catch { + throw new Error( + `Please provide To Object IDs in a valid format. Provided : ${JSON.stringify( + context.propsValue.toObjectIds, + )}`, + ); + } + } + + // find the association category + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + const association = associationLabels.results.find( + (associationLabel) => associationLabel.typeId === associationType, + ); + if (!association) { + throw new Error( + `Association type ${associationType} not found for ${fromObjectType} to ${toObjectType}`, + ); + } + const associationCategory = association.category; + + const response = await client.crm.associations.v4.batchApi.archiveLabels( + fromObjectType as string, + toObjectType as string, + { + inputs: toObjectIds.map((objectId) => { + return { + _from: { + id: fromObjectId, + }, + to: { + id: objectId, + }, + types: [ + { + associationCategory: + associationCategory as unknown as AssociationSpecAssociationCategoryEnum, + associationTypeId: associationType, + }, + ], + }; + }), + }, + ); + + return {success: true}; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts new file mode 100644 index 0000000000..489f9df163 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-contact-from-list.ts @@ -0,0 +1,51 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { staticListsDropdown } from '../common/props'; +import { HttpMethod } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum, HubSpotAddContactsToListResponse } from '../common/types'; + +export const removeContactFromListAction = createAction({ + auth: hubspotAuth, + name: 'remove-contact-from-list', + displayName: 'Remove Contact from List', + description: 'Remove a contact from a specific list.', + props: { + listId: staticListsDropdown, + email: Property.ShortText({ + displayName: 'Contact Email', + required: true, + }), + }, + async run(context) { + const { listId, email } = context.propsValue; + + const client = new Client({ accessToken: context.auth.access_token }); + + const contact = await client.crm.contacts.searchApi.doSearch({ + limit: 1, + filterGroups: [ + { filters: [{ propertyName: 'email', operator: FilterOperatorEnum.Eq, value: email }] }, + ], + }); + if (contact.results.length === 0) { + throw new Error( + `No contact with email '${email}' was found. Unable to remove unknown contact from list.`, + ); + } + const apiResponse = await client.apiRequest({ + path: `/contacts/v1/lists/${listId}/remove`, + method: HttpMethod.POST, + body: { vids: [contact.results[0].id] }, + defaultJson: true, + }); + + const parsedResponse =(await apiResponse.json()) as HubSpotAddContactsToListResponse; + if(parsedResponse.updated.length === 0) { + throw new Error( + `Contact with email '${email}' wasn't a member of list with id '${listId}'.`, + ); + } + return parsedResponse; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts b/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts new file mode 100644 index 0000000000..2ae777919c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/remove-email-subscription.ts @@ -0,0 +1,34 @@ +import { hubspotAuth } from '../../'; +import { AuthenticationType, httpClient, HttpMethod } from '@activepieces/pieces-common'; +import { createAction, Property } from '@activepieces/pieces-framework'; + +export const removeEmailSubscriptionAction = createAction({ + auth: hubspotAuth, + name: 'remove-email-subscription', + displayName: 'Remove Email Subscription', + description: 'Removes email subscription.', + props: { + email: Property.ShortText({ + displayName: 'Email', + required: true, + }), + }, + async run(context) { + const { email } = context.propsValue; + + // https://developers.hubspot.com/docs/reference/api/marketing/subscriptions-preferences/v1#update-email-subscription-status-for-an-email-address + const response = await httpClient.sendRequest({ + method: HttpMethod.PUT, + url: `https://api.hubapi.com/email/public/v1/subscriptions/${email}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: context.auth.access_token, + }, + body: { + unsubscribeFromAll: true, + }, + }); + + return response.body; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/search-owner-by-email.ts b/packages/pieces/community/hubspot/src/lib/actions/search-owner-by-email.ts deleted file mode 100644 index c5249e0b85..0000000000 --- a/packages/pieces/community/hubspot/src/lib/actions/search-owner-by-email.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { createAction, Property } from '@activepieces/pieces-framework'; -import { hubspotAuth } from '../../'; -import { hubSpotClient } from '../common/client'; - -export const hubSpotGetOwnerByEmailAction = createAction({ - auth: hubspotAuth, - name: 'get_owner_by_email', - displayName: 'Get Owner by Email', - description: 'Retrieves an existing owner by email.', - props: { - email: Property.ShortText({ - displayName: 'Owner Email', - required: true, - }), - }, - async run(context) { - const { email } = context.propsValue; - return await hubSpotClient.listContactOwners( - context.auth.access_token as string, - email as string - ); - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-company.ts b/packages/pieces/community/hubspot/src/lib/actions/update-company.ts new file mode 100644 index 0000000000..d06411d80e --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-company.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateCompanyAction = createAction({ + auth: hubspotAuth, + name: 'update-company', + displayName: 'Update Company', + description: 'Updates a company in Hubspot.', + props: { + companyId: Property.ShortText({ + displayName: 'Company ID', + description: 'The ID of the company to update.', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.COMPANY, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const companyId = context.propsValue.companyId; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const companyProperties: Record = {}; + + // Add additional properties to the companyProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + companyProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedCompany = await client.crm.companies.basicApi.update(companyId, { + properties: companyProperties, + }); + // Retrieve default properties for the comapny and merge with additional properties to retrieve + const defaultcompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + + const companyDetails = await client.crm.companies.basicApi.getById(updatedCompany.id, [ + ...defaultcompanyProperties, + ...additionalPropertiesToRetrieve, + ]); + + return companyDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts b/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts new file mode 100644 index 0000000000..75d1288c03 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-contact.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, + +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateContactAction = createAction({ + auth: hubspotAuth, + name: 'update-contact', + displayName: 'Update Contact', + description: 'Updates a contact in Hubspot.', + props: { + contactId: Property.ShortText({ + displayName: 'Contact ID', + description: 'The ID of the contact to update.', + required: true, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.CONTACT, []), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const {contactId} = context.propsValue; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const contactProperties: Record = {}; + + // Add additional properties to the contactProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + contactProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedContact = await client.crm.contacts.basicApi.update(contactId, { + properties: contactProperties, + }); + // Retrieve default properties for the contact and merge with additional properties to retrieve + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + + const contactDetails = await client.crm.contacts.basicApi.getById(updatedContact.id, [ + ...defaultContactProperties, + ...additionalPropertiesToRetrieve, + ]); + + return contactDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts b/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts new file mode 100644 index 0000000000..c92844157c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-custom-object.ts @@ -0,0 +1,80 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../..'; +import { + customObjectDropdown, + customObjectDynamicProperties, + customObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; + +export const updateCustomObjectAction = createAction({ + auth: hubspotAuth, + name: 'update-custome-object', + displayName: 'Update Custom Object', + description: 'Updates a custom object in Hubspot.', + props: { + customObjectType: customObjectDropdown, + customObjectId: Property.ShortText({ + displayName: 'Custom Object ID', + description: 'The ID of the custom object to update.', + required: true, + }), + objectProperties: customObjectDynamicProperties, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown('Additional Properties to Retrieve', false), + }, + async run(context) { + const customObjectType = context.propsValue.customObjectType as string; + const customObjectId = context.propsValue.customObjectId as string; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = + context.propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const customObjectProperties: Record = {}; + + // Add additional properties to the customObjectProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + customObjectProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedCustomObject = await client.crm.objects.basicApi.update( + customObjectType, + customObjectId, + { + properties: customObjectProperties, + }, + ); + + const customObjectDetails = await client.crm.objects.basicApi.getById( + customObjectType, + updatedCustomObject.id, + propertiesToRetrieve, + ); + + return customObjectDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts b/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts index c266f79745..30ad3973fc 100644 --- a/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts +++ b/packages/pieces/community/hubspot/src/lib/actions/update-deal.ts @@ -1,320 +1,103 @@ -import { - AuthenticationType, - HttpMethod, - HttpRequest, - httpClient, -} from '@activepieces/pieces-common'; - import { hubspotAuth } from '../../'; import { - DynamicPropsValue, - PiecePropValueSchema, Property, createAction, } from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; import { - ListDealPipelinesResponse, - ListOwnersResponse, - ListPipelineStagesResponse, - ListPropertiesResponse, - PropertyResponse, - SearchDealsResponse, -} from '../common/models'; + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; + +import { Client } from '@hubspot/api-client'; export const updateDealAction = createAction({ auth: hubspotAuth, - name: 'update_deal', + name: 'update-deal', displayName: 'Update Deal', - description: 'Updates an existing deal in hubspot.', + description: 'Updates a deal in HubSpot.', props: { - dealId: Property.Dropdown({ + dealId: Property.ShortText({ displayName: 'Deal ID', - refreshers: [], + description: 'The ID of the deal to update.', required: true, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const deals: { label: string; value: string }[] = []; - let after; - do { - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/objects/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - queryParams: after - ? { - limit: '100', - after: after, - } - : { limit: '100' }, - }; - const response = await httpClient.sendRequest(request); - deals.push( - ...response.body.results.map((deal) => { - return { - label: deal.properties['dealname'], - value: deal.id, - }; - }), - ); - after = response.body.paging?.next.after; - } while (after !== undefined); - - return { - disabled: false, - options: deals, - }; - }, }), dealname: Property.ShortText({ displayName: 'Deal Name', required: false, }), - pipelineId: Property.Dropdown({ + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, displayName: 'Deal Pipeline', - refreshers: [], - required: true, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/pipelines/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((pipeline) => { - return { - label: pipeline.label, - value: pipeline.id, - }; - }), - }; - }, + required: false, }), - dealstageId: Property.Dropdown({ + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, displayName: 'Deal Stage', - refreshers: ['pipelineId'], - required: true, - options: async ({ auth, pipelineId }) => { - if (!auth || !pipelineId) { - return { - disabled: true, - placeholder: 'Please connect your account first and select pipeline.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: `https://api.hubapi.com/crm/v3/pipelines/deals/${pipelineId}/stages`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((stage) => { - return { - label: stage.label, - value: stage.id, - }; - }), - }; - }, + required: false, }), - hubspot_owner_id: Property.Dropdown({ - displayName: 'Deal Owner', - refreshers: [], + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.DEAL, [ + 'dealname', + 'pipeline', + 'dealstage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', required: false, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/owners', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((owner) => { - return { - label: owner.email, - value: owner.id, - }; - }), - }; - }, }), - additionalFields: Property.DynamicProperties({ - displayName: 'Additional Fields', - refreshers: [], - required: true, - props: async ({ auth }) => { - if (!auth) return {}; + }, + async run(context) { + const { dealId, dealname, pipelineId, pipelineStageId } = context.propsValue; + const objectProperites = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; - const fields: DynamicPropsValue = {}; - const authValue = auth as PiecePropValueSchema; + const dealProperties: Record = {}; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/properties/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; + if (dealname) { + dealProperties['dealname'] = dealname; + } + if (pipelineId) { + dealProperties['pipeline'] = pipelineId; + } + if (pipelineStageId) { + dealProperties['dealstage'] = pipelineStageId; + } - const response = await httpClient.sendRequest(request); + // Add additional properties to the dealProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + dealProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); - for (const property of response.body.results) { - if (isRelevantProperty(property)) { - switch (property.fieldType) { - case 'booleancheckbox': - case 'radio': - case 'select': - fields[property.name] = Property.StaticDropdown({ - displayName: property.label, - description: property.description ?? '', - required: false, - options: { - disabled: false, - options: property.options.map((option) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - break; - case 'checkbox': - fields[property.name] = Property.StaticMultiSelectDropdown({ - displayName: property.label, - description: property.description ?? '', - required: false, - options: { - disabled: false, - options: property.options.map((option) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - break; - case 'date': - fields[property.name] = Property.DateTime({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'file': - fields[property.name] = Property.File({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'text': - case 'phonenumber': - case 'html': - fields[property.name] = Property.ShortText({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - case 'textarea': - fields[property.name] = Property.LongText({ - displayName: property.label, - description: property.description ?? '', - required: false, - }); - break; - default: - break; - } - } - } - return fields; - }, - }), - }, - async run(context) { - const dealId = context.propsValue.dealId; - const additionalFields = context.propsValue.additionalFields; - const properties: DynamicPropsValue = {}; - Object.entries(additionalFields).forEach(([key, value]) => { - if (Array.isArray(value)) { - properties[key] = value.join(';'); - } else { - properties[key] = value; - } + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedDeal = await client.crm.deals.basicApi.update(dealId, { + properties: dealProperties, }); - const request: HttpRequest = { - method: HttpMethod.PATCH, - url: `https://api.hubapi.com/crm/v3/objects/deals/${dealId}`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: context.auth.access_token, - }, - body: { - properties: { - dealname: context.propsValue.dealname, - pipeline: context.propsValue.pipelineId, - dealstage: context.propsValue.dealstageId, - hubspot_owner_id: context.propsValue.hubspot_owner_id, - ...properties, - }, - }, - }; - const response = await httpClient.sendRequest(request); - return response.body; + // Retrieve default properties for the deal and merge with additional properties to retrieve + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + + const dealDetails = await client.crm.deals.basicApi.getById(updatedDeal.id, [ + ...defaultDealProperties, + ...additionalPropertiesToRetrieve, + ]); + + return dealDetails; }, }); -function isRelevantProperty(property: PropertyResponse) { - return ( - !property.modificationMetadata.readOnlyValue && - !property.hidden && - !property.externalOptions && - !['dealname', 'pipeline', 'dealstage', 'hubspot_owner_id'].includes(property.name) - ); -} diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts b/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts new file mode 100644 index 0000000000..8f713ffcb0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-line-item.ts @@ -0,0 +1,81 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + productDropdown, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; + +import { Client } from '@hubspot/api-client'; + +export const updateLineItemAction = createAction({ + auth: hubspotAuth, + name: 'update-line-item', + displayName: 'Update Line Item', + description: 'Updates a line item in Hubspot.', + props: { + lineItemId: Property.ShortText({ + displayName: 'Line Item ID', + description: 'The ID of the line item to update.', + required: true, + }), + productId: productDropdown({ + displayName: 'Line Item Information: Product ID', + required: false, + objectType: OBJECT_TYPE.PRODUCT, + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.LINE_ITEM, ['hs_product_id']), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const lineItemId = context.propsValue.lineItemId; + const productId = context.propsValue.productId; + const objectProperites = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const lineItemProperties: Record = { + hs_product_id: productId!, + }; + + if(productId) + { + lineItemProperties['hs_product_id'] = productId; + } + + // Add additional properties to the lineItemProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + lineItemProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const createdLineItem = await client.crm.lineItems.basicApi.update(lineItemId, { + properties: lineItemProperties, + }); + // Retrieve default properties for the line item and merge with additional properties to retrieve + const defaultlineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + + const lineItemDeatils = await client.crm.lineItems.basicApi.getById(createdLineItem.id, [ + ...defaultlineItemProperties, + ...additionalPropertiesToRetrieve, + ]); + + return lineItemDeatils; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-product.ts b/packages/pieces/community/hubspot/src/lib/actions/update-product.ts new file mode 100644 index 0000000000..0f94bb7d5c --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-product.ts @@ -0,0 +1,67 @@ +import { hubspotAuth } from '../../'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { + getDefaultPropertiesForObject, + standardObjectDynamicProperties, + standardObjectPropertiesDropdown, + +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; + +export const updateProductAction = createAction({ + auth: hubspotAuth, + name: 'update-product', + displayName: 'Update Product', + description: 'Updates a product in Hubspot.', + props: { + productId:Property.ShortText({ + displayName:'Product ID', + description:'The ID of the product to update.', + required:true + }), + objectProperties: standardObjectDynamicProperties(OBJECT_TYPE.PRODUCT,[]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const productId = context.propsValue.productId; + const objectProperties = context.propsValue.objectProperties ?? {}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve ?? []; + + const productProperties: Record = {}; + + // Add additional properties to the productProperties object + Object.entries(objectProperties).forEach(([key, value]) => { + // Format values if they are arrays + productProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedProduct = await client.crm.products.basicApi.update(productId, { + properties: productProperties, + }); + // Retrieve default properties for the product and merge with additional properties to retrieve + const defaultproductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + + const productDetails = await client.crm.products.basicApi.getById(updatedProduct.id, [ + ...defaultproductProperties, + ...additionalPropertiesToRetrieve, + ]); + + return productDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts b/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts new file mode 100644 index 0000000000..e8a5d141c9 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/update-ticket.ts @@ -0,0 +1,102 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { getDefaultPropertiesForObject, pipelineDropdown, pipelineStageDropdown, standardObjectDynamicProperties, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; + +export const updateTicketAction = createAction({ + auth: hubspotAuth, + name: 'update-ticket', + displayName: 'Update Ticket', + description: 'Updates a ticket in HubSpot.', + props: { + ticketId: Property.ShortText({ + displayName: 'Ticket ID', + description: 'The ID of the ticket to update.', + required: true, + }), + ticketName: Property.ShortText({ + displayName: 'Ticket Name', + required: false, + }), + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline', + required: false, + }), + pipelineStageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Ticket Pipeline Stage', + required: false, + }), + objectProperites: standardObjectDynamicProperties(OBJECT_TYPE.TICKET, [ + 'subject', + 'hs_pipeline', + 'hs_pipeline_stage', + ]), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async run(context) { + const { + ticketId, + ticketName, + pipelineId, + pipelineStageId, + } = context.propsValue; + const objectProperites = context.propsValue.objectProperites??{}; + const additionalPropertiesToRetrieve = context.propsValue.additionalPropertiesToRetrieve??[]; + + + const ticketProperties: Record = { + }; + + if(ticketName) + { + ticketProperties['subject'] = ticketName; + } + if(pipelineId) + { + ticketProperties['hs_pipeline'] = pipelineId; + } + if(pipelineStageId) + { + ticketProperties['hs_pipeline_stage'] = pipelineStageId; + } + + // Add additional properties to the ticketProperties object + Object.entries(objectProperites).forEach(([key, value]) => { + // Format values if they are arrays + ticketProperties[key] = Array.isArray(value) ? value.join(';') : value; + }); + + const client = new Client({ accessToken: context.auth.access_token }); + + const updatedTicket = await client.crm.tickets.basicApi.update(ticketId,{ + properties: ticketProperties, + }); + + // Retrieve default properties for the ticket and merge with additional properties to retrieve + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + + const ticketDetails = await client.crm.tickets.basicApi.getById(updatedTicket.id, [ + ...defaultTicketProperties, + ...additionalPropertiesToRetrieve, + ]); + + return ticketDetails; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts b/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts new file mode 100644 index 0000000000..312f0cbda5 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/actions/upload-file.ts @@ -0,0 +1,106 @@ +import { hubspotAuth } from '../../'; +import { + createAction, + DropdownOption, + PiecePropValueSchema, + Property, +} from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; + +export const uploadFileAction = createAction({ + auth: hubspotAuth, + name: 'upload-file', + displayName: 'Upload File', + description: 'Uploads a file to HubSpot File Manager.', + props: { + folderId: Property.Dropdown({ + displayName: 'Folder', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + let after: string | undefined; + + do { + const response = await client.files.foldersApi.doSearch( + undefined, + after, + undefined, + limit, + ); + + for (const folder of response.results) { + options.push({ + value: folder.id, + label: folder.name ?? folder.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }), + fileName: Property.ShortText({ + displayName: 'File Name', + required: true, + }), + accessLevel: Property.StaticDropdown({ + displayName: 'Access Level', + required: true, + options: { + disabled: false, + options: [ + { + value: 'PUBLIC_INDEXABLE', + label: 'PUBLIC_INDEXABLE', + }, + { + value: 'PUBLIC_NOT_INDEXABLE', + label: 'PUBLIC_NOT_INDEXABLE', + }, + { label: 'PRIVATE', value: 'PRIVATE' }, + ], + }, + }), + file: Property.File({ + displayName: 'File', + required: true, + }), + }, + async run(context) { + const { accessLevel, fileName, folderId, file } = context.propsValue; + const client = new Client({ accessToken: context.auth.access_token }); + + const response = await client.files.filesApi.upload( + { + name: fileName, + data: file.data, + }, + folderId, + undefined, + fileName, + undefined, + JSON.stringify({ + access: accessLevel, + }), + ); + + return response; + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/common/client.ts b/packages/pieces/community/hubspot/src/lib/common/client.ts deleted file mode 100644 index 8e0d4569a4..0000000000 --- a/packages/pieces/community/hubspot/src/lib/common/client.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { - AuthenticationType, - HttpMethod, - HttpRequest, - httpClient, -} from '@activepieces/pieces-common'; -import { - Contact, - HubSpotAddContactsToListRequest, - HubSpotAddContactsToListResponse, - HubSpotContactsCreateOrUpdateResponse, - HubSpotListsResponse, - HubSpotRequest, -} from './models'; - -const API = 'https://api.hubapi.com'; - -export const hubSpotClient = { - contacts: { - async createOrUpdate({ - token, - email, - contact, - }: ContactsCreateOrUpdateParams): Promise { - const properties = Object.entries(contact).map(([property, value]) => ({ - property, - value, - })); - - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/contacts/v1/contact/createOrUpdate/email/${email}`, - body: { - properties, - }, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, - - lists: { - async getStaticLists({ token }: GetStaticListsParams): Promise { - const request: HttpRequest = { - method: HttpMethod.GET, - url: `${API}/contacts/v1/lists/static`, - queryParams: { - count: '250', - offset: '0', - }, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - - async addContact({ - token, - listId, - email, - }: ListsAddContactParams): Promise { - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/contacts/v1/lists/${listId}/add`, - body: { - emails: [email], - }, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, - - tasks: { - async getTasksAfterLastSearch(accessToken: string, lastFetchEpochMS: number) { - const request: HttpRequest = { - method: HttpMethod.POST, - url: `${API}/crm/v3/objects/tasks/search`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - body: { - filters: [ - { - propertyName: 'hs_createdate', - operator: 'GT', - value: lastFetchEpochMS, - }, - ], - properties: [ - 'hs_task_body', - 'hubspot_owner_id', - 'hs_task_subject', - 'hs_task_status', - 'hs_task_priority', - 'hs_task_type', - 'hs_created_by', - 'hs_created_by_user_id', - 'hs_modified_by', - 'hs_num_associated_companies', - 'hs_num_associated_contacts', - 'hs_num_associated_deals', - 'hs_num_associated_tickets', - 'hs_product_name', - 'hs_read_only', - 'hs_repeat_status', - 'hs_task_completion_count', - 'hs_task_completion_date', - 'hs_task_is_all_day', - 'hs_task_is_completed', - 'hs_task_is_completed_call', - 'hs_task_is_completed_email', - 'hs_task_is_completed_linked_in', - 'hs_task_is_completed_sequence', - 'hs_task_repeat_interval', - 'hs_updated_by_user_id', - 'hs_timestamp', - ], - limit: 100, - }, - }; - - const response = await httpClient.sendRequest(request); - return response.body; - }, - }, - - async searchCompanies( - accessToken: string, - filters?: { - createdAt?: number; - createdAtOperator?: string; - }, - ) { - const searchParams = []; - - if (filters && filters.createdAt) { - searchParams.push({ - propertyName: 'createdate', - operator: filters.createdAtOperator ?? 'GT', - value: filters.createdAt, - }); - } - - const response = await httpClient.sendRequest({ - method: HttpMethod.POST, - url: `${API}/crm/v3/objects/companies/search`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - body: { - filters: searchParams, - }, - }); - - return response.body; - }, - - async searchContacts( - accessToken: string, - wantedFields: string[], - filters?: { - createdAt?: number; - createdAtOperator?: string; - }, - ) { - const searchParams = []; - - if (filters && filters.createdAt) { - searchParams.push({ - propertyName: 'createdate', - operator: filters.createdAtOperator ?? 'GT', - value: filters.createdAt, - }); - } - const requestBody: Record = { - filters: searchParams, - properties: wantedFields, - }; - - const response = await httpClient.sendRequest({ - method: HttpMethod.POST, - url: `${API}/crm/v3/objects/contacts/search`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - body: requestBody, - }); - - return response.body; - }, - - async searchDeals( - accessToken: string, - filters?: { - createdAt?: number; - createdAtOperator?: string; - }, - ) { - const searchParams = []; - - if (filters && filters.createdAt) { - searchParams.push({ - propertyName: 'createdate', - operator: filters.createdAtOperator ?? 'GT', - value: filters.createdAt, - }); - } - - const response = await httpClient.sendRequest({ - method: HttpMethod.POST, - url: `${API}/crm/v3/objects/deals/search`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - body: { - filters: searchParams, - }, - }); - - return response.body; - }, - - async searchTickets( - accessToken: string, - filters?: { - createdAt?: number; - createdAtOperator?: string; - }, - ) { - const searchParams = []; - - if (filters && filters.createdAt) { - searchParams.push({ - propertyName: 'createdate', - operator: filters.createdAtOperator ?? 'GT', - value: filters.createdAt, - }); - } - - const response = await httpClient.sendRequest({ - method: HttpMethod.POST, - url: `${API}/crm/v3/objects/tickets/search`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - body: { - filters: searchParams, - }, - }); - - return response.body; - }, - async listContactOwners(accessToken: string, email?: string) { - const response = await httpClient.sendRequest({ - method: HttpMethod.GET, - url: `${API}/crm/v3/owners`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - headers: { - 'Content-Type': 'application/json', - }, - queryParams: { - email: email!, - }, - }); - return response.body; - }, -}; - -type ContactsCreateOrUpdateParams = { - token: string; - email: string; - contact: Partial; -}; - -type GetStaticListsParams = { - token: string; -}; - -type ListsAddContactParams = { - token: string; - listId: number; - email: string; -}; diff --git a/packages/pieces/community/hubspot/src/lib/common/constants.ts b/packages/pieces/community/hubspot/src/lib/common/constants.ts new file mode 100644 index 0000000000..54f02d75cd --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/common/constants.ts @@ -0,0 +1,177 @@ +export const enum OBJECT_TYPE { + CONTACT = 'contact', + COMPANY = 'company', + DEAL = 'deal', + TICKET = 'ticket', + PRODUCT = 'product', + LINE_ITEM = 'line_item', + TASK = 'task', +} + +export const DEFAULT_CONTACT_PROPERTIES = [ + 'firstname', + 'lastname', + 'email', + 'company', + 'website', + 'mobilephone', + 'phone', + 'fax', + 'address', + 'city', + 'state', + 'zip', + 'salutation', + 'country', + 'jobtitle', + 'hs_createdate', + 'hs_email_domain', + 'hs_object_id', + 'lastmodifieddate', + 'hs_persona', + 'hs_language', + 'lifecyclestage', + 'createdate', + 'numemployees', + 'annualrevenue', + 'industry', +]; + +export const DEFAULT_DEAL_PROPERTIES = [ + 'dealtype', + 'dealname', + 'amount', + 'description', + 'closedate', + 'createdate', + 'num_associated_contacts', + 'hs_forecast_amount', + 'hs_forecast_probability', + 'hs_manual_forecast_category', + 'hs_next_step', + 'hs_object_id', + 'hs_lastmodifieddate', + 'hubspot_owner_id', + 'hubspot_team_id', +]; + +export const DEFAULT_TICKET_PROPERTIES = [ + 'subject', + 'content', + 'source_type', + 'createdate', + 'hs_pipeline', + 'hs_pipeline_stage', + 'hs_resolution', + 'hs_ticket_category', + 'hs_ticket_id', + 'hs_ticket_priority', + 'hs_lastmodifieddate', + 'hubspot_owner_id', + 'hubspot_team_id', +]; + +export const DEFAULT_COMPANY_PROPERTIES = [ + 'name', + 'domain', + 'industry', + 'about_us', + 'phone', + 'address', + 'address2', + 'city', + 'state', + 'zip', + 'country', + 'website', + 'type', + 'description', + 'founded_year', + 'hs_createdate', + 'hs_lastmodifieddate', + 'hs_object_id', + 'is_public', + 'timezone', + 'total_money_raised', + 'total_revenue', + 'owneremail', + 'ownername', + 'numberofemployees', + 'annualrevenue', + 'lifecyclestage', + 'createdate', + 'web_technologies', +]; + +export const DEFAULT_PRODUCT_PROPERTIES = [ + 'createdate', + 'description', + 'name', + 'price', + 'tax', + 'hs_lastmodifieddate', +]; + +export const DEFAULT_LINE_ITEM_PROPERTIES = [ + 'name', + 'description', + 'price', + 'quantity', + 'amount', + 'discount', + 'tax', + 'createdate', + 'hs_object_id', + 'hs_product_id', + 'hs_images', + 'hs_lastmodifieddate', + 'hs_line_item_currency_code', + 'hs_sku', + 'hs_url', + 'hs_cost_of_goods_sold', + 'hs_discount_percentage', + 'hs_term_in_months', +]; + + + +export const DEFAULT_TASK_PROPERTIES = [ + 'hs_task_body', + 'hubspot_owner_id', + 'hs_task_subject', + 'hs_task_status', + 'hs_task_priority', + 'hs_task_type', + 'hs_created_by', + 'hs_repeat_status', + 'hs_task_completion_date', + 'hs_task_is_completed', + 'hs_timestamp', + 'hs_queue_membership_ids', + 'hs_lastmodifieddate', + 'hs_createdate', +]; + +export const STANDARD_OBJECT_TYPES = [ + { + label: 'Contacts', + value: OBJECT_TYPE.CONTACT, + }, + { + label: 'Companies', + value: OBJECT_TYPE.COMPANY, + }, + { + label: 'Deals', + value: OBJECT_TYPE.DEAL, + }, + { + label: 'Tickets', + value: OBJECT_TYPE.TICKET, + }, + { + label: 'Line Items', + value: OBJECT_TYPE.LINE_ITEM, + }, +]; + diff --git a/packages/pieces/community/hubspot/src/lib/common/index.ts b/packages/pieces/community/hubspot/src/lib/common/index.ts deleted file mode 100644 index 4f99b071c2..0000000000 --- a/packages/pieces/community/hubspot/src/lib/common/index.ts +++ /dev/null @@ -1,227 +0,0 @@ -import { - AuthenticationType, - HttpMethod, - HttpRequest, - httpClient, -} from '@activepieces/pieces-common'; -import { - CheckboxProperty, - DateTimeProperty, - FileProperty, - LongTextProperty, - NumberProperty, - OAuth2PropertyValue, - Property, - ShortTextProperty, -} from '@activepieces/pieces-framework'; -import { hubSpotClient } from './client'; - -enum HubspotFieldType { - BooleanCheckBox = 'booleancheckbox', - Date = 'date', - File = 'file', - Number = 'number', - CalculationEquation = 'calculation_equation', - PhoneNumber = 'phonenumber', - Text = 'text', - TextArea = 'textarea', - Html = 'html', - CheckBox = 'checkbox', - Select = 'select', - Radio = 'radio', -} - -interface ContactProperty { - name: string; - label: string; - description: string; - type: string; - fieldType: HubspotFieldType; - options: []; -} - -type DynamicPropsValue = - | ShortTextProperty - | LongTextProperty - | CheckboxProperty - | DateTimeProperty - | FileProperty - | NumberProperty; - -export const hubspotCommon = { - choose_props: Property.MultiSelectDropdown({ - displayName: 'Properties', - description: 'Choose extra properties to add to the contact', - required: false, - refreshers: ['auth'], - options: async ({ auth }) => { - const connection = auth as OAuth2PropertyValue; - if (!connection) { - return { - disabled: true, - options: [], - placeholder: - 'please authenticate your account first before selecting properties', - }; - } - try { - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/properties/v1/contacts/properties', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: connection.access_token, - }, - }; - const result = await httpClient.sendRequest(request); - - const properties = result.body.map((property: any) => { - return { - label: property.label, - value: property, - }; - }); - - return { - disabled: false, - options: properties, - }; - } catch (error) { - return { - disabled: true, - options: [], - placeholder: 'An error occurred while fetching properties', - }; - } - }, - }), - dynamicProperties: Property.DynamicProperties({ - displayName: 'Dynamic Properties', - description: 'Extra properties to add to the contact', - required: false, - refreshers: ['choose_props'], - props: async ({ auth, choose_props }) => { - const all_props = choose_props as ContactProperty[]; - - if (!all_props) { - return {}; - } - - const fields: any = {}; - - for (const prop of all_props) { - switch (prop.fieldType) { - case HubspotFieldType.BooleanCheckBox: - fields[prop.name] = Property.Checkbox({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.Date: - fields[prop.name] = Property.DateTime({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.File: - fields[prop.name] = Property.File({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.Number: - fields[prop.name] = Property.Number({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.CalculationEquation: - case HubspotFieldType.PhoneNumber: - case HubspotFieldType.Text: - fields[prop.name] = Property.ShortText({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.TextArea: - case HubspotFieldType.Html: - fields[prop.name] = Property.LongText({ - displayName: prop.label, - description: prop.description, - required: false, - }); - break; - case HubspotFieldType.CheckBox: - fields[prop.name] = Property.StaticMultiSelectDropdown({ - displayName: prop.label, - description: prop.description, - required: false, - options: { - options: prop.options.map((option: any) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - break; - case HubspotFieldType.Select: - case HubspotFieldType.Radio: - if (prop.name === 'hubspot_owner_id') { - try { - const res = - ( - await hubSpotClient.listContactOwners( - auth.access_token as string - ) - ).results ?? []; - fields[prop.name] = Property.StaticDropdown({ - displayName: prop.label, - description: prop.description, - required: false, - options: { - options: res.map((owner: { id: string; email: string }) => { - return { - label: owner.email, - value: owner.id, - }; - }), - }, - }); - } catch (error) { - return { - disabled: true, - options: [], - placeholder: - 'An error occurred while fetching contact owner list.', - }; - } - } else { - fields[prop.name] = Property.StaticDropdown({ - displayName: prop.label, - description: prop.description, - required: false, - options: { - options: prop.options.map((option: any) => { - return { - label: option.label, - value: option.value, - }; - }), - }, - }); - } - break; - } - } - - return fields; - }, - }), -}; diff --git a/packages/pieces/community/hubspot/src/lib/common/models.ts b/packages/pieces/community/hubspot/src/lib/common/models.ts deleted file mode 100644 index 31be55be4e..0000000000 --- a/packages/pieces/community/hubspot/src/lib/common/models.ts +++ /dev/null @@ -1,133 +0,0 @@ -export type Contact = { - email: string; - firstname: string; - lastname: string; - website: string; - company: string; - phone: string; - address: string; - city: string; - state: string; - zip: string; -}; - -export type RequestProperty = { - property: string; - value: string; -}; - -export type HubSpotRequest = { - properties: RequestProperty[]; -}; - -export type HubSpotContactsCreateOrUpdateResponse = { - vid: string; - isNew: boolean; -}; - -export type HubSpotList = { - listId: number; - name: string; -}; - -export type HubSpotListsResponse = { - lists: HubSpotList[]; -}; - -export type HubSpotAddContactsToListResponse = { - updated: number[]; - discarded: number[]; - invalidVids: number[]; - invalidEmails: string[]; -}; - -export type HubSpotAddContactsToListRequest = { - emails: string[]; -}; -export type OwnerResponse = { - id: string; - email: string; - firstName: string; - lastName: string; - userId: number; - createdAt: string; - updatedAt: string; - archived: boolean; -}; -export type DealStageResponse = { - label: string; - displayOrder: number; - id: string; - createdAt: string; - updatedAt: string; - archived: boolean; -}; -export type PropertyResponse = { - updatedAt: string; - createdAt: string; - name: string; - label: string; - type: string; - fieldType: string; - description: string; - groupName: string; - options: Array<{ - label: string; - value: string; - displayOrder: number; - hidden: boolean; - }>; - displayOrder: number; - calculated: boolean; - externalOptions: boolean; - hasUniqueValue: boolean; - hidden: boolean; - hubspotDefined: boolean; - modificationMetadata: { - archivable: boolean; - readOnlyDefinition: boolean; - readOnlyOptions: boolean; - readOnlyValue: boolean; - }; - formField: boolean; -}; -export type DealPipelineResponse = { - label: string; - displayOrder: number; - id: string; - stages: Array; - createdAt: string; - updatedAt: string; - archived: boolean; -}; - -export type ListDealPipelinesResponse = { - results: Array; -}; -export type ListPipelineStagesResponse = { - results: Array; -}; -export type ListOwnersResponse = { - results: Array; -}; - -export type ListPropertiesResponse = { - results: Array; -}; - -export type SearchDealsResponse = { - total: number; - results: Array<{ - id: string; - createdAt: string; - updatedAt: string; - archived: boolean; - properties: Record; - }>; - paging?: { - next: { - link: string; - after: string; - }; - }; -}; diff --git a/packages/pieces/community/hubspot/src/lib/common/props.ts b/packages/pieces/community/hubspot/src/lib/common/props.ts index f8cf2018da..6725e38012 100644 --- a/packages/pieces/community/hubspot/src/lib/common/props.ts +++ b/packages/pieces/community/hubspot/src/lib/common/props.ts @@ -1,63 +1,950 @@ import { - OAuth2PropertyValue, - PieceAuth, - Property, + DropdownOption, + DynamicPropsValue, + OAuth2PropertyValue, + PiecePropValueSchema, + Property, } from '@activepieces/pieces-framework'; -import { hubSpotClient } from './client'; - -export const hubSpotAuthentication = PieceAuth.OAuth2({ - authUrl: 'https://app.hubspot.com/oauth/authorize', - tokenUrl: 'https://api.hubapi.com/oauth/v1/token', - required: true, - scope: [ - 'crm.lists.read', - 'crm.lists.write', - 'crm.objects.contacts.read', - 'crm.objects.contacts.write', - 'crm.objects.companies.read', - 'crm.objects.deals.read', - 'tickets', - 'forms', - ], -}); +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { WorkflowResponse, HubspotProperty, HubspotFieldType, ListBlogsResponse } from './types'; +import { + DEFAULT_COMPANY_PROPERTIES, + DEFAULT_CONTACT_PROPERTIES, + DEFAULT_DEAL_PROPERTIES, + DEFAULT_LINE_ITEM_PROPERTIES, + DEFAULT_PRODUCT_PROPERTIES, + DEFAULT_TICKET_PROPERTIES, + DEFAULT_TASK_PROPERTIES, + OBJECT_TYPE, + STANDARD_OBJECT_TYPES, +} from './constants'; +import { Client } from '@hubspot/api-client'; +import { hubspotAuth } from '../../'; const buildEmptyList = ({ placeholder }: { placeholder: string }) => { - return { - disabled: true, - options: [], - placeholder, - }; + return { + disabled: true, + options: [], + placeholder, + }; +}; + +export function getDefaultPropertiesForObject(objectType: OBJECT_TYPE): string[] { + switch (objectType) { + case OBJECT_TYPE.CONTACT: + return DEFAULT_CONTACT_PROPERTIES; + case OBJECT_TYPE.DEAL: + return DEFAULT_DEAL_PROPERTIES; + case OBJECT_TYPE.TICKET: + return DEFAULT_TICKET_PROPERTIES; + case OBJECT_TYPE.COMPANY: + return DEFAULT_COMPANY_PROPERTIES; + case OBJECT_TYPE.PRODUCT: + return DEFAULT_PRODUCT_PROPERTIES; + case OBJECT_TYPE.LINE_ITEM: + return DEFAULT_LINE_ITEM_PROPERTIES; + case OBJECT_TYPE.TASK: + return DEFAULT_TASK_PROPERTIES; + default: + return []; + } +} + +async function fetchOwnersOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.crm.owners.ownersApi.getPage(undefined, after, limit); + for (const owner of response.results) + if (owner.email) { + options.push({ + label: owner.email, + value: owner.id, + }); + } + after = response.paging?.next?.after; + } while (after); + return options; +} + +async function fetchUsersOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.settings.users.usersApi.getPage(limit, after); + for (const user of response.results) { + if (user.email) { + options.push({ + label: user.email, + value: user.id, + }); + } + } + after = response.paging?.next?.after; + } while (after); + return options; +} + +async function fetchTeamsOptions(accessToken: string): Promise[]> { + const client = new Client({ accessToken: accessToken }); + const options: DropdownOption[] = []; + + const response = await client.settings.users.teamsApi.getAll(); + for (const team of response.results) { + if (team.name) { + options.push({ + label: team.name, + value: team.id, + }); + } + } + return options; +} + +async function fetchCurrenciesOptions(accessToken: string): Promise[]> { + const options: DropdownOption[] = []; + + const response = await httpClient.sendRequest<{ + results: Array<{ currencyCode: string; currencyName: string }>; + }>({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/settings/v3/currencies/codes', + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + }); + for (const currency of response.body.results) { + options.push({ + label: currency.currencyName, + value: currency.currencyCode, + }); + } + return options; +} + +// async function fetchBusinessUnitsOptions(accessToken: string): Promise[]> { +// const client = new Client({ accessToken: accessToken }); +// const options: DropdownOption[] = []; + +// const response = await client.settings.businessUnits.businessUnitApi.getByUserID() +// for (const businessUnit of response.results) { +// if (businessUnit.name) { +// options.push({ +// label: businessUnit.name, +// value: businessUnit.id, +// }); +// } +// } +// return options; +// } + +async function createReferencedPropertyDefinition( + property: HubspotProperty, + propertyDisplayName: string, + accessToken: string, +) { + let options: DropdownOption[] = []; + + switch (property.referencedObjectType) { + case 'OWNER': + options = await fetchOwnersOptions(accessToken); + break; + default: + return null; + } + + return Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options, + }, + }); +} + +function createPropertyDefinition(property: HubspotProperty, propertyDisplayName: string) { + switch (property.fieldType) { + case HubspotFieldType.BooleanCheckBox: + return Property.Checkbox({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.Date: + return Property.DateTime({ + displayName: propertyDisplayName, + description: property.type === 'date' ? 'Provide date in YYYY-MM-DD format' : '', + required: false, + }); + case HubspotFieldType.Number: + return Property.Number({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.PhoneNumber: + case HubspotFieldType.Text: + return Property.ShortText({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.TextArea: + case HubspotFieldType.Html: + return Property.LongText({ + displayName: propertyDisplayName, + required: false, + }); + case HubspotFieldType.CheckBox: + return Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.value, + }; + }) + : [], + }, + }); + case HubspotFieldType.Select: + case HubspotFieldType.Radio: + return Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + options: property.options + ? property.options.map((option) => { + return { + label: option.label, + value: option.value, + }; + }) + : [], + }, + }); + default: + return null; + } +} + +async function retriveObjectProperties( + auth: PiecePropValueSchema, + objectType: string, + excludedProperties: string[] = [], +) { + const client = new Client({ accessToken: auth.access_token }); + + // Fetch property groups + const propertyGroups = await client.crm.properties.groupsApi.getAll(objectType); + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll(objectType); + + const props: DynamicPropsValue = {}; + + for (const property of allProperties.results) { + // skip read only properties + if ( + excludedProperties.includes(property.name) || + property.modificationMetadata?.readOnlyValue || + property.hidden + ) { + continue; + } + + // create property name with property group name + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + + if (property.referencedObjectType) { + props[property.name] = await createReferencedPropertyDefinition( + property, + propertyDisplayName, + auth.access_token, + ); + continue; + } + if (property.name === 'hs_shared_user_ids') { + const userOptions = await fetchUsersOptions(auth.access_token); + props[property.name] = Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: userOptions, + }, + }); + continue; + } + if (['hs_shared_team_ids', 'hs_attributed_team_ids'].includes(property.name)) { + const teamOptions = await fetchTeamsOptions(auth.access_token); + props[property.name] = Property.StaticMultiSelectDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: teamOptions, + }, + }); + continue; + } + if (property.name === 'deal_currency_code') { + const currencyOptions = await fetchCurrenciesOptions(auth.access_token); + props[property.name] = Property.StaticDropdown({ + displayName: propertyDisplayName, + required: false, + options: { + disabled: false, + options: currencyOptions, + }, + }); + continue; + } + if (property.name === 'hs_all_assigned_business_unit_ids') { + // TO DO : Add business unit options + // const businessUnitOptions = await fetchBusinessUnitsOptions(authValue.access_token); + // props[property.name] = Property.StaticMultiSelectDropdown({ + // displayName: propertyDisplayName, + // required: false, + // options: { + // disabled: false, + // options: businessUnitOptions, + // }, + // }); + continue; + } + props[property.name] = createPropertyDefinition(property, propertyDisplayName); + } + // Remove null props + return Object.fromEntries(Object.entries(props).filter(([_, prop]) => prop !== null)); +} + +export const standardObjectDynamicProperties = (objectType: string, excludedProperties: string[]) => + Property.DynamicProperties({ + displayName: 'Object Properties', + refreshers: [], + required: false, + props: async ({ auth }) => { + if (!auth) return {}; + // Useful for Find actions + // if (typeof createIfNotExists === "boolean" && createIfNotExists === false) { + // return {}; + // } + const authValue = auth as PiecePropValueSchema; + return await retriveObjectProperties(authValue, objectType, excludedProperties); + }, + }); + +export const customObjectDynamicProperties = Property.DynamicProperties({ + displayName: 'Custom Object Properties', + refreshers: ['customObjectType'], + required: false, + props: async ({ auth, customObjectType }) => { + if (!auth || !customObjectType) { + return {}; + } + const authValue = auth as PiecePropValueSchema; + return await retriveObjectProperties(authValue, customObjectType as unknown as string); + }, +}); + +export const standardObjectPropertiesDropdown = ( + params: DropdownParams, + includeDefaultProperties = false, + isSingleSelect = false, +) => { + const dropdownFunction = isSingleSelect ? Property.Dropdown : Property.MultiSelectDropdown; + return dropdownFunction({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll(params.objectType); + + const propertyGroups = await client.crm.properties.groupsApi.getAll(params.objectType); + + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + const defaultProperties = includeDefaultProperties + ? [] + : getDefaultPropertiesForObject(params.objectType); + + // Filter and create options for properties that are not default + const options: DropdownOption[] = []; + for (const property of allProperties.results) { + if (!includeDefaultProperties && defaultProperties.includes(property.name)) { + continue; + } + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + options.push({ + label: propertyDisplayName, + value: property.name, + }); + } + + return { + disabled: false, + options, + }; + }, + }); }; -export const hubSpotListIdDropdown = Property.Dropdown({ - displayName: 'List', - refreshers: [], - description: 'List to add contact to', - required: true, - options: async ({ auth }) => { - if (!auth) { - return buildEmptyList({ - placeholder: 'Please select an authentication', - }); - } - - const token = (auth as OAuth2PropertyValue).access_token; - const listsResponse = await hubSpotClient.lists.getStaticLists({ token }); - - if (listsResponse.lists.length === 0) { - return buildEmptyList({ - placeholder: 'No lists found! Please create a list.', - }); - } - - const options = listsResponse.lists.map((list) => ({ - label: list.name, - value: list.listId, - })); - - return { - disabled: false, - options, - }; - }, +export const customObjectPropertiesDropdown = ( + displayName: string, + required: boolean, + isSingleSelect = false, +) => + Property.DynamicProperties({ + displayName, + refreshers: ['customObjectType'], + required, + props: async ({ auth, customObjectType }) => { + if (!auth || !customObjectType) { + return {}; + } + const authValue = auth as PiecePropValueSchema; + + const client = new Client({ accessToken: authValue.access_token }); + + // Fetch all properties for the given object type + const allProperties = await client.crm.properties.coreApi.getAll( + customObjectType as unknown as string, + ); + + const propertyGroups = await client.crm.properties.groupsApi.getAll( + customObjectType as unknown as string, + ); + + const groupLabels = propertyGroups.results.reduce((map, group) => { + map[group.name] = group.label; + return map; + }, {} as Record); + + const options: DropdownOption[] = []; + for (const property of allProperties.results) { + const propertyDisplayName = `${groupLabels[property.groupName] || ''}: ${property.label}`; + options.push({ + label: propertyDisplayName, + value: property.name, + }); + } + + const props: DynamicPropsValue = {}; + const dropdownFunction = isSingleSelect + ? Property.StaticDropdown + : Property.StaticMultiSelectDropdown; + + props['values'] = dropdownFunction({ + displayName, + required, + options: { + disabled: false, + options, + }, + }); + return props; + }, + }); + +export const workflowIdDropdown = Property.Dropdown({ + displayName: 'Workflow', + refreshers: [], + // description: 'Workflow to add contact to', + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const token = (auth as OAuth2PropertyValue).access_token; + const workflowsResponse = await httpClient.sendRequest<{ + workflows: WorkflowResponse[]; + }>({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/automation/v2/workflows`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: token, + }, + }); + const options: DropdownOption[] = []; + + for (const workflow of workflowsResponse.body.workflows) { + if (workflow.enabled) { + options.push({ + label: workflow.name, + value: workflow.id, + }); + } + } + + return { + disabled: false, + options, + }; + }, }); + +export const pipelineDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const pipelinesResponse = await client.crm.pipelines.pipelinesApi.getAll(params.objectType); + + const options = pipelinesResponse.results.map((pipeline) => { + return { + label: pipeline.label, + value: pipeline.id, + }; + }); + return { + disabled: false, + options, + }; + }, + }); + +export const pipelineStageDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: ['pipelineId'], + required: params.required, + description: params.description, + options: async ({ auth, pipelineId }) => { + if (!auth || !pipelineId) { + return buildEmptyList({ + placeholder: 'Please connect your account and select a pipeline.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const pipelineStagesResponse = await client.crm.pipelines.pipelineStagesApi.getAll( + params.objectType, + pipelineId as string, + ); + + const options = pipelineStagesResponse.results.map((stage) => { + return { + label: stage.label, + value: stage.id, + }; + }); + + return { + disabled: false, + options, + }; + }, + }); + +export const productDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const options: DropdownOption[] = []; + + const limit = 100; + let after: string | undefined; + do { + const response = await client.crm.products.basicApi.getPage(limit, after, ['name']); + for (const product of response.results) { + options.push({ + label: product.properties.name ?? product.id, + value: product.id, + }); + } + + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }); +export const customObjectDropdown = Property.Dropdown({ + displayName: 'Type of Custom Object', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const customObjectsResponse = await client.crm.schemas.coreApi.getAll(); + + const options = customObjectsResponse.results.map((customObj) => { + return { + label: customObj.labels.plural ?? customObj.name, + value: customObj.objectTypeId, + }; + }); + + return { + disabled: false, + options, + }; + }, +}); + +export const staticListsDropdown = Property.Dropdown({ + displayName: 'List ID', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const options: DropdownOption[] = []; + + let offset = 0; + let hasMore = true; + do { + const request: HttpRequest = { + url: 'https://api.hubapi.com/contacts/v1/lists/static', + method: HttpMethod.GET, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + queryParams: { + count: '100', + offset: offset.toString(), + }, + }; + const response = await httpClient.sendRequest<{ + total: number; + offset: number; + 'has-more': boolean; + lists: Array<{ name: string; listId: number }>; + }>(request); + + for (const list of response.body.lists) { + options.push({ + label: list.name, + value: list.listId, + }); + } + offset += 100; + hasMore = response.body['has-more']; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, +}); + +export const fromObjectTypeAssociationDropdown = (params: DropdownParams) => + Property.Dropdown({ + displayName: params.displayName, + refreshers: [], + required: params.required, + description: params.description, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const customObjectsResponse = await client.crm.schemas.coreApi.getAll(); + + // + const options = customObjectsResponse.results.map((customObj) => { + return { + label: customObj.labels.plural ?? customObj.name, + value: customObj.objectTypeId, + }; + }); + return { + disabled: false, + options: [...STANDARD_OBJECT_TYPES, ...options], + }; + }, + }); + +export const associationTypeDropdown = Property.Dropdown({ + displayName: 'Type of the association', + refreshers: ['fromObjectType', 'toObjectType'], + required: true, + options: async ({ auth, fromObjectType, toObjectType }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + const associationLabels = await client.crm.associations.v4.schema.definitionsApi.getAll( + fromObjectType as string, + toObjectType as string, + ); + + const options = associationLabels.results.map((associationLabel) => { + return { + label: associationLabel.label ?? `${fromObjectType}_to_${toObjectType}`, + value: associationLabel.typeId, + }; + }); + + return { + disabled: false, + options, + }; + }, +}); + +export const toObjectIdsDropdown = (params: DropdownParams) => + Property.MultiSelectDropdown({ + displayName: params.displayName, + description: params.description, + refreshers: ['toObjectType'], + required: params.required, + options: async ({ auth, toObjectType }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + let after: string | undefined; + do { + const response = await client.crm.objects.basicApi.getPage( + toObjectType as string, + limit, + after, + ); + for (const object of response.results) { + let labelName; + switch (toObjectType) { + case OBJECT_TYPE.CONTACT: + labelName = 'email'; + break; + case OBJECT_TYPE.COMPANY: + labelName = 'name'; + break; + case OBJECT_TYPE.DEAL: + labelName = 'dealname'; + break; + case OBJECT_TYPE.TICKET: + labelName = 'subject'; + break; + case OBJECT_TYPE.LINE_ITEM: + labelName = 'name'; + break; + } + options.push({ + label: object.properties[labelName!] ?? object.id, + value: object.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, + }); + +export const formDropdown = Property.Dropdown({ + displayName: 'Form', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return buildEmptyList({ + placeholder: 'Please connect your account.', + }); + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + + const limit = 100; + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.marketing.forms.formsApi.getPage(after, limit); + for (const form of response.results) { + options.push({ + label: form.name, + value: form.id, + }); + } + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, +}); + +export const blogUrlDropdown = Property.Dropdown({ + displayName: 'Blog URL', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { disabled: true, options: [], placeholder: 'Please connect your account.' }; + } + + const authValue = auth as PiecePropValueSchema; + + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/content/api/v2/blogs', + authentication: { type: AuthenticationType.BEARER_TOKEN, token: authValue.access_token }, + queryParams: { + limit: '100', + }, + }); + + return { + disabled: false, + options: response.body.objects.map((blog) => { + return { + label: blog.absolute_url, + value: blog.id.toString(), + }; + }), + }; + }, +}); + +export const blogAuthorDropdown = Property.Dropdown({ + displayName: 'Blog Author', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { disabled: true, options: [], placeholder: 'Please connect your account.' }; + } + + const authValue = auth as PiecePropValueSchema; + + const client = new Client({ accessToken: authValue.access_token }); + + const options: DropdownOption[] = []; + + let after: string | undefined; + do { + const response = await client.cms.blogs.authors.blogAuthorsApi.getPage( + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + after, + 100, + ); + for (const author of response.results) { + options.push({ + label: author.name, + value: author.id, + }); + } + + after = response.paging?.next?.after; + } while (after); + + return { + disabled: false, + options, + }; + }, +}); + +type DropdownParams = { + objectType: OBJECT_TYPE; + displayName: string; + required: boolean; + description?: string; +}; diff --git a/packages/pieces/community/hubspot/src/lib/common/types.ts b/packages/pieces/community/hubspot/src/lib/common/types.ts new file mode 100644 index 0000000000..17599ec787 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/common/types.ts @@ -0,0 +1,75 @@ +export type HubSpotAddContactsToListResponse = { + updated: number[]; + discarded: number[]; + invalidVids: number[]; + invalidEmails: string[]; +}; + +export type HubspotProperty = { + name: string; + label: string; + description: string; + hidden?: boolean; + type: string; + groupName: string; + fieldType: string; + referencedObjectType?: string; + modificationMetadata?: { + archivable: boolean; + readOnlyDefinition: boolean; + readOnlyValue: boolean; + }; + options: Array<{ label: string; value: string }>; +}; + +export type WorkflowResponse = { + id: number; + insertAt: number; + updatedAt: number; + name: string; + enabled: boolean; +}; + +export enum FilterOperatorEnum { + Eq = 'EQ', + Neq = 'NEQ', + Lt = 'LT', + Lte = 'LTE', + Gt = 'GT', + Gte = 'GTE', + Between = 'BETWEEN', + In = 'IN', + NotIn = 'NOT_IN', + HasProperty = 'HAS_PROPERTY', + NotHasProperty = 'NOT_HAS_PROPERTY', + ContainsToken = 'CONTAINS_TOKEN', + NotContainsToken = 'NOT_CONTAINS_TOKEN', +} + +export enum HubspotFieldType { + BooleanCheckBox = 'booleancheckbox', + Date = 'date', + File = 'file', + Number = 'number', + CalculationEquation = 'calculation_equation', + PhoneNumber = 'phonenumber', + Text = 'text', + TextArea = 'textarea', + Html = 'html', + CheckBox = 'checkbox', + Select = 'select', + Radio = 'radio', +} + +export declare enum AssociationSpecAssociationCategoryEnum { + HubspotDefined = 'HUBSPOT_DEFINED', + UserDefined = 'USER_DEFINED', + IntegratorDefined = 'INTEGRATOR_DEFINED', +} + +export type ListBlogsResponse = { + objects: Array<{ absolute_url: string; id: number }>; + offset: number; + total: number; + limit: number; +}; diff --git a/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts b/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts index 5366489daf..50a0f299d1 100644 --- a/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts +++ b/packages/pieces/community/hubspot/src/lib/triggers/deal-stage-updated.ts @@ -5,78 +5,74 @@ import { createTrigger, } from '@activepieces/pieces-framework'; -import { - AuthenticationType, - DedupeStrategy, - HttpMethod, - HttpRequest, - Polling, - httpClient, - pollingHelper, -} from '@activepieces/pieces-common'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; import dayjs from 'dayjs'; import { hubspotAuth } from '../../'; import { - ListDealPipelinesResponse, - ListPipelineStagesResponse, - SearchDealsResponse, -} from '../common/models'; + getDefaultPropertiesForObject, + pipelineDropdown, + pipelineStageDropdown, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { MarkdownVariant } from '@activepieces/shared'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; + pipelineId?: string; + stageId?: string; +}; -const polling: Polling< - PiecePropValueSchema, - { pipelineId: string; dealStageId: string } -> = { +const polling: Polling, Props> = { strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, propsValue, lastFetchEpochMS }) => { - const items: { - id: string; - createdAt: string; - updatedAt: string; - properties: Record; - }[] = []; + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultDealProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + const propertiesToRetrieve = [...defaultDealProperties, ...additionalProperties]; + + const items = []; let after; + do { - const request: HttpRequest = { - method: HttpMethod.POST, - url: 'https://api.hubapi.com/crm/v3/objects/deals/search', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: auth.access_token, - }, - body: { - limit: 100, - filterGroups: [ - { - filters: [ - { - propertyName: 'pipeline', - operator: 'EQ', - value: propsValue.pipelineId, - }, - { propertyName: 'dealstage', operator: 'EQ', value: propsValue.dealStageId }, - ], - }, - ], - sorts: [ - { - propertyName: 'hs_lastmodifieddate', - direction: 'DESCENDING', - }, - ], - after: after, - }, - }; + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.deals.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + after, + filterGroups: [ + { + filters: [ + { + propertyName: 'pipeline', + operator: FilterOperatorEnum.Eq, + value: propsValue.pipelineId, + }, + { + propertyName: 'dealstage', + operator: FilterOperatorEnum.Eq, + value: propsValue.stageId, + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); - const response = await httpClient.sendRequest(request); - items.push(...response.body.results); - after = response.body.paging?.next.after; - } while (after !== undefined); return items.map((item) => ({ - epochMilliSeconds: dayjs(item.updatedAt).valueOf(), + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), data: item, })); }, @@ -84,125 +80,54 @@ const polling: Polling< export const dealStageUpdatedTrigger = createTrigger({ auth: hubspotAuth, - name: 'deal_stage_updated', + name: 'deal-stage-updated', displayName: 'Updated Deal Stage', - description: 'Triggers when a deal enters s specified stage.', + description: 'Triggers when a deal enters a specified stage.', props: { - pipelineId: Property.Dropdown({ - displayName: 'Deal Pipeline', - refreshers: [], + pipelineId: pipelineDropdown({ + objectType: OBJECT_TYPE.DEAL, required: true, - options: async ({ auth }) => { - if (!auth) { - return { - disabled: true, - placeholder: 'Please connect your account first.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: 'https://api.hubapi.com/crm/v3/pipelines/deals', - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((pipeline) => { - return { - label: pipeline.label, - value: pipeline.id, - }; - }), - }; - }, + displayName: 'Deal Pipeline', }), - dealstageId: Property.Dropdown({ - displayName: 'Deal Stage', - refreshers: ['pipelineId'], + stageId: pipelineStageDropdown({ + objectType: OBJECT_TYPE.DEAL, required: true, - options: async ({ auth, pipelineId }) => { - if (!auth || !pipelineId) { - return { - disabled: true, - placeholder: 'Please connect your account first and select pipeline.', - options: [], - }; - } - const authValue = auth as PiecePropValueSchema; - const request: HttpRequest = { - method: HttpMethod.GET, - url: `https://api.hubapi.com/crm/v3/pipelines/deals/${pipelineId}/stages`, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: authValue.access_token, - }, - }; - const response = await httpClient.sendRequest(request); - return { - disabled: false, - options: response.body.results.map((stage) => { - return { - label: stage.label, - value: stage.id, - }; - }), - }; - }, + displayName: 'Deal Stage', + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, }), }, type: TriggerStrategy.POLLING, - async test(context) { - const { store, auth, propsValue, files } = context; - return await pollingHelper.test(polling, { - store, - auth, - propsValue: { - pipelineId: propsValue.pipelineId, - dealStageId: propsValue.dealstageId, - }, - files: files, - }); - }, async onEnable(context) { - const { store, auth, propsValue } = context; await pollingHelper.onEnable(polling, { - store, - auth, - propsValue: { - pipelineId: propsValue.pipelineId, - dealStageId: propsValue.dealstageId, - }, + auth: context.auth, + store: context.store, + propsValue: context.propsValue, }); }, - async onDisable(context) { - const { store, auth, propsValue } = context; await pollingHelper.onDisable(polling, { - store, - auth, - propsValue: { - pipelineId: propsValue.pipelineId, - dealStageId: propsValue.dealstageId, - }, + auth: context.auth, + store: context.store, + propsValue: context.propsValue, }); }, - + async test(context) { + return await pollingHelper.test(polling, context); + }, async run(context) { - const { store, auth, propsValue, files } = context; - return await pollingHelper.poll(polling, { - store, - auth, - propsValue: { - pipelineId: propsValue.pipelineId, - dealStageId: propsValue.dealstageId, - }, - files: files, - }); + return await pollingHelper.poll(polling, context); }, sampleData: { id: '18011922225', diff --git a/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts b/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts new file mode 100644 index 0000000000..70561641d1 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/email-subscriptions-timeline.ts @@ -0,0 +1,101 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type SubscriptionTimeLineResponse = { + hasMore: boolean; + offset: string; + timeline: Array>; +}; + +const polling: Polling, Record> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, lastFetchEpochMS }) { + const qs: QueryParams = { limit: '100' }; + if (lastFetchEpochMS) { + qs.startTimestamp = lastFetchEpochMS.toString(); + } + const items = []; + + let hasMore = true; + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/email/public/v1/subscriptions/timeline', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const item of response.body.timeline) { + items.push(item); + } + } while (hasMore); + + return items.map((item) => ({ + epochMilliSeconds: item.timestamp as number, + data: item, + })); + }, +}; + +export const newEmailSubscriptionsTimelineTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-email-subscriptions-timeline', + displayName: 'New Email Subscriptions Timeline', + description: 'Triggers when a new email timeline subscription added for the portal.', + type: TriggerStrategy.POLLING, + props: {}, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + timestamp: 1401975207000, + portalId: 62515, + recipient: '6d4b537e-c5ac-11e3-a673-00262df65d03@some.email.com', + changes: [ + { + change: 'BOUNCED', + source: 'SOURCE_NON_DELIVERY_REPORT', + portalId: 62515, + changeType: 'PORTAL_BOUNCE', + causedByEvent: { + id: '6d72d39c-87da-3ced-bfdf-5f0213363827', + created: 1401975207000, + }, + }, + ], + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts new file mode 100644 index 0000000000..e89f787244 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-blog-article.ts @@ -0,0 +1,133 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import dayjs from 'dayjs'; + +type Props = { + articleState: string; +}; + +type ListBlogPostsResponse = { + results: Array>; + paging?: { + next?: { + after: string; + }; + }; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const articleState = propsValue.articleState; + const isTestMode = lastFetchEpochMS === 0; + + const qs: QueryParams = { limit: '100', sort: '-createdAt' }; + if (articleState !== 'BOTH') { + qs.state = articleState; + } + if (!isTestMode) { + if(articleState === 'PUBLISHED') { + qs['publishDate__gt']=lastFetchEpochMS.toString(); + } + else + { + qs['createdAt__gt']=lastFetchEpochMS.toString(); + } + } + + const items = []; + + let after; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/cms/v3/blogs/posts', + queryParams: qs, + authentication: { type: AuthenticationType.BEARER_TOKEN, token: auth.access_token }, + }); + + after = response.body.paging?.next?.after; + if(response.body.paging?.next?.after){ + qs.after = response.body.paging?.next?.after; + } + items.push(...response.body.results); + if (isTestMode) { + break; + } + } while (after); + + return items.map((item) => { + return { + epochMilliSeconds: articleState === 'PUBLISHED' ? dayjs(item.publishDate).valueOf() : dayjs(item.createdAt).valueOf(), + data: item, + } + }); + }, +}; + +export const newBlogArticleTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-blog-article', + displayName: 'New COS Blog Article', + description: 'Triggers when a new article is added to your COS blog.', + type: TriggerStrategy.POLLING, + props: { + articleState: Property.StaticDropdown({ + displayName: 'Article State', + required: true, + options: { + disabled: false, + options: [ + { + label: 'Published Only', + value: 'PUBLISHED', + }, + { + label: 'Draft Only', + value: 'DRAFT', + }, + { + label: 'Both', + value: 'BOTH', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-company-added.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-company-added.ts deleted file mode 100644 index 854649feaf..0000000000 --- a/packages/pieces/community/hubspot/src/lib/triggers/new-company-added.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { - OAuth2PropertyValue, - createTrigger, -} from '@activepieces/pieces-framework'; -import { TriggerStrategy } from '@activepieces/pieces-framework'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; - -import { hubSpotAuthentication } from '../common/props'; -import { hubSpotClient } from '../common/client'; -import dayjs from 'dayjs'; - -const polling: Polling> = { - strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, lastFetchEpochMS }) => { - const currentValues = - ( - await hubSpotClient.searchCompanies(auth.access_token, { - createdAt: lastFetchEpochMS, - }) - ).results ?? []; - const items = currentValues.map((item: { createdAt: string }) => ({ - epochMilliSeconds: dayjs(item.createdAt).valueOf(), - data: item, - })); - return items; - }, -}; - -export const newCompanyAdded = createTrigger({ - auth: hubSpotAuthentication, - name: 'new_company', - displayName: 'New Company Added', - description: 'Trigger when a new company is added.', - props: {}, - type: TriggerStrategy.POLLING, - onEnable: async (context) => { - await pollingHelper.onEnable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - onDisable: async (context) => { - await pollingHelper.onDisable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - run: async (context) => { - return await pollingHelper.poll(polling, context); - }, - test: async (context) => { - return await pollingHelper.test(polling, context); - }, - - sampleData: { - id: '123123123', - archived: false, - createdAt: '2023-07-03T14:48:13.839Z', - updatedAt: '2023-07-03T14:48:14.769Z', - properties: { - name: 'Company Name', - domain: 'company.com', - createdate: '2023-07-03T14:48:13.839Z', - hs_object_id: '123123123', - hs_lastmodifieddate: '2023-07-03T14:48:14.769Z', - }, - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-company-propety-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-company-propety-change.ts new file mode 100644 index 0000000000..034d766a30 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-company-propety-change.ts @@ -0,0 +1,154 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 comapnies + if (lastFetchEpochMS === 0) { + const response = await client.crm.companies.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated companies + const updatedComapnies = []; + let after; + do { + const response = await client.crm.companies.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedComapnies.push(...response.results); + } while (after); + + if (updatedComapnies.length === 0) { + return []; + } + + // Fetch companies with property history + const updatedComapniesWithPropertyHistory = await client.crm.companies.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedComapnies.map((company) => { + return { + id: company.id, + }; + }), + }); + + for (const company of updatedComapniesWithPropertyHistory.results) { + const history = company.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = company; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newCompanyPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-company-property-change', + displayName: 'New Company Property Change', + description: 'Triggers when a specified property is updated on a company.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '27656515180', + properties: { + createdate: '2024-12-26T08:36:10.463Z', + domain: 'www.activepieces.com', + hs_lastmodifieddate: '2024-12-26T08:58:48.657Z', + hs_object_id: '27656515180', + name: 'Activepieces', + }, + createdAt: '2024-12-26T08:36:10.463Z', + updatedAt: '2024-12-26T08:58:48.657Z', + archived: false, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts new file mode 100644 index 0000000000..91dcb62522 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-company.ts @@ -0,0 +1,117 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + const propertiesToRetrieve = [...defaultCompanyProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.companies.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; +export const newCompanyTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-company', + displayName: 'New Company', + description: 'Trigger when a new company is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + id: '123123123', + archived: false, + createdAt: '2023-07-03T14:48:13.839Z', + updatedAt: '2023-07-03T14:48:14.769Z', + properties: { + name: 'Company Name', + domain: 'company.com', + createdate: '2023-07-03T14:48:13.839Z', + hs_object_id: '123123123', + hs_lastmodifieddate: '2023-07-03T14:48:14.769Z', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-added.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-added.ts deleted file mode 100644 index 5c7d037a43..0000000000 --- a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-added.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - OAuth2PropertyValue, - createTrigger, -} from '@activepieces/pieces-framework'; -import { TriggerStrategy } from '@activepieces/pieces-framework'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; - -import { hubSpotAuthentication } from '../common/props'; -import { hubSpotClient } from '../common/client'; -import dayjs from 'dayjs'; -import { hubspotCommon } from '../common'; - -const polling: Polling> = { - strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, lastFetchEpochMS, propsValue }) => { - const wantedFields = propsValue['contactProps']; - let fixedFields: string[]; - if (wantedFields === undefined) { - fixedFields = ['firstname', 'lastname', 'phone', 'email']; - } else { - fixedFields = wantedFields.map((field: { name: string }) => { - return field.name; - }); - fixedFields.push('firstname'); - fixedFields.push('lastname'); - fixedFields.push('phone'); - fixedFields.push('email'); - } - const currentValues = - ( - await hubSpotClient.searchContacts(auth.access_token, fixedFields, { - createdAt: lastFetchEpochMS, - }) - ).results ?? []; - const items = currentValues.map((item: { createdAt: string }) => ({ - epochMilliSeconds: dayjs(item.createdAt).valueOf(), - data: item, - })); - return items; - }, -}; - -export const newContactAdded = createTrigger({ - auth: hubSpotAuthentication, - name: 'new_contact', - displayName: 'New Contact Added', - description: 'Trigger when a new contact is added.', - props: { - contactProps: hubspotCommon.choose_props, - }, - type: TriggerStrategy.POLLING, - onEnable: async (context) => { - await pollingHelper.onEnable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - onDisable: async (context) => { - await pollingHelper.onDisable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - run: async (context) => { - return await pollingHelper.poll(polling, context); - }, - test: async (context) => { - return await pollingHelper.test(polling, context); - }, - - sampleData: { - id: '123', - archived: false, - createdAt: '2023-06-13T10:24:42.392Z', - updatedAt: '2023-06-30T06:16:51.869Z', - properties: { - email: 'contact@email.com', - lastname: 'Last', - firstname: 'First', - createdate: '2023-06-13T10:24:42.392Z', - hs_object_id: '123', - lastmodifieddate: '2023-06-30T06:16:51.869Z', - }, - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts new file mode 100644 index 0000000000..016e196ff8 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-in-list.ts @@ -0,0 +1,174 @@ +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + DropdownOption, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { Client } from '@hubspot/api-client'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import dayjs from 'dayjs'; + +type Props = { + listId: string; + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const listId = propsValue.listId; + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const client = new Client({ accessToken: auth.access_token }); + const isTestMode = lastFetchEpochMS === 0; + + let listMembers = []; + let after; + + // Fetch members from the list + do { + const response = await client.crm.lists.membershipsApi.getPageOrderedByAddedToListDate( + listId, + after, + undefined, + isTestMode ? 10 : 100, + ); + after = response.paging?.next?.after; + listMembers.push(...response.results); + if (isTestMode) { + break; + } + } while (after); + + if (!isTestMode) { + listMembers = listMembers.filter( + (member) => dayjs(member.membershipTimestamp).valueOf() > lastFetchEpochMS, + ); + } + + // Fetch detailed contact properties + const contactDetailsResponse = await client.crm.contacts.batchApi.read({ + inputs: listMembers.map((member) => ({ id: member.recordId })), + properties: propertiesToRetrieve, + propertiesWithHistory: [], + }); + + // Merge `membershipTimestamp` with contact properties + const enrichedMembers = contactDetailsResponse.results.map((contact) => { + const correspondingMember = listMembers.find((member) => member.recordId === contact.id); + return { + ...contact, + membershipTimestamp: correspondingMember?.membershipTimestamp, + }; + }); + + return enrichedMembers.map((member) => ({ + epochMilliSeconds: dayjs(member.membershipTimestamp).valueOf(), + data: member, + })); + }, +}; + +export const newContactInListTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact-in-list', + displayName: 'New Contact in List', + description: 'Triggers when a new contact is added to the specified list.', + type: TriggerStrategy.POLLING, + props: { + listId: Property.Dropdown({ + displayName: 'Contact List', + refreshers: [], + required: true, + options: async ({ auth }) => { + if (!auth) { + return { + disabled: true, + options: [], + placeholder: 'Please connect your account.', + }; + } + + const authValue = auth as PiecePropValueSchema; + const client = new Client({ accessToken: authValue.access_token }); + let offset = 0; + let hasMore = true; + const options: DropdownOption[] = []; + do { + const response = await client.crm.lists.listsApi.doSearch({ + count: 100, + offset: offset, + }); + for (const list of response.lists) { + options.push({ + label: list.name, + value: list.listId, + }); + } + hasMore = response.hasMore; + offset += 100; + } while (hasMore); + + return { + disabled: false, + options, + }; + }, + }), + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + id: '123', + archived: false, + createdAt: '2023-06-13T10:24:42.392Z', + updatedAt: '2023-06-30T06:16:51.869Z', + properties: { + email: 'contact@email.com', + lastname: 'Last', + firstname: 'First', + createdate: '2023-06-13T10:24:42.392Z', + hs_object_id: '123', + lastmodifieddate: '2023-06-30T06:16:51.869Z', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts new file mode 100644 index 0000000000..8054944fd4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact-property-change.ts @@ -0,0 +1,176 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + // Extract properties once to avoid recomputation + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 contacts + if (lastFetchEpochMS === 0) { + const response = await client.crm.contacts.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated contacts + const updatedContacts = []; + let after; + do { + const response = await client.crm.contacts.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedContacts.push(...response.results); + } while (after); + + if (updatedContacts.length === 0) { + return []; + } + + // Fetch contacts with property history + const updatedContcatsWithPropertyHistory = await client.crm.contacts.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedContacts.map((contact) => { + return { + id: contact.id, + }; + }), + }); + + for (const contact of updatedContcatsWithPropertyHistory.results) { + const history = contact.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = contact; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newContactPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact-property-change', + displayName: 'New Contact Property Change', + description: 'Triggers when a specified property is updated on a contact.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts new file mode 100644 index 0000000000..943204f458 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-contact.ts @@ -0,0 +1,140 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { hubspotAuth } from '../..'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.contacts.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + after, + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newContactTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-contact', + displayName: 'New Contact', + description: 'Trigger when new contact is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts new file mode 100644 index 0000000000..edff0165a4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object-property-change.ts @@ -0,0 +1,155 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + DynamicPropsValue, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { + customObjectDropdown, + customObjectPropertiesDropdown, + standardObjectPropertiesDropdown, +} from '../common/props'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + customObjectType?: string; + propertyName?: DynamicPropsValue; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const customObjectType = propsValue.customObjectType as string; + const propertyToCheck = propsValue.propertyName?.['values'] as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 custom objects + if (lastFetchEpochMS === 0) { + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated custom objects + const updatedCustomObjects = []; + let after; + do { + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedCustomObjects.push(...response.results); + } while (after); + + if (updatedCustomObjects.length === 0) { + return []; + } + + // Fetch custom objects with property history + const updatedCustomObjectsWithPropertyHistory = await client.crm.objects.batchApi.read( + customObjectType, + { + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedCustomObjects.map((customObject) => { + return { + id: customObject.id, + }; + }), + }, + ); + + for (const customObject of updatedCustomObjectsWithPropertyHistory.results) { + const history = customObject.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = customObject; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newCustomObjectPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-custom-object-property-change', + displayName: 'New Custom Object Property Change', + description: 'Triggers when a specified property is updated on a custom object.', + props: { + customObjectType: customObjectDropdown, + propertyName: customObjectPropertiesDropdown('Property Name', true, true), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T15:20:16.121Z', + archived: false, + id: '21583829313', + properties: { + hs_createdate: '2024-12-22T15:20:16.121Z', + hs_lastmodifieddate: '2024-12-22T15:20:16.818Z', + hs_object_id: '21583829313', + pet_name: 'Oreo', + }, + updatedAt: '2024-12-22T15:20:16.818Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts new file mode 100644 index 0000000000..f2dab8b711 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-custom-object.ts @@ -0,0 +1,132 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + DynamicPropsValue, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { customObjectDropdown, customObjectPropertiesDropdown } from '../common/props'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + customObjectType?: string; + additionalPropertiesToRetrieve?: DynamicPropsValue; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const customObjectType = propsValue.customObjectType as string; + const additionalPropertiesToRetrieve = propsValue.additionalPropertiesToRetrieve?.['values']; + + let propertiesToRetrieve; + try { + if (Array.isArray(additionalPropertiesToRetrieve)) { + propertiesToRetrieve = additionalPropertiesToRetrieve; + } + if (typeof additionalPropertiesToRetrieve === 'string') { + propertiesToRetrieve = JSON.parse(additionalPropertiesToRetrieve as string); + } + } catch (error) { + propertiesToRetrieve = []; + } + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.objects.searchApi.doSearch(customObjectType, { + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newCustomObjectTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-custom-object', + displayName: 'New Custom Object', + description: 'Triggers when new custom object is available.', + props: { + customObjectType: customObjectDropdown, + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_object_id, hs_lastmodifieddate, hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: customObjectPropertiesDropdown( + 'Additional Properties to Retrieve', + false, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T15:20:16.121Z', + archived: false, + id: '21583829313', + properties: { + hs_createdate: '2024-12-22T15:20:16.121Z', + hs_lastmodifieddate: '2024-12-22T15:20:16.818Z', + hs_object_id: '21583829313', + pet_name: 'Oreo', + }, + updatedAt: '2024-12-22T15:20:16.818Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-deal-added.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-deal-added.ts deleted file mode 100644 index c5324c226a..0000000000 --- a/packages/pieces/community/hubspot/src/lib/triggers/new-deal-added.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - OAuth2PropertyValue, - createTrigger, -} from '@activepieces/pieces-framework'; -import { TriggerStrategy } from '@activepieces/pieces-framework'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; - -import { hubSpotAuthentication } from '../common/props'; -import { hubSpotClient } from '../common/client'; -import dayjs from 'dayjs'; - -const polling: Polling> = { - strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, lastFetchEpochMS }) => { - const currentValues = - ( - await hubSpotClient.searchDeals(auth.access_token, { - createdAt: lastFetchEpochMS, - }) - ).results ?? []; - const items = currentValues.map((item: { createdAt: string }) => ({ - epochMilliSeconds: dayjs(item.createdAt).valueOf(), - data: item, - })); - return items; - }, -}; - -export const newDealAdded = createTrigger({ - auth: hubSpotAuthentication, - name: 'new_deal', - displayName: 'New Deal Added', - description: 'Trigger when a new deal is added.', - props: {}, - type: TriggerStrategy.POLLING, - onEnable: async (context) => { - await pollingHelper.onEnable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - onDisable: async (context) => { - await pollingHelper.onDisable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - run: async (context) => { - return await pollingHelper.poll(polling, context) - }, - test: async (context) => { - return await pollingHelper.test(polling, context) - }, - - sampleData: {}, -}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts new file mode 100644 index 0000000000..e0213c8da3 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-deal-property-change.ts @@ -0,0 +1,153 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 deals + if (lastFetchEpochMS === 0) { + const response = await client.crm.deals.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated deals + const updatedDeals = []; + let after; + do { + const response = await client.crm.deals.searchApi.doSearch({ + limit: 100, + after, + sorts: ['-hs_lastmodifieddate'], + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedDeals.push(...response.results); + } while (after); + + if (updatedDeals.length === 0) { + return []; + } + + // Fetch deals with property history + const updatedDealsWithPropertyHistory = await client.crm.deals.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedDeals.map((deal) => { + return { + id: deal.id, + }; + }), + }); + + for (const deal of updatedDealsWithPropertyHistory.results) { + const history = deal.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = deal; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newDealPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-deal-property-change', + displayName: 'New Deal Property Change', + description: 'Triggers when a specified property is updated on a deal.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.DEAL, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-23T08:19:21.614Z', + archived: false, + id: '30906615140', + properties: { + amount: '150', + createdate: '2024-12-23T08:19:21.614Z', + hs_lastmodifieddate: '2024-12-26T09:30:44.578Z', + hs_object_id: '30906615140', + }, + updatedAt: '2024-12-26T09:30:44.578Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts new file mode 100644 index 0000000000..4d18a36b32 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-deal.ts @@ -0,0 +1,129 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.DEAL); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.deals.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newDealTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-deal', + displayName: 'New Deal', + description: 'Trigger when a new deal is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + dealtype, dealname, amount, description, closedate, createdate, num_associated_contacts, hs_forecast_amount, hs_forecast_probability, hs_manual_forecast_category, hs_next_step, hs_object_id, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.DEAL, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-23T08:19:21.614Z', + archived: false, + id: '30906615140', + properties: { + amount: '150', + closedate: null, + createdate: '2024-12-23T08:19:21.614Z', + dealname: 'test deal', + dealtype: 'newbusiness', + description: 'test', + hs_forecast_amount: '150.0', + hs_forecast_probability: null, + hs_lastmodifieddate: '2024-12-26T10:31:45.624Z', + hs_manual_forecast_category: null, + hs_next_step: null, + hs_object_id: '30906615140', + hubspot_owner_id: '64914635', + hubspot_team_id: '55094099', + num_associated_contacts: '1', + }, + updatedAt: '2024-12-26T10:31:45.624Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts new file mode 100644 index 0000000000..9a718bb8c4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-email-event.ts @@ -0,0 +1,147 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type Props = { + eventType?: string; +}; + +type EmailEventResponse = { + events: Array>; + hasMore: boolean; + offset: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const eventType = propsValue.eventType; + const isTestMode = lastFetchEpochMS === 0; + + let hasMore = true; + const qs: QueryParams = { limit: '100' }; + if (eventType) { + qs.eventType = eventType; + } + if (lastFetchEpochMS) { + qs.startTimestamp = lastFetchEpochMS.toString(); + } + const emailEvents = []; + + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/email/public/v1/events', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const event of response.body.events) { + emailEvents.push(event); + } + if (isTestMode) break; + } while (hasMore); + + return emailEvents.map((item) => ({ + epochMilliSeconds: item['created'] as number, + data: item, + })); + }, +}; + +export const newEmailEventTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-email-event', + displayName: 'New Email Event', + description: 'Triggers when all,or specific new email event is available.', + type: TriggerStrategy.POLLING, + props: { + eventType: Property.StaticDropdown({ + displayName: 'Event Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Sent', + value: 'SENT', + }, + { + label: 'Dropped', + value: 'DROPPED', + }, + { + label: 'Processed', + value: 'PROCESSED', + }, + { + label: 'Delivered', + value: 'DELIVERED', + }, + { + label: 'Deferred', + value: 'DEFERRED', + }, + { + label: 'Bounce', + value: 'BOUNCE', + }, + { + label: 'Open', + value: 'OPEN', + }, + { + label: 'Click', + value: 'CLICK', + }, + { + label: 'Status Change', + value: 'STATUSCHANGE', + }, + { + label: 'Spam Report', + value: 'SPAMREPORT', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: {}, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts new file mode 100644 index 0000000000..1e73b2d36b --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-engagement.ts @@ -0,0 +1,146 @@ +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; + +type Props = { + eventType?: string; +}; + +type EngagementResponse = { + results: Array>; + hasMore: boolean; + offset: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const eventType = propsValue.eventType; + const engagements = []; + + let hasMore = true; + const qs: QueryParams = { limit: '100' }; + do { + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: 'https://api.hubapi.com/engagements/v1/engagements/paged', + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: auth.access_token, + }, + }); + hasMore = response.body.hasMore; + qs.offset = response.body.offset; + for (const engagement of response.body.results) { + engagements.push(engagement); + } + } while (hasMore); + + const filteredEngagements = eventType + ? engagements.filter((engagement) => engagement.engagement.type === eventType) + : engagements; + + return filteredEngagements.map((item) => ({ + epochMilliSeconds: item.engagement.createdAt as number, + data: item, + })); + }, +}; + +export const newEngagementTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-engagement', + displayName: 'New Engagement', + description: 'Triggers when a new engagement is created.', + type: TriggerStrategy.POLLING, + props: { + eventType: Property.StaticDropdown({ + displayName: 'Type', + required: false, + options: { + disabled: false, + options: [ + { + label: 'Note', + value: 'NOTE', + }, + { + label: 'Task', + value: 'TASK', + }, + { + label: 'Meeting', + value: 'MEETING', + }, + { + label: 'Email', + value: 'EMAIL', + }, + { + label: 'Call', + value: 'CALL', + }, + ], + }, + }), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + engagement: { + id: 29090716, + portalId: 62515, + active: true, + createdAt: 1444223400781, + lastUpdated: 1444223400781, + createdBy: 215482, + modifiedBy: 215482, + ownerId: 70, + type: 'NOTE', + timestamp: 1444223400781, + }, + associations: { + contactIds: [247], + companyIds: [], + dealIds: [], + ownerIds: [], + workflowIds: [], + }, + attachments: [], + metadata: { + body: 'This is a test note ', + }, + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts new file mode 100644 index 0000000000..d6baf939f7 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-form-submission.ts @@ -0,0 +1,155 @@ +import { hubspotAuth } from '../../'; +import { + AuthenticationType, + DedupeStrategy, + httpClient, + HttpMethod, + Polling, + pollingHelper, + QueryParams, +} from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { formDropdown } from '../common/props'; + +type Props = { + formId: string; +}; + +type FormSubmissionResponse = { + results: Array<{ + conversionId: string; + submittedAt: number; + pageUrl: string; + values: Array<{ name: string; value: string }>; + }>; + paging?: { + next?: { + after: string; + }; + }; +}; + +type FormField = { + name: string; + label: string; + fieldType: string; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const authValue = auth as PiecePropValueSchema; + const formId = propsValue.formId; + + const submissions = []; + let after; + do { + const qs: QueryParams = { limit: '50' }; + if (after) { + qs.after = after; + } + const response = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/form-integrations/v1/submissions/forms/${formId}`, + queryParams: qs, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + after = response.body.paging?.next?.after; + submissions.push(...response.body.results); + } while (after); + + const formFields = await httpClient.sendRequest({ + method: HttpMethod.GET, + url: `https://api.hubapi.com/forms/v2/fields/${formId}`, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: authValue.access_token, + }, + }); + + const fieldMapping = formFields.body.reduce((acc, field) => { + acc[field.name] = { label: field.label, fieldType: field.fieldType }; + return acc; + }, {} as Record); + + const items = []; + + for (const submission of submissions) { + const formattedValues: Record = {}; + + const submissionData = submission.values ?? []; + for (const fieldValue of submissionData) { + const field = fieldMapping[fieldValue.name]; + + if (field) { + const { label, fieldType } = field; + if (fieldType === 'checkbox') { + formattedValues[label] = formattedValues[label] || []; + formattedValues[label].push(fieldValue.value); + } else { + formattedValues[label] = fieldValue.value; + } + } else { + formattedValues[fieldValue.name] = fieldValue.value; + } + } + items.push({ + ...submission, + values: formattedValues, + }); + } + + return items.map((item) => ({ + epochMilliSeconds: item.submittedAt, + data: item, + })); + }, +}; + +export const newFormSubmissionTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-form-submission', + displayName: 'New Form Submission', + description: 'Triggers when a form is submitted.', + type: TriggerStrategy.POLLING, + props: { + formId: formDropdown, + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + conversionId: '82800398-30af-48a0-942a-1d0623fce08c', + submittedAt: 1735216921730, + values: { + "First Name": "John", + "Last Name": "Doe", + "Email": "john.doe@example.com", + }, + pageUrl: 'https://share.hsforms.com/1VXAvM044Tcyaa3Y5XRQFSQsuf7d', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts new file mode 100644 index 0000000000..9d2da15ba4 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-line-item.ts @@ -0,0 +1,138 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const propertiesToRetrieve = [...defaultLineItemProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.lineItems.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newLineItemTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-line-item', + displayName: 'New Line Item', + description: 'Triggers when new line item is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-25T11:08:54.763Z', + archived: false, + id: '26882583648', + properties: { + amount: '5.00', + createdate: '2024-12-25T11:08:54.763Z', + description: 'CHAIR', + discount: '10', + hs_cost_of_goods_sold: '10', + hs_discount_percentage: null, + hs_images: null, + hs_lastmodifieddate: '2024-12-25T11:10:02.750Z', + hs_line_item_currency_code: null, + hs_object_id: '26882583648', + hs_product_id: '17602013482', + hs_sku: 'fb-100', + hs_tax_amount: null, + hs_tcv: '5.00', + hs_term_in_months: null, + hs_total_discount: '10.00', + hs_url: null, + name: 'Chair', + price: '15.0', + quantity: null, + tax: null, + }, + updatedAt: '2024-12-25T11:10:02.750Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts new file mode 100644 index 0000000000..9df1c8bc58 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-company.ts @@ -0,0 +1,145 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultCompanyProperties = getDefaultPropertiesForObject(OBJECT_TYPE.COMPANY); + const propertiesToRetrieve = [...defaultCompanyProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.companies.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedCompanyTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-company', + displayName: 'Company Recently Created or Updated', + description: 'Triggers when a company recenty created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, domain, industry, about_us, phone, address, address2, city, state, zip, country, website, type, description, founded_year, hs_createdate, hs_lastmodifieddate, hs_object_id, is_public, timezone, total_money_raised, total_revenue, owneremail, ownername, numberofemployees, annualrevenue, lifecyclestage, createdate, web_technologies + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.COMPANY, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-20T10:08:20.243Z', + archived: false, + id: '27508860336', + properties: { + about_us: 'Automation', + address: null, + address2: null, + annualrevenue: '500000', + city: null, + country: null, + createdate: '2024-12-20T10:08:20.243Z', + description: 'Automation', + domain: 'www.activepieces.com', + founded_year: null, + hs_createdate: null, + hs_lastmodifieddate: '2024-12-25T10:04:47.382Z', + hs_object_id: '27508860336', + industry: 'COMPUTER_SOFTWARE', + is_public: null, + lifecyclestage: 'lead', + name: 'Activepieces', + numberofemployees: '6', + owneremail: null, + ownername: null, + phone: null, + state: null, + timezone: null, + total_money_raised: null, + total_revenue: null, + type: 'OTHER', + web_technologies: null, + website: 'www.activepieces.com', + zip: null, + }, + updatedAt: '2024-12-25T10:04:47.382Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts new file mode 100644 index 0000000000..da50b3d5c0 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-contact.ts @@ -0,0 +1,142 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultContactProperties = getDefaultPropertiesForObject(OBJECT_TYPE.CONTACT); + const propertiesToRetrieve = [...defaultContactProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.contacts.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedContactTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-contact', + displayName: 'Contact Recently Created or Updated', + description: 'Triggers when a contact recenty created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + firstname, lastname, email, company, website, mobilephone, phone, fax, address, city, state, zip, salutation, country, jobtitle, hs_createdate, hs_email_domain, hs_object_id, lastmodifieddate, hs_persona, hs_language, lifecyclestage, createdate, numemployees, annualrevenue, industry + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.CONTACT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-06T10:52:58.322Z', + archived: false, + id: '82665997707', + properties: { + address: null, + annualrevenue: null, + city: 'Brisbane', + company: 'HubSpot', + country: null, + createdate: '2024-12-06T10:52:58.322Z', + email: 'emailmaria@hubspot.com', + fax: null, + firstname: 'Maria', + hs_createdate: null, + hs_email_domain: 'hubspot.com', + hs_language: null, + hs_object_id: '82665997707', + hs_persona: null, + industry: null, + jobtitle: 'Salesperson', + lastmodifieddate: '2024-12-20T12:50:35.201Z', + lastname: 'Johnson (Sample Contact)', + lifecyclestage: 'lead', + mobilephone: null, + numemployees: null, + phone: null, + salutation: null, + state: null, + website: 'http://www.HubSpot.com', + zip: null, + }, + updatedAt: '2024-12-20T12:50:35.201Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts new file mode 100644 index 0000000000..52ea24649d --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-line-item.ts @@ -0,0 +1,134 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultLineItemProperties = getDefaultPropertiesForObject(OBJECT_TYPE.LINE_ITEM); + const propertiesToRetrieve = [...defaultLineItemProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.lineItems.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedLineItemTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-line-item', + displayName: 'Line Item Recently Created or Updated', + description: 'Triggers when a line item recenty created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + name, description, price, quantity, amount, discount, tax, createdate, hs_object_id, hs_product_id, hs_images, hs_lastmodifieddate, hs_line_item_currency_code, hs_sku, hs_url, hs_cost_of_goods_sold, hs_discount_percentage, hs_term_in_months + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.LINE_ITEM, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-24T16:17:34.281Z', + archived: false, + id: '26854130802', + properties: { + amount: '15.00', + createdate: '2024-12-24T16:17:34.281Z', + description: 'Chair', + discount: null, + hs_cost_of_goods_sold: '10', + hs_discount_percentage: null, + hs_images: null, + hs_lastmodifieddate: '2024-12-24T16:17:50.488Z', + hs_line_item_currency_code: 'USD', + hs_object_id: '26854130802', + hs_product_id: '17602013482', + hs_sku: 'ch-100', + hs_term_in_months: null, + hs_url: null, + name: 'Chair', + price: '15.0', + quantity: '1', + tax: null, + }, + updatedAt: '2024-12-24T16:17:50.488Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts new file mode 100644 index 0000000000..0f5552759f --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-or-updated-product.ts @@ -0,0 +1,123 @@ +import { MarkdownVariant } from '@activepieces/shared'; +import { hubspotAuth } from '../../'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +const polling: Polling< + PiecePropValueSchema, + { additionalPropertiesToRetrieve?: string[] | string } +> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.products.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newOrUpdatedProductTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-or-updated-product', + displayName: 'Product Recently Created or Updated', + description: 'Triggers when a product recenty created or updated.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-18T16:10:27.710Z', + archived: false, + id: '17602013482', + properties: { + createdate: '2024-12-18T16:10:27.710Z', + description: 'Chair', + hs_lastmodifieddate: '2024-12-23T08:13:30.314Z', + hs_object_id: '17602013482', + name: 'Chair', + price: '15.0', + tax: null, + }, + updatedAt: '2024-12-23T08:13:30.314Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts new file mode 100644 index 0000000000..d23cc25e4e --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-product.ts @@ -0,0 +1,124 @@ +import { hubspotAuth } from '../../'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import { + createTrigger, + PiecePropValueSchema, + Property, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { MarkdownVariant } from '@activepieces/shared'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; +import dayjs from 'dayjs'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + // Extract properties once to avoid recomputation + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultProductProperties = getDefaultPropertiesForObject(OBJECT_TYPE.PRODUCT); + const propertiesToRetrieve = [...defaultProductProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.products.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newProductTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-product', + displayName: 'New Product', + description: 'Triggers when new product is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + createdate, description, name, price, tax, hs_lastmodifieddate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.PRODUCT, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-22T10:43:01.920Z', + archived: false, + id: '17727139749', + properties: { + createdate: '2024-12-22T10:43:01.920Z', + description: null, + hs_lastmodifieddate: '2024-12-23T08:13:30.506Z', + hs_object_id: '17727139749', + name: 'TEST', + price: '20.0', + tax: null, + }, + updatedAt: '2024-12-23T08:13:30.506Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-task-added.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-task-added.ts deleted file mode 100644 index f4381dc9d3..0000000000 --- a/packages/pieces/community/hubspot/src/lib/triggers/new-task-added.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - OAuth2PropertyValue, - createTrigger, -} from '@activepieces/pieces-framework'; -import { TriggerStrategy } from '@activepieces/pieces-framework'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; -import { hubSpotClient } from '../common/client'; -import dayjs from 'dayjs'; -import { hubspotAuth } from '../..'; - -const polling: Polling = { - strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, lastFetchEpochMS }) => { - const currentValues = - ( - await hubSpotClient.tasks.getTasksAfterLastSearch( - auth.access_token, - lastFetchEpochMS - ) - ).results ?? []; - const items = currentValues.map((item: { createdAt: string }) => ({ - epochMilliSeconds: dayjs(item.createdAt).valueOf(), - data: item, - })); - return items; - }, -}; - -export const newTaskAdded = createTrigger({ - auth: hubspotAuth, - name: 'new_task', - displayName: 'New Task', - description: 'Trigger when a new task is added.', - props: {}, - type: TriggerStrategy.POLLING, - onEnable: async (context) => { - await pollingHelper.onEnable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - onDisable: async (context) => { - await pollingHelper.onDisable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - run: async (context) => { - return await pollingHelper.poll(polling, context); - }, - test: async (context) => { - return await pollingHelper.test(polling, context); - }, - - sampleData: { - results: [ - { - id: '18156543966', - properties: { - hs_created_by: '5605286', - hs_created_by_user_id: '5605286', - hs_createdate: '2023-06-13T09:42:37.557Z', - hs_modified_by: '5605286', - hs_num_associated_companies: '0', - hs_num_associated_contacts: '1', - hs_num_associated_deals: '0', - hs_num_associated_tickets: '0', - hs_product_name: null, - hs_read_only: null, - hs_repeat_status: null, - hs_task_body: null, - hs_task_completion_count: '0', - hs_task_completion_date: null, - hs_task_is_all_day: 'false', - hs_task_is_completed: '0', - hs_task_is_completed_call: '0', - hs_task_is_completed_email: '0', - hs_task_is_completed_linked_in: '0', - hs_task_is_completed_sequence: '0', - hs_task_repeat_interval: null, - hs_task_status: 'NOT_STARTED', - hs_task_subject: 'My Test Task', - hs_task_type: 'TODO', - hs_updated_by_user_id: '5605286', - hubspot_owner_id: '1041576162', - hs_timestamp: '2023-06-16T05:00:00Z', - }, - createdAt: '2023-06-13T09:42:37.557Z', - updatedAt: '2023-06-13T10:03:41.073Z', - archived: false, - }, - ], - }, -}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts new file mode 100644 index 0000000000..fa903ab9b6 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-task.ts @@ -0,0 +1,145 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; + +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultTaskProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TASK); + const propertiesToRetrieve = [...defaultTaskProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.objects.tasks.searchApi.doSearch({ + limit: isTest ? 10 : 100, + after, + properties: propertiesToRetrieve, + sorts: ['-hs_createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'hs_createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newTaskTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-task', + displayName: 'New Task', + description: 'Trigger when a new task is added.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + hs_task_subject, hs_task_type, hs_task_priority, hubspot_owner_id, hs_timestamp, hs_queue_membership_ids, hs_lastmodifieddate,hs_createdate + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TASK, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + results: [ + { + id: '18156543966', + properties: { + hs_created_by: '5605286', + hs_created_by_user_id: '5605286', + hs_createdate: '2023-06-13T09:42:37.557Z', + hs_modified_by: '5605286', + hs_num_associated_companies: '0', + hs_num_associated_contacts: '1', + hs_num_associated_deals: '0', + hs_num_associated_tickets: '0', + hs_product_name: null, + hs_read_only: null, + hs_repeat_status: null, + hs_task_body: null, + hs_task_completion_count: '0', + hs_task_completion_date: null, + hs_task_is_all_day: 'false', + hs_task_is_completed: '0', + hs_task_is_completed_call: '0', + hs_task_is_completed_email: '0', + hs_task_is_completed_linked_in: '0', + hs_task_is_completed_sequence: '0', + hs_task_repeat_interval: null, + hs_task_status: 'NOT_STARTED', + hs_task_subject: 'My Test Task', + hs_task_type: 'TODO', + hs_updated_by_user_id: '5605286', + hubspot_owner_id: '1041576162', + hs_timestamp: '2023-06-16T05:00:00Z', + }, + createdAt: '2023-06-13T09:42:37.557Z', + updatedAt: '2023-06-13T10:03:41.073Z', + archived: false, + }, + ], + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-added.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-added.ts deleted file mode 100644 index 16018eace9..0000000000 --- a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-added.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - OAuth2PropertyValue, - createTrigger, -} from '@activepieces/pieces-framework'; -import { TriggerStrategy } from '@activepieces/pieces-framework'; -import { - DedupeStrategy, - Polling, - pollingHelper, -} from '@activepieces/pieces-common'; - -import { hubSpotAuthentication } from '../common/props'; -import { hubSpotClient } from '../common/client'; -import dayjs from 'dayjs'; - -const polling: Polling> = { - strategy: DedupeStrategy.TIMEBASED, - items: async ({ auth, lastFetchEpochMS }) => { - const currentValues = - ( - await hubSpotClient.searchTickets(auth.access_token, { - createdAt: lastFetchEpochMS, - }) - ).results ?? []; - const items = currentValues.map((item: { createdAt: string }) => ({ - epochMilliSeconds: dayjs(item.createdAt).valueOf(), - data: item, - })); - return items; - }, -}; - -export const newTicketAdded = createTrigger({ - name: 'new_ticket', - auth: hubSpotAuthentication, - displayName: 'New Ticket Added', - description: 'Trigger when a new ticket is added.', - props: {}, - type: TriggerStrategy.POLLING, - onEnable: async ({ store, propsValue, auth }) => { - await pollingHelper.onEnable(polling, { - auth, - store: store, - propsValue: propsValue, - }); - }, - onDisable: async ({ store, propsValue, auth }) => { - await pollingHelper.onDisable(polling, { - auth, - store: store, - propsValue: propsValue, - }); - }, - run: async (context) => { - return await pollingHelper.poll(polling, context); - }, - test: async (context) => { - return await pollingHelper.test(polling, context); - }, - - sampleData: {}, -}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts new file mode 100644 index 0000000000..e5677d6c94 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket-property-change.ts @@ -0,0 +1,163 @@ +import { hubspotAuth } from '../..'; +import { + createTrigger, + PiecePropValueSchema, + TriggerStrategy, +} from '@activepieces/pieces-framework'; +import { standardObjectPropertiesDropdown } from '../common/props'; +import { OBJECT_TYPE } from '../common/constants'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { Client } from '@hubspot/api-client'; +import dayjs from 'dayjs'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + propertyName?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const propertyToCheck = propsValue.propertyName as string; + + const propertiesToRetrieve = [propertyToCheck]; + + const items = []; + // For test, we only fetch 10 tickets + if (lastFetchEpochMS === 0) { + const response = await client.crm.tickets.searchApi.doSearch({ + limit: 10, + properties: propertiesToRetrieve, + sorts: ['-hs_lastmodifieddate'], + }); + items.push(...response.results); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + } + //fetch updated tickets + const updatedTickets = []; + let after; + do { + const response = await client.crm.tickets.searchApi.doSearch({ + limit: 100, + sorts: ['-hs_lastmodifieddate'], + after, + filterGroups: [ + { + filters: [ + { + propertyName: propertyToCheck, + operator: FilterOperatorEnum.HasProperty, + }, + { + propertyName: 'hs_lastmodifieddate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + updatedTickets.push(...response.results); + } while (after); + + if (updatedTickets.length === 0) { + return []; + } + + // Fetch tickets with property history + const updatedTicketsWithPropertyHistory = await client.crm.tickets.batchApi.read({ + propertiesWithHistory: [propertyToCheck], + properties: propertiesToRetrieve, + inputs: updatedTickets.map((ticket) => { + return { + id: ticket.id, + }; + }), + }); + + for (const ticket of updatedTicketsWithPropertyHistory.results) { + const history = ticket.propertiesWithHistory?.[propertyToCheck]; + if (!history || history.length === 0) { + continue; + } + const propertyLastModifiedDateTimeStamp = dayjs(history[0].timestamp).valueOf(); + if (propertyLastModifiedDateTimeStamp > lastFetchEpochMS) { + const { propertiesWithHistory, ...item } = ticket; + items.push(item); + } + } + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['hs_lastmodifieddate']).valueOf(), + data: item, + })); + }, +}; + +export const newTicketPropertyChangeTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-ticket-property-change', + displayName: 'New Ticket Property Change', + description: 'Triggers when a specified property is updated on a ticket.', + props: { + propertyName: standardObjectPropertiesDropdown( + { + objectType: OBJECT_TYPE.TICKET, + displayName: 'Property Name', + required: true, + }, + true, + true, + ), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + createdAt: '2024-12-21T14:23:38.368Z', + archived: false, + id: '18092693102', + properties: { + content: null, + createdate: '2024-12-21T14:23:38.368Z', + hs_lastmodifieddate: '2024-12-26T08:11:34.374Z', + hs_object_id: '18092693102', + hs_pipeline: '0', + hs_pipeline_stage: '1', + hs_resolution: 'ISSUE_FIXED', + hs_ticket_category: null, + hs_ticket_id: '18092693102', + hs_ticket_priority: null, + hubspot_owner_id: null, + hubspot_team_id: null, + source_type: null, + subject: 'NEW', + }, + updatedAt: '2024-12-26T08:11:34.374Z', + }, +}); diff --git a/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts new file mode 100644 index 0000000000..38dcee4650 --- /dev/null +++ b/packages/pieces/community/hubspot/src/lib/triggers/new-ticket.ts @@ -0,0 +1,128 @@ +import { PiecePropValueSchema, Property, createTrigger } from '@activepieces/pieces-framework'; +import { TriggerStrategy } from '@activepieces/pieces-framework'; +import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; + +import { getDefaultPropertiesForObject, standardObjectPropertiesDropdown } from '../common/props'; +import dayjs from 'dayjs'; +import { hubspotAuth } from '../..'; +import { MarkdownVariant } from '@activepieces/shared'; +import { OBJECT_TYPE } from '../common/constants'; +import { Client } from '@hubspot/api-client'; +import { FilterOperatorEnum } from '../common/types'; + +type Props = { + additionalPropertiesToRetrieve?: string | string[]; +}; + +const polling: Polling, Props> = { + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const client = new Client({ accessToken: auth.access_token }); + + const additionalProperties = propsValue.additionalPropertiesToRetrieve ?? []; + const defaultTicketProperties = getDefaultPropertiesForObject(OBJECT_TYPE.TICKET); + const propertiesToRetrieve = [...defaultTicketProperties, ...additionalProperties]; + + const items = []; + let after; + + do { + const isTest = lastFetchEpochMS === 0; + const response = await client.crm.tickets.searchApi.doSearch({ + limit: isTest ? 10 : 100, + properties: propertiesToRetrieve, + after, + sorts: ['-createdate'], + filterGroups: isTest + ? [] + : [ + { + filters: [ + { + propertyName: 'createdate', + operator: FilterOperatorEnum.Gt, + value: lastFetchEpochMS.toString(), + }, + ], + }, + ], + }); + after = response.paging?.next?.after; + items.push(...response.results); + + // Stop fetching if it's a test + if (isTest) break; + } while (after); + + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.properties['createdate']).valueOf(), + data: item, + })); + }, +}; + +export const newTicketTrigger = createTrigger({ + auth: hubspotAuth, + name: 'new-ticket', + displayName: 'New Ticket', + description: 'Trigger when new deal is available.', + props: { + markdown: Property.MarkDown({ + variant: MarkdownVariant.INFO, + value: `### Properties to retrieve: + + subject, content, source_type, createdate, hs_pipeline, hs_pipeline_stage, hs_resolution, hs_ticket_category, hs_ticket_id, hs_ticket_priority, hs_lastmodifieddate, hubspot_owner_id, hubspot_team_id + + **Specify here a list of additional properties to retrieve**`, + }), + additionalPropertiesToRetrieve: standardObjectPropertiesDropdown({ + objectType: OBJECT_TYPE.TICKET, + displayName: 'Additional properties to retrieve', + required: false, + }), + }, + type: TriggerStrategy.POLLING, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + + sampleData: { + createdAt: '2024-12-26T08:40:12.881Z', + archived: false, + id: '18166565782', + properties: { + content: null, + createdate: '2024-12-26T08:40:12.881Z', + hs_lastmodifieddate: '2024-12-26T08:40:14.245Z', + hs_object_id: '18166565782', + hs_pipeline: '0', + hs_pipeline_stage: '1', + hs_resolution: null, + hs_ticket_category: null, + hs_ticket_id: '18166565782', + hs_ticket_priority: null, + hubspot_owner_id: '1594636734', + hubspot_team_id: '55094099', + source_type: null, + subject: 'test', + }, + updatedAt: '2024-12-26T08:40:14.245Z', + }, +});