Hi,
I am trying to figure out how to work with dealstage from the event object in custom code actions. I am receiving the following logs:
2023-11-01T15:40:53.378Z INFO {"timestamp":"2023-11-01T15:40:53.378Z","message":"Event received","data":{"event":{"callbackId":"a7d31b0f-945a-42d0-9fa5-e9e0aa8e08ad","origin":{"portalId":20745819,"actionDefinitionId":48171846,"actionDefinitionVersion":0,"actionExecutionIndexIdentifier":null,"extensionDefinitionId":48171846,"extensionDefinitionVersionId":0},"object":{"objectId":15796334605,"objectType":"DEAL"},"fields":{"hs_object_id":"15796334605","DEALSTAGE":"115192911"},"inputFields":{"hs_object_id":"15796334605","DEALSTAGE":"115192911"}}}}
2023-11-01T15:40:53.380Z INFO {"timestamp":"2023-11-01T15:40:53.380Z","message":"Deal object","data":{"deal":{"objectId":15796334605,"objectType":"DEAL"}}}
2023-11-01T15:40:53.380Z INFO {"timestamp":"2023-11-01T15:40:53.380Z","message":"INFO","data":"Deal object"}
2023-11-01T15:40:53.402Z ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"TypeError: Cannot read properties of undefined (reading 'DEALSTAGE')","reason":{"errorType":"TypeError","errorMessage":"Cannot read properties of undefined (reading 'DEALSTAGE')","stack":["TypeError: Cannot read properties of undefined (reading 'DEALSTAGE')"," at initiateAndProcessDeal (/var/task/file.js:160:43)"," at Object.exports.main (/var/task/file.js:300:9)"," at Runtime.exports.hubspot_handler [as handler] (/var/task/hubspotHandler.js:6:21)"," at Runtime.handleOnceNonStreaming (file:///var/runtime/index.mjs:1173:29)"]},"promise":{},"stack":["Runtime.UnhandledPromiseRejection: TypeError: Cannot read properties of undefined (reading 'DEALSTAGE')"," at process.<anonymous> (file:///var/runtime/index.mjs:1276:17)"," at process.emit (node:events:513:28)"," at emit (node:internal/process/promises:140:20)"," at processPromiseRejections (node:internal/process/promises:274:27)"," at processTicksAndRejections (node:internal/process/task_queues:97:32)"]}
Unknown application error occurred
Runtime.Unknown
Memory: 67/128 MB
Runtime: 84.48 ms
My code is as follows:
// Importing necessary library
const axios = require('axios');
// Base URL for HubSpot's APIs
const HUBSPOT_API_BASE_URL = 'https://api.hubapi.com';
function logEvent(message, data) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
message,
data
}));
}
// Centralized error handling function
function handleError(error, context = {}) {
logEvent('Error occurred', {
message: error.message,
stack: error.stack,
responseData: error.response ? {
data: error.response.data,
status: error.response.status,
headers: error.response.headers,
config: error.config
} : null,
...context // Additional context data
});
}
// Function to retrieve stage details from HubSpot
async function getDealstageDetails(pipelineId, stageId, API_KEY) {
if (!pipelineId || !stageId) {
logEvent('PipelineId or StageId is undefined or null');
throw new Error('Invalid input parameters');
}
// Ensure the objectType is 'deals'
const objectType = 'deals';
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/pipelines/${objectType}/${pipelineId}`;
const config = {
headers: {
'Authorization': `Bearer ${API_KEY}`
}
};
try {
logEvent('Sending request to retrieve pipeline details', { url, config });
const response = await axios.get(url, config);
logEvent('Response received for retrieving pipeline details', { responseData: response.data });
// Find the desired stage from the response
const stageDetails = response.data.stages.find(stage => stage.stageId === stageId);
if(!stageDetails) {
logEvent('Stage not found in the pipeline', { stageId });
throw new Error('Stage not found');
}
return stageDetails;
} catch (error) {
handleError(error, { url, config });
throw error;
}
}
async function processDeal(deal, pipelineId, dealstage, lineItems, API_KEY) {
logEvent('Processing deal', { dealId: deal.objectId });
logEvent('Dealstage details', { pipeline: pipelineId, stage: dealstage });
logEvent('Type of dealstage', { type: typeof dealstage }); // Log the data type of dealstage
// Coerce dealstage to a string for the comparison
if (pipelineId === '57968926' && String(dealstage) === '115192911') {
logEvent('Processing deal in Impact EMS Wordpress Sync pipeline, Completed stage');
logEvent('Total line items', { lineItemCount: lineItems.length });
for (const [index, lineItem] of lineItems.entries()) {
logEvent('Processing line item', { lineItemIndex: index, lineItemId: lineItem.id, lineItemSKU: lineItem.sku });
try {
const classObj = await findClassBySKU(lineItem.sku, API_KEY);
if (classObj) {
logEvent('Class found for SKU', { classObjId: classObj.id });
await processLineItem(lineItem, deal, API_KEY);
logEvent('Line item processed successfully', { lineItemIndex: index, lineItemId: lineItem.id });
} else {
logEvent('No class found for SKU', { lineItemSKU: lineItem.sku });
}
} catch (error) {
logEvent('Error processing line item', { lineItemIndex: index, lineItemId: lineItem.id, error: error.toString(), stack: error.stack });
}
}
} else {
logEvent('Deal not in Impact EMS Wordpress Sync pipeline or not in Completed stage', { pipelineId, dealstage: String(dealstage) }); // Coerce dealstage to a string for logging
}
}
// Function to process a line item
async function processLineItem(lineItem, deal, API_KEY) {
logEvent('INFO: Processing line item', { lineItemId: lineItem.id, lineItemSKU: lineItem.sku }); // Modified logEvent
// Removing the condition that checks for 'in-person' in the SKU
logEvent('INFO: Calling findClassBySKU', { lineItemSKU: lineItem.sku }); // Modified logEvent
const classObj = await findClassBySKU(lineItem.sku, API_KEY);
logEvent('INFO: Returned from findClassBySKU', { classObj }); // Modified logEvent
if (classObj) {
await createAssociations(deal, classObj, API_KEY);
} else {
logEvent('No class object returned for SKU', { lineItemSKU: lineItem.sku }); // Logging when no class object is found
}
}
// Function to create associations
async function createAssociations(deal, classObj, API_KEY) {
logEvent('Class object found', { classObjId: classObj.id });
try {
logEvent('Attempting to create Deal to Class association', { dealId: deal.objectId, classObjId: classObj.id }); // Log attempt to create association
const dealToClassResponse = await createAssociation({type: 'deals', id: deal.objectId}, {type: 'classes', id: classObj.id}, 'Deal to Class', API_KEY);
logEvent('Response for creating Deal to Class association', { responseData: dealToClassResponse });
logEvent('Attempting to create Contact to Class association', { contactId: deal.contact.id, classObjId: classObj.id }); // Log attempt to create association
const contactToClassResponse = await createAssociation({type: 'contacts', id: deal.contact.id}, {type: 'classes', id: classObj.id}, 'Contact to Class', API_KEY);
logEvent('Response for creating Contact to Class association', { responseData: contactToClassResponse });
if (deal.company) {
logEvent('Attempting to create Company to Class association', { companyId: deal.company.id, classObjId: classObj.id }); // Log attempt to create association
const companyToClassResponse = await createAssociation({type: 'companies', id: deal.company.id}, {type: 'classes', id: classObj.id}, 'Company to Class', API_KEY);
logEvent('Response for creating Company to Class association', { responseData: companyToClassResponse });
}
const trainingSessions = await findTrainingSessionsByClass(classObj, API_KEY);
for (const session of trainingSessions) {
logEvent('Attempting to create Contact to Training Session association', { contactId: deal.contact.id, sessionId: session.id }); // Log attempt to create association
const contactToSessionResponse = await createAssociation({type: 'contacts', id: deal.contact.id}, {type: 'training-sessions', id: session.id}, 'Contact to Training Session', API_KEY);
logEvent('Response for creating Contact to Training Session association', { responseData: contactToSessionResponse });
}
} catch (error) {
logEvent('Error in createAssociations', { error: error.toString(), stack: error.stack });
throw error; // re-throw the error after logging it
}
}
async function initiateAndProcessDeal(event, API_KEY) {
const deal = event.object;
logEvent('INFO', 'Deal object', { deal: JSON.stringify(deal, null, 2) });
const pipelineId = process.env.PIPELINE_ID;
const dealstageId = event.inputFields.DEALSTAGE; // Adjusted this line
try {
// Get deal stage details
const dealstage = await getDealstageDetails(pipelineId, dealstageId, API_KEY);
const lineItems = await retrieveLineItems(deal.objectId, API_KEY);
logEvent('Processing deal', { dealId: deal.objectId });
logEvent('Dealstage details', { pipeline: pipelineId, stage: dealstage });
logEvent('Type of dealstage', { type: typeof dealstage });
logEvent('Processing deal in Impact EMS Wordpress Sync pipeline, Completed stage');
logEvent('Total line items', { lineItemCount: lineItems.length });
for (const [index, lineItem] of lineItems.entries()) {
logEvent('Processing line item', { lineItemIndex: index, lineItemId: lineItem.id, lineItemSKU: lineItem.sku });
try {
const classObj = await findClassBySKU(lineItem.sku, API_KEY);
if (classObj) {
logEvent('Class found for SKU', { classObjId: classObj.id });
await processLineItem(lineItem, deal, API_KEY);
logEvent('Line item processed successfully', { lineItemIndex: index, lineItemId: lineItem.id });
} else {
logEvent('No class found for SKU', { lineItemSKU: lineItem.sku });
}
} catch (error) {
logEvent('Error processing line item', { lineItemIndex: index, lineItemId: lineItem.id, error: error.toString(), stack: error.stack });
}
}
} catch (error) {
handleError(error);
}
}
// Function to retrieve line items associated with a deal using CRM Associations API
async function retrieveLineItems(dealId, API_KEY) {
// URL for CRM Associations API to retrieve associated line items for a deal
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/associations/deals/line_item/batch/read`;
// Data payload for the POST request
const data = {
inputs: [{ id: dealId }] // Providing the dealId to retrieve associated line items
};
// Configuration for the axios request
const config = {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
};
// Log the request details
logEvent('Request Details', {
url: url,
method: 'POST',
headers: config.headers,
data: data
});
try {
logEvent('Sending request to retrieve line items', { url, data });
const response = await axios.post(url, data, config); // Sending POST request to CRM Associations API
logEvent('Response received for retrieving line items', { responseData: response.data });
// Assuming that the response contains the associated line items in a property named 'results'
return response.data.results;
} catch (error) {
handleError(error, { url, config }); // Adjusted to use centralized error handling function
throw error;
}
}
// findClassBySKU function
async function findClassBySKU(sku, API_KEY) {
if (!sku) {
logEvent('SKU is undefined or null');
return null;
}
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/classes/search`;
const data = {
filterGroups: [{
filters: [{
value: sku,
propertyName: 'sku',
operator: 'EQ'
}]
}]
};
const config = {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
};
try {
logEvent('Sending request to find class by SKU', { url, data });
const response = await axios.post(url, data, config);
logEvent('Response received for finding class by SKU', { responseData: response.data });
return response.data.results[0]; // Assuming each SKU corresponds to at most one class
} catch (error) {
logEvent('Error finding class by SKU', { error: error.toString(), stack: error.stack });
throw error;
}
}
// exports.main function
exports.main = async (event, callback) => {
const startTime = Date.now();
logEvent('Event received', { event });
const API_KEY = process.env.billing_contacts_secret;
const deal = event.object;
logEvent('Deal object', { deal });
if (!deal.objectId) {
logEvent('deal.id is undefined');
return callback(new Error('deal.id is undefined'));
}
// Initiate SKU search and process the deal
await initiateAndProcessDeal(deal, API_KEY);
callback();
const endTime = Date.now();
logEvent('Operation completed', { durationMs: endTime - startTime });
};
// createAssociation function
async function createAssociation(object1, object2, associationType, API_KEY) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v4/objects/${object1.type}/${object1.id}/associations/${object2.type}/${object2.id}/default`;
const config = {
method: 'PUT', // Updated method to PUT as per v4 documentation
headers: {
'Authorization': `Bearer ${API_KEY}`
}
};
try {
logEvent('Sending request to create association', { url, config });
const response = await axios(url, config);
logEvent('Response received for creating association', { responseData: response.data });
return response.data;
} catch (error) {
logEvent('Error creating association', { error: error.toString(), stack: error.stack });
throw error;
}
}
// checkExistingAssociation function
async function checkExistingAssociation(object1, object2, associationType, API_KEY) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/associations/${object1.type}/${object2.type}`;
const config = {
headers: {
'Authorization': 'Bearer ${API_KEY}'
},
params: {
id: object1.id
}
};
try {
logEvent('Sending request to check existing association', { url, config });
const response = await axios.get(url, config);
logEvent('Response received for checking existing association', { responseData: response.data });
return response.data.results.some(association => association.id === object2.id && association.type === associationType);
} catch (error) {
logEvent('Error checking existing association', { error: error.toString(), stack: error.stack });
throw error;
}
}
// findTrainingSessionsByClass function
async function findTrainingSessionsByClass(classObj, API_KEY) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/training-sessions/search`;
const data = {
filterGroups: [{
filters: [{
propertyName: 'classId',
operator: 'EQ',
value: classObj.id
}]
}]
};
const config = {
headers: {
'Authorization': 'Bearer ${API_KEY}'
}
};
try {
logEvent('Sending request to find training sessions by class', { url, data });
const response = await axios.post(url, data, config);
logEvent('Response received for finding training sessions by class', { responseData: response.data });
return response.data.results;
} catch (error) {
logEvent('Error finding training sessions by class', { error: error.toString(), stack: error.stack });
throw error;
}
}
Thank you