Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon, addRowToSmartsheet } from '../common';
|
||||
|
||||
export const addRowToSheet = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'add_row_to_sheet',
|
||||
displayName: 'Add Row to Sheet',
|
||||
description:'Adds new row to a sheet.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
cells: smartsheetCommon.cells,
|
||||
location_type: Property.StaticDropdown({
|
||||
displayName: 'Add Row to Top or Bottom',
|
||||
required: true,
|
||||
defaultValue: 'bottom',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Top of sheet', value: 'top' },
|
||||
{ label: 'Bottom of sheet', value: 'bottom' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const { sheet_id, cells, location_type } = context.propsValue;
|
||||
|
||||
// Transform dynamic cells data into proper Smartsheet format
|
||||
const cellsData = cells as Record<string, any>;
|
||||
const transformedCells: any[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(cellsData)) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
continue; // Skip empty values
|
||||
}
|
||||
|
||||
let columnId: number;
|
||||
const cellObj: any = {};
|
||||
|
||||
if (key.startsWith('column_')) {
|
||||
// Regular column value
|
||||
columnId = parseInt(key.replace('column_', ''));
|
||||
cellObj.columnId = columnId;
|
||||
cellObj.value = value;
|
||||
} else {
|
||||
continue; // Skip unknown keys
|
||||
}
|
||||
|
||||
transformedCells.push(cellObj);
|
||||
}
|
||||
|
||||
if (transformedCells.length === 0) {
|
||||
throw new Error('At least one cell value must be provided');
|
||||
}
|
||||
|
||||
// Build the row object with location specifiers
|
||||
const rowObj: any = {
|
||||
cells: transformedCells,
|
||||
};
|
||||
|
||||
// Add location specifiers based on location_type
|
||||
switch (location_type) {
|
||||
case 'top':
|
||||
rowObj.toTop = true;
|
||||
break;
|
||||
case 'bottom':
|
||||
rowObj.toBottom = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const rowPayload = [rowObj];
|
||||
|
||||
try {
|
||||
const result = await addRowToSmartsheet(
|
||||
context.auth.secret_text,
|
||||
sheet_id as string,
|
||||
rowPayload,
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
row: result,
|
||||
message: 'Row added successfully',
|
||||
cells_processed: transformedCells.length,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid row data or parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to add rows to this sheet');
|
||||
} else if (error.response?.status === 404) {
|
||||
throw new Error('Sheet not found or you do not have access to it');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to add row: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,204 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon } from '../common';
|
||||
|
||||
export const attachFileToRow = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'attach_file_to_row',
|
||||
displayName: 'Attach File to Row',
|
||||
description: 'Adds a file attachment to a row.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
row_id: smartsheetCommon.row_id,
|
||||
|
||||
attachment_type: Property.StaticDropdown({
|
||||
displayName: 'Attachment Type',
|
||||
description: 'Type of attachment to add',
|
||||
required: true,
|
||||
defaultValue: 'FILE',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'File Upload', value: 'FILE' },
|
||||
{ label: 'URL Link', value: 'LINK' },
|
||||
{ label: 'Box.com', value: 'BOX_COM' },
|
||||
{ label: 'Dropbox', value: 'DROPBOX' },
|
||||
{ label: 'Egnyte', value: 'EGNYTE' },
|
||||
{ label: 'Evernote', value: 'EVERNOTE' },
|
||||
{ label: 'Google Drive', value: 'GOOGLE_DRIVE' },
|
||||
{ label: 'OneDrive', value: 'ONEDRIVE' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
// For file uploads
|
||||
file: Property.File({
|
||||
displayName: 'File',
|
||||
description: 'The file to attach (required for FILE type)',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
// For URL attachments
|
||||
url: Property.ShortText({
|
||||
displayName: 'URL',
|
||||
description: 'The URL to attach (required for URL-based attachment types)',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
attachment_name: Property.ShortText({
|
||||
displayName: 'Attachment Name',
|
||||
description: 'Name for the attachment (optional, will use file name or URL if not provided)',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
// Advanced options
|
||||
attachment_sub_type: Property.StaticDropdown({
|
||||
displayName: 'Attachment Sub Type',
|
||||
description: 'Sub type for Google Drive and Egnyte attachments',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Document', value: 'DOCUMENT' },
|
||||
{ label: 'Drawing', value: 'DRAWING' },
|
||||
{ label: 'Folder', value: 'FOLDER' },
|
||||
{ label: 'PDF', value: 'PDF' },
|
||||
{ label: 'Presentation', value: 'PRESENTATION' },
|
||||
{ label: 'Spreadsheet', value: 'SPREADSHEET' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
mime_type: Property.ShortText({
|
||||
displayName: 'MIME Type',
|
||||
description: 'MIME type of the attachment (optional, auto-detected for files)',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const {
|
||||
sheet_id,
|
||||
row_id,
|
||||
attachment_type,
|
||||
file,
|
||||
url,
|
||||
attachment_name,
|
||||
attachment_sub_type,
|
||||
mime_type,
|
||||
} = context.propsValue;
|
||||
|
||||
// Validate input based on attachment type
|
||||
if (attachment_type === 'FILE') {
|
||||
if (!file) {
|
||||
throw new Error('File is required when attachment type is FILE');
|
||||
}
|
||||
} else {
|
||||
if (!url) {
|
||||
throw new Error('URL is required for URL-based attachment types');
|
||||
}
|
||||
|
||||
// Validate URL format for specific types
|
||||
if (attachment_type === 'BOX_COM' && !url.includes('box.com')) {
|
||||
throw new Error('Box.com URLs should contain "box.com" in the domain');
|
||||
}
|
||||
if (attachment_type === 'DROPBOX' && !url.includes('dropbox.com')) {
|
||||
throw new Error('Dropbox URLs should contain "dropbox.com" in the domain');
|
||||
}
|
||||
if (attachment_type === 'GOOGLE_DRIVE' && !url.includes('drive.google.com')) {
|
||||
throw new Error('Google Drive URLs should contain "drive.google.com" in the domain');
|
||||
}
|
||||
if (attachment_type === 'ONEDRIVE' && !url.includes('onedrive')) {
|
||||
throw new Error('OneDrive URLs should contain "onedrive" in the domain');
|
||||
}
|
||||
}
|
||||
|
||||
const apiUrl = `${smartsheetCommon.baseUrl}/sheets/${sheet_id}/rows/${row_id}/attachments`;
|
||||
|
||||
try {
|
||||
let request: HttpRequest;
|
||||
|
||||
if (attachment_type === 'FILE') {
|
||||
// File upload using multipart/form-data
|
||||
const formData = new FormData();
|
||||
|
||||
// Determine MIME type
|
||||
const fileMimeType = mime_type || (file?.extension ? `application/${file.extension}` : 'application/octet-stream');
|
||||
const fileName = attachment_name || file?.filename || 'attachment';
|
||||
|
||||
// Create blob with proper MIME type
|
||||
const blob = new Blob([file!.data as unknown as ArrayBuffer], { type: fileMimeType });
|
||||
formData.append('file', blob, fileName);
|
||||
|
||||
request = {
|
||||
method: HttpMethod.POST,
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${context.auth}`,
|
||||
// Don't set Content-Type for FormData, let the browser set it with boundary
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
} else {
|
||||
// URL attachment using JSON
|
||||
const attachmentData: any = {
|
||||
attachmentType: attachment_type,
|
||||
url: url,
|
||||
};
|
||||
|
||||
if (attachment_name) {
|
||||
attachmentData.name = attachment_name;
|
||||
}
|
||||
|
||||
if (attachment_sub_type) {
|
||||
attachmentData.attachmentSubType = attachment_sub_type;
|
||||
}
|
||||
|
||||
if (mime_type) {
|
||||
attachmentData.mimeType = mime_type;
|
||||
}
|
||||
|
||||
request = {
|
||||
method: HttpMethod.POST,
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${context.auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: attachmentData,
|
||||
};
|
||||
}
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
attachment: response.body.result,
|
||||
message: 'Attachment added successfully',
|
||||
attachment_id: response.body.result?.id,
|
||||
attachment_type: response.body.result?.attachmentType,
|
||||
attachment_name: response.body.result?.name,
|
||||
size_kb: response.body.result?.sizeInKb,
|
||||
created_at: response.body.result?.createdAt,
|
||||
created_by: response.body.result?.createdBy,
|
||||
version: response.body.version,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid attachment data or parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to add attachments to this sheet');
|
||||
} else if (error.response?.status === 404) {
|
||||
throw new Error('Sheet or row not found or you do not have access to it');
|
||||
} else if (error.response?.status === 413) {
|
||||
throw new Error('File size too large. Check Smartsheet file size limits for your plan.');
|
||||
} else if (error.response?.status === 415) {
|
||||
throw new Error('Unsupported media type. Check file format restrictions.');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to attach file: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,259 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon } from '../common';
|
||||
|
||||
export const findAttachmentByRowId = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'find_attachment_by_row_id',
|
||||
displayName: 'List Row Attachments',
|
||||
description: 'Get all attachments for a specific row in a Smartsheet, including row and discussion-level attachments with comprehensive pagination and filtering options',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
row_id: smartsheetCommon.row_id,
|
||||
|
||||
// Pagination options
|
||||
include_all: Property.Checkbox({
|
||||
displayName: 'Include All Results',
|
||||
description: 'If true, include all results without pagination (overrides page and page size)',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
page: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
description: 'Which page to return (defaults to 1, ignored if "Include All Results" is true)',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Maximum number of items to return per page (defaults to 100, max 10000, ignored if "Include All Results" is true)',
|
||||
required: false,
|
||||
defaultValue: 100,
|
||||
}),
|
||||
|
||||
// Filtering options
|
||||
attachment_type_filter: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Filter by Attachment Type',
|
||||
description: 'Only return attachments of specific types (leave empty for all types)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Files', value: 'FILE' },
|
||||
{ label: 'URLs/Links', value: 'LINK' },
|
||||
{ label: 'Box.com', value: 'BOX_COM' },
|
||||
{ label: 'Dropbox', value: 'DROPBOX' },
|
||||
{ label: 'Egnyte', value: 'EGNYTE' },
|
||||
{ label: 'Evernote', value: 'EVERNOTE' },
|
||||
{ label: 'Google Drive', value: 'GOOGLE_DRIVE' },
|
||||
{ label: 'OneDrive', value: 'ONEDRIVE' },
|
||||
{ label: 'Trello', value: 'TRELLO' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
parent_type_filter: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Filter by Parent Type',
|
||||
description: 'Only return attachments from specific parent types (leave empty for all)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Row Attachments', value: 'ROW' },
|
||||
{ label: 'Comment Attachments', value: 'COMMENT' },
|
||||
{ label: 'Sheet Attachments', value: 'SHEET' },
|
||||
{ label: 'Proof Attachments', value: 'PROOF' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
min_file_size_kb: Property.Number({
|
||||
displayName: 'Minimum File Size (KB)',
|
||||
description: 'Only return files with size greater than or equal to this value (applies to FILE type only)',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
max_file_size_kb: Property.Number({
|
||||
displayName: 'Maximum File Size (KB)',
|
||||
description: 'Only return files with size less than or equal to this value (applies to FILE type only)',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const {
|
||||
sheet_id,
|
||||
row_id,
|
||||
include_all,
|
||||
page,
|
||||
page_size,
|
||||
attachment_type_filter,
|
||||
parent_type_filter,
|
||||
min_file_size_kb,
|
||||
max_file_size_kb,
|
||||
} = context.propsValue;
|
||||
|
||||
// Build query parameters
|
||||
const queryParams: any = {};
|
||||
|
||||
if (include_all) {
|
||||
queryParams.includeAll = true;
|
||||
} else {
|
||||
if (page && page > 1) {
|
||||
queryParams.page = page;
|
||||
}
|
||||
if (page_size && page_size !== 100) {
|
||||
queryParams.pageSize = Math.min(page_size, 10000); // Cap at API limit
|
||||
}
|
||||
}
|
||||
|
||||
const apiUrl = `${smartsheetCommon.baseUrl}/sheets/${sheet_id}/rows/${row_id}/attachments`;
|
||||
|
||||
try {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${context.auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
queryParams,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
const attachmentData = response.body;
|
||||
|
||||
// Apply client-side filters
|
||||
let filteredAttachments = attachmentData.data || [];
|
||||
|
||||
// Filter by attachment type
|
||||
if (attachment_type_filter && attachment_type_filter.length > 0) {
|
||||
filteredAttachments = filteredAttachments.filter((attachment: any) =>
|
||||
attachment_type_filter.includes(attachment.attachmentType)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by parent type
|
||||
if (parent_type_filter && parent_type_filter.length > 0) {
|
||||
filteredAttachments = filteredAttachments.filter((attachment: any) =>
|
||||
parent_type_filter.includes(attachment.parentType)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by file size (only applies to FILE type)
|
||||
if (min_file_size_kb !== undefined || max_file_size_kb !== undefined) {
|
||||
filteredAttachments = filteredAttachments.filter((attachment: any) => {
|
||||
if (attachment.attachmentType !== 'FILE' || !attachment.sizeInKb) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size = attachment.sizeInKb;
|
||||
if (min_file_size_kb !== undefined && size < min_file_size_kb) {
|
||||
return false;
|
||||
}
|
||||
if (max_file_size_kb !== undefined && size > max_file_size_kb) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// Organize attachments by type for better analysis
|
||||
const attachmentsByType: any = {};
|
||||
const attachmentsByParent: any = {};
|
||||
let totalFileSize = 0;
|
||||
|
||||
filteredAttachments.forEach((attachment: any) => {
|
||||
// Group by attachment type
|
||||
if (!attachmentsByType[attachment.attachmentType]) {
|
||||
attachmentsByType[attachment.attachmentType] = [];
|
||||
}
|
||||
attachmentsByType[attachment.attachmentType].push(attachment);
|
||||
|
||||
// Group by parent type
|
||||
if (!attachmentsByParent[attachment.parentType]) {
|
||||
attachmentsByParent[attachment.parentType] = [];
|
||||
}
|
||||
attachmentsByParent[attachment.parentType].push(attachment);
|
||||
|
||||
// Calculate total file size for files
|
||||
if (attachment.attachmentType === 'FILE' && attachment.sizeInKb) {
|
||||
totalFileSize += attachment.sizeInKb;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
// Pagination info
|
||||
pagination: {
|
||||
page_number: attachmentData.pageNumber,
|
||||
page_size: attachmentData.pageSize,
|
||||
total_pages: attachmentData.totalPages,
|
||||
total_count: attachmentData.totalCount,
|
||||
filtered_count: filteredAttachments.length,
|
||||
},
|
||||
|
||||
// Main results
|
||||
attachments: filteredAttachments,
|
||||
|
||||
// Organized results
|
||||
attachments_by_type: attachmentsByType,
|
||||
attachments_by_parent: attachmentsByParent,
|
||||
|
||||
// Summary statistics
|
||||
summary: {
|
||||
total_attachments: filteredAttachments.length,
|
||||
files_count: (attachmentsByType.FILE || []).length,
|
||||
links_count: (attachmentsByType.LINK || []).length,
|
||||
cloud_storage_count: filteredAttachments.length -
|
||||
(attachmentsByType.FILE || []).length -
|
||||
(attachmentsByType.LINK || []).length,
|
||||
row_attachments: (attachmentsByParent.ROW || []).length,
|
||||
comment_attachments: (attachmentsByParent.COMMENT || []).length,
|
||||
total_file_size_kb: totalFileSize,
|
||||
total_file_size_mb: Math.round(totalFileSize / 1024 * 100) / 100,
|
||||
},
|
||||
|
||||
// Download info for files
|
||||
download_info: filteredAttachments
|
||||
.filter((att: any) => att.attachmentType === 'FILE' && att.url)
|
||||
.map((att: any) => ({
|
||||
attachment_id: att.id,
|
||||
name: att.name,
|
||||
download_url: att.url,
|
||||
url_expires_in_millis: att.urlExpiresInMillis,
|
||||
url_expires_at: att.urlExpiresInMillis ?
|
||||
new Date(Date.now() + att.urlExpiresInMillis).toISOString() : null,
|
||||
size_kb: att.sizeInKb,
|
||||
})),
|
||||
|
||||
// Applied filters info
|
||||
filters_applied: {
|
||||
attachment_types: attachment_type_filter || [],
|
||||
parent_types: parent_type_filter || [],
|
||||
min_file_size_kb: min_file_size_kb,
|
||||
max_file_size_kb: max_file_size_kb,
|
||||
},
|
||||
|
||||
// Row and sheet info
|
||||
row_id: row_id,
|
||||
sheet_id: sheet_id,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to access attachments for this row');
|
||||
} else if (error.response?.status === 404) {
|
||||
throw new Error('Sheet or row not found, or you do not have access to it');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to retrieve attachments: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon } from '../common';
|
||||
|
||||
export const findRowsByQuery = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'find_rows_by_query',
|
||||
displayName: 'Find Row',
|
||||
description: 'Finds rows in a specific sheet or across all accessible sheets using text queries with advanced filtering options.',
|
||||
props: {
|
||||
search_scope: Property.StaticDropdown({
|
||||
displayName: 'Search Scope',
|
||||
description: 'Choose whether to search within a specific sheet or across all accessible sheets.',
|
||||
required: true,
|
||||
defaultValue: 'specific_sheet',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Specific Sheet', value: 'specific_sheet' },
|
||||
{ label: 'All Accessible Sheets', value: 'all_sheets' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
sheet_id: smartsheetCommon.sheet_id(false),
|
||||
|
||||
query: Property.ShortText({
|
||||
displayName: 'Search Query',
|
||||
description: 'Text to search for. Use double quotes for exact phrase matching (e.g., "project status")',
|
||||
required: true,
|
||||
}),
|
||||
|
||||
// Advanced search options
|
||||
search_scopes: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Search Scopes',
|
||||
description: 'Specify what types of content to search in (leave empty to search all)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Cell Data', value: 'cellData' },
|
||||
{ label: 'Comments', value: 'comments' },
|
||||
{ label: 'Attachments', value: 'attachments' },
|
||||
{ label: 'Sheet Names', value: 'sheetNames' },
|
||||
{ label: 'Folder Names', value: 'folderNames' },
|
||||
{ label: 'Report Names', value: 'reportNames' },
|
||||
{ label: 'Dashboard Names', value: 'sightNames' },
|
||||
{ label: 'Template Names', value: 'templateNames' },
|
||||
{ label: 'Workspace Names', value: 'workspaceNames' },
|
||||
{ label: 'Summary Fields', value: 'summaryFields' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
include_favorites: Property.Checkbox({
|
||||
displayName: 'Include Favorite Flags',
|
||||
description: 'Include information about which items are marked as favorites',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
modified_since: Property.DateTime({
|
||||
displayName: 'Modified Since',
|
||||
description: 'Only return results modified on or after this date/time',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
max_results: Property.Number({
|
||||
displayName: 'Max Results',
|
||||
description: 'Maximum number of results to return (default: 50, max: 100)',
|
||||
required: false,
|
||||
defaultValue: 50,
|
||||
}),
|
||||
|
||||
object_types_filter: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Filter by Object Types',
|
||||
description: 'Only return results of specific object types (leave empty for all types)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Rows', value: 'row' },
|
||||
{ label: 'Sheets', value: 'sheet' },
|
||||
{ label: 'Attachments', value: 'attachment' },
|
||||
{ label: 'Comments/Discussions', value: 'discussion' },
|
||||
{ label: 'Dashboards', value: 'dashboard' },
|
||||
{ label: 'Reports', value: 'report' },
|
||||
{ label: 'Folders', value: 'folder' },
|
||||
{ label: 'Templates', value: 'template' },
|
||||
{ label: 'Workspaces', value: 'workspace' },
|
||||
{ label: 'Summary Fields', value: 'summaryField' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const {
|
||||
search_scope,
|
||||
sheet_id,
|
||||
query,
|
||||
search_scopes,
|
||||
include_favorites,
|
||||
modified_since,
|
||||
max_results,
|
||||
object_types_filter,
|
||||
} = context.propsValue;
|
||||
|
||||
// Validate sheet_id requirement for specific sheet search
|
||||
if (search_scope === 'specific_sheet' && !sheet_id) {
|
||||
throw new Error('Sheet ID is required when searching within a specific sheet');
|
||||
}
|
||||
|
||||
// Build query parameters
|
||||
const queryParams: any = {
|
||||
query: query,
|
||||
};
|
||||
|
||||
// Add search scopes if specified
|
||||
if (search_scopes && search_scopes.length > 0) {
|
||||
queryParams.scopes = search_scopes;
|
||||
}
|
||||
|
||||
// Add include favorites flag
|
||||
if (include_favorites) {
|
||||
queryParams.include = 'favoriteFlag';
|
||||
}
|
||||
|
||||
// Add modified since filter
|
||||
if (modified_since) {
|
||||
queryParams.modifiedSince = new Date(modified_since as string).toISOString();
|
||||
}
|
||||
|
||||
// Determine API endpoint
|
||||
let apiUrl: string;
|
||||
if (search_scope === 'specific_sheet') {
|
||||
apiUrl = `${smartsheetCommon.baseUrl}/search/sheets/${sheet_id}`;
|
||||
} else {
|
||||
apiUrl = `${smartsheetCommon.baseUrl}/search`;
|
||||
}
|
||||
|
||||
try {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${context.auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
queryParams,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
const searchResults = response.body;
|
||||
|
||||
// Filter by object types if specified
|
||||
let filteredResults = searchResults.results || [];
|
||||
if (object_types_filter && object_types_filter.length > 0) {
|
||||
filteredResults = filteredResults.filter((result: any) =>
|
||||
object_types_filter.includes(result.objectType)
|
||||
);
|
||||
}
|
||||
|
||||
// Limit results if specified
|
||||
const maxResults = Math.min(max_results || 50, 100);
|
||||
if (filteredResults.length > maxResults) {
|
||||
filteredResults = filteredResults.slice(0, maxResults);
|
||||
}
|
||||
|
||||
// Organize results by type for better usability
|
||||
const resultsByType: any = {};
|
||||
filteredResults.forEach((result: any) => {
|
||||
if (!resultsByType[result.objectType]) {
|
||||
resultsByType[result.objectType] = [];
|
||||
}
|
||||
resultsByType[result.objectType].push(result);
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
total_count: searchResults.totalCount,
|
||||
returned_count: filteredResults.length,
|
||||
search_query: query,
|
||||
search_scope: search_scope,
|
||||
results: filteredResults,
|
||||
results_by_type: resultsByType,
|
||||
sheet_searched: search_scope === 'specific_sheet' ? sheet_id : 'all_accessible_sheets',
|
||||
|
||||
// Summary statistics
|
||||
summary: {
|
||||
rows_found: (resultsByType.row || []).length,
|
||||
sheets_found: (resultsByType.sheet || []).length,
|
||||
attachments_found: (resultsByType.attachment || []).length,
|
||||
discussions_found: (resultsByType.discussion || []).length,
|
||||
other_objects_found: filteredResults.length -
|
||||
(resultsByType.row || []).length -
|
||||
(resultsByType.sheet || []).length -
|
||||
(resultsByType.attachment || []).length -
|
||||
(resultsByType.discussion || []).length,
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to access sheets listing');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to retrieve sheets: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,395 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon } from '../common';
|
||||
|
||||
export const findSheetByName = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'find_sheet_by_name',
|
||||
displayName: 'Find Sheet(s)',
|
||||
description: 'Fetches existings sheets matching provided filter criteria.',
|
||||
props: {
|
||||
// Search options
|
||||
sheet_name: Property.ShortText({
|
||||
displayName: 'Sheet Name Filter',
|
||||
description: 'Filter sheets by name (partial or exact match). Leave empty to list all sheets.',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
exact_match: Property.Checkbox({
|
||||
displayName: 'Exact Name Match',
|
||||
description: 'When filtering by name, require exact match instead of partial match',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
// Pagination options
|
||||
include_all: Property.Checkbox({
|
||||
displayName: 'Include All Results',
|
||||
description: 'If true, include all results without pagination (overrides page and page size)',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
page: Property.Number({
|
||||
displayName: 'Page Number',
|
||||
description: 'Which page to return (defaults to 1, ignored if "Include All Results" is true)',
|
||||
required: false,
|
||||
defaultValue: 1,
|
||||
}),
|
||||
|
||||
page_size: Property.Number({
|
||||
displayName: 'Page Size',
|
||||
description: 'Maximum number of items to return per page (defaults to 100, max 10000, ignored if "Include All Results" is true)',
|
||||
required: false,
|
||||
defaultValue: 100,
|
||||
}),
|
||||
|
||||
// Access and filtering options
|
||||
access_api_level: Property.StaticDropdown({
|
||||
displayName: 'Access API Level',
|
||||
description: 'API access level for viewing and filtering permissions',
|
||||
required: false,
|
||||
defaultValue: '0',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Viewer (default)', value: '0' },
|
||||
{ label: 'Commenter', value: '1' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
access_level_filter: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Filter by Access Level',
|
||||
description: 'Only return sheets where you have specific access levels (leave empty for all)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Owner', value: 'OWNER' },
|
||||
{ label: 'Admin', value: 'ADMIN' },
|
||||
{ label: 'Editor (with sharing)', value: 'EDITOR_SHARE' },
|
||||
{ label: 'Editor', value: 'EDITOR' },
|
||||
{ label: 'Commenter', value: 'COMMENTER' },
|
||||
{ label: 'Viewer', value: 'VIEWER' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
modified_since: Property.DateTime({
|
||||
displayName: 'Modified Since',
|
||||
description: 'Only return sheets modified on or after this date/time',
|
||||
required: false,
|
||||
}),
|
||||
|
||||
// Additional data options
|
||||
include_sheet_version: Property.Checkbox({
|
||||
displayName: 'Include Sheet Version',
|
||||
description: 'Include current version number of each sheet',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
include_source_info: Property.Checkbox({
|
||||
displayName: 'Include Source Information',
|
||||
description: 'Include information about the source (template/sheet) each sheet was created from',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
numeric_dates: Property.Checkbox({
|
||||
displayName: 'Numeric Dates',
|
||||
description: 'Return dates as milliseconds since UNIX epoch instead of ISO strings',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
|
||||
// Advanced filtering
|
||||
created_date_range: Property.StaticDropdown({
|
||||
displayName: 'Created Date Range',
|
||||
description: 'Filter sheets by creation date range',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'All time', value: 'all' },
|
||||
{ label: 'Last 7 days', value: 'week' },
|
||||
{ label: 'Last 30 days', value: 'month' },
|
||||
{ label: 'Last 90 days', value: 'quarter' },
|
||||
{ label: 'Last 365 days', value: 'year' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
sort_by: Property.StaticDropdown({
|
||||
displayName: 'Sort Results By',
|
||||
description: 'How to sort the returned sheets',
|
||||
required: false,
|
||||
defaultValue: 'name',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Sheet Name', value: 'name' },
|
||||
{ label: 'Creation Date (newest first)', value: 'created_desc' },
|
||||
{ label: 'Creation Date (oldest first)', value: 'created_asc' },
|
||||
{ label: 'Modified Date (newest first)', value: 'modified_desc' },
|
||||
{ label: 'Modified Date (oldest first)', value: 'modified_asc' },
|
||||
{ label: 'Access Level', value: 'access' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const {
|
||||
sheet_name,
|
||||
exact_match,
|
||||
include_all,
|
||||
page,
|
||||
page_size,
|
||||
access_api_level,
|
||||
access_level_filter,
|
||||
modified_since,
|
||||
include_sheet_version,
|
||||
include_source_info,
|
||||
numeric_dates,
|
||||
created_date_range,
|
||||
sort_by,
|
||||
} = context.propsValue;
|
||||
|
||||
// Build query parameters
|
||||
const queryParams: any = {};
|
||||
|
||||
// Pagination
|
||||
if (include_all) {
|
||||
queryParams.includeAll = true;
|
||||
} else {
|
||||
if (page && page > 1) {
|
||||
queryParams.page = page;
|
||||
}
|
||||
if (page_size && page_size !== 100) {
|
||||
queryParams.pageSize = Math.min(page_size, 10000); // Cap at API limit
|
||||
}
|
||||
}
|
||||
|
||||
// Access level
|
||||
if (access_api_level && access_api_level !== '0') {
|
||||
queryParams.accessApiLevel = parseInt(access_api_level as string);
|
||||
}
|
||||
|
||||
// Modified since filter
|
||||
if (modified_since) {
|
||||
queryParams.modifiedSince = new Date(modified_since as string).toISOString();
|
||||
}
|
||||
|
||||
// Include options
|
||||
const includeOptions: string[] = [];
|
||||
if (include_sheet_version) {
|
||||
includeOptions.push('sheetVersion');
|
||||
}
|
||||
if (include_source_info) {
|
||||
includeOptions.push('source');
|
||||
}
|
||||
if (includeOptions.length > 0) {
|
||||
queryParams.include = includeOptions.join(',');
|
||||
}
|
||||
|
||||
// Numeric dates
|
||||
if (numeric_dates) {
|
||||
queryParams.numericDates = true;
|
||||
}
|
||||
|
||||
const apiUrl = `${smartsheetCommon.baseUrl}/sheets`;
|
||||
|
||||
try {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: apiUrl,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${context.auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
queryParams,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
const sheetData = response.body;
|
||||
|
||||
// Apply client-side filters
|
||||
let filteredSheets = sheetData.data || [];
|
||||
|
||||
// Filter by sheet name if specified
|
||||
if (sheet_name) {
|
||||
const searchName = (sheet_name as string).toLowerCase();
|
||||
filteredSheets = filteredSheets.filter((sheet: any) => {
|
||||
const sheetName = sheet.name.toLowerCase();
|
||||
return exact_match ?
|
||||
sheetName === searchName :
|
||||
sheetName.includes(searchName);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by access level
|
||||
if (access_level_filter && access_level_filter.length > 0) {
|
||||
filteredSheets = filteredSheets.filter((sheet: any) =>
|
||||
access_level_filter.includes(sheet.accessLevel)
|
||||
);
|
||||
}
|
||||
|
||||
// Filter by creation date range
|
||||
if (created_date_range && created_date_range !== 'all') {
|
||||
const now = new Date();
|
||||
const cutoffDate = new Date();
|
||||
|
||||
switch (created_date_range) {
|
||||
case 'week': {
|
||||
cutoffDate.setDate(now.getDate() - 7);
|
||||
break;
|
||||
}
|
||||
case 'month': {
|
||||
cutoffDate.setDate(now.getDate() - 30);
|
||||
break;
|
||||
}
|
||||
case 'quarter': {
|
||||
cutoffDate.setDate(now.getDate() - 90);
|
||||
break;
|
||||
}
|
||||
case 'year': {
|
||||
cutoffDate.setDate(now.getDate() - 365);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
filteredSheets = filteredSheets.filter((sheet: any) => {
|
||||
const createdDate = new Date(sheet.createdAt);
|
||||
return createdDate >= cutoffDate;
|
||||
});
|
||||
}
|
||||
|
||||
// Sort results
|
||||
if (sort_by) {
|
||||
filteredSheets.sort((a: any, b: any) => {
|
||||
switch (sort_by) {
|
||||
case 'name':
|
||||
return a.name.localeCompare(b.name);
|
||||
case 'created_desc':
|
||||
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||
case 'created_asc':
|
||||
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||
case 'modified_desc':
|
||||
return new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime();
|
||||
case 'modified_asc':
|
||||
return new Date(a.modifiedAt).getTime() - new Date(b.modifiedAt).getTime();
|
||||
case 'access': {
|
||||
const accessOrder = ['OWNER', 'ADMIN', 'EDITOR_SHARE', 'EDITOR', 'COMMENTER', 'VIEWER'];
|
||||
return accessOrder.indexOf(a.accessLevel) - accessOrder.indexOf(b.accessLevel);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Organize sheets by access level for analysis
|
||||
const sheetsByAccess: any = {};
|
||||
const sheetsBySource: any = {};
|
||||
|
||||
filteredSheets.forEach((sheet: any) => {
|
||||
// Group by access level
|
||||
if (!sheetsByAccess[sheet.accessLevel]) {
|
||||
sheetsByAccess[sheet.accessLevel] = [];
|
||||
}
|
||||
sheetsByAccess[sheet.accessLevel].push(sheet);
|
||||
|
||||
// Group by source type (if source info is included)
|
||||
if (sheet.source) {
|
||||
const sourceType = sheet.source.type || 'unknown';
|
||||
if (!sheetsBySource[sourceType]) {
|
||||
sheetsBySource[sourceType] = [];
|
||||
}
|
||||
sheetsBySource[sourceType].push(sheet);
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate date-based statistics
|
||||
const now = new Date();
|
||||
const recentlyModified = filteredSheets.filter((sheet: any) => {
|
||||
const modifiedDate = new Date(sheet.modifiedAt);
|
||||
const daysDiff = (now.getTime() - modifiedDate.getTime()) / (1000 * 3600 * 24);
|
||||
return daysDiff <= 7;
|
||||
}).length;
|
||||
|
||||
const recentlyCreated = filteredSheets.filter((sheet: any) => {
|
||||
const createdDate = new Date(sheet.createdAt);
|
||||
const daysDiff = (now.getTime() - createdDate.getTime()) / (1000 * 3600 * 24);
|
||||
return daysDiff <= 7;
|
||||
}).length;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
|
||||
// Pagination info
|
||||
pagination: {
|
||||
page_number: sheetData.pageNumber,
|
||||
page_size: sheetData.pageSize,
|
||||
total_pages: sheetData.totalPages,
|
||||
total_count: sheetData.totalCount,
|
||||
filtered_count: filteredSheets.length,
|
||||
},
|
||||
|
||||
// Main results
|
||||
sheets: filteredSheets,
|
||||
|
||||
// Organized results
|
||||
sheets_by_access_level: sheetsByAccess,
|
||||
sheets_by_source_type: sheetsBySource,
|
||||
|
||||
// Summary statistics
|
||||
summary: {
|
||||
total_sheets: filteredSheets.length,
|
||||
owned_sheets: (sheetsByAccess.OWNER || []).length,
|
||||
admin_sheets: (sheetsByAccess.ADMIN || []).length,
|
||||
editor_sheets: ((sheetsByAccess.EDITOR || []).length + (sheetsByAccess.EDITOR_SHARE || []).length),
|
||||
commenter_sheets: (sheetsByAccess.COMMENTER || []).length,
|
||||
viewer_sheets: (sheetsByAccess.VIEWER || []).length,
|
||||
recently_modified: recentlyModified,
|
||||
recently_created: recentlyCreated,
|
||||
sheets_with_source: Object.values(sheetsBySource).flat().length,
|
||||
},
|
||||
|
||||
// Access level breakdown
|
||||
access_breakdown: Object.keys(sheetsByAccess).map(level => ({
|
||||
access_level: level,
|
||||
count: sheetsByAccess[level].length,
|
||||
percentage: Math.round((sheetsByAccess[level].length / filteredSheets.length) * 100),
|
||||
})),
|
||||
|
||||
// Applied filters info
|
||||
filters_applied: {
|
||||
name_filter: sheet_name || null,
|
||||
exact_match: exact_match,
|
||||
access_levels: access_level_filter || [],
|
||||
modified_since: modified_since || null,
|
||||
created_date_range: created_date_range || 'all',
|
||||
sort_by: sort_by || 'name',
|
||||
},
|
||||
|
||||
// API options used
|
||||
api_options: {
|
||||
access_api_level: access_api_level,
|
||||
include_sheet_version: include_sheet_version,
|
||||
include_source_info: include_source_info,
|
||||
numeric_dates: numeric_dates,
|
||||
},
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid request parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to access sheets listing');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to retrieve sheets: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import { smartsheetCommon, updateRowInSmartsheet } from '../common';
|
||||
|
||||
export const updateRow = createAction({
|
||||
auth: smartsheetAuth,
|
||||
name: 'update_row',
|
||||
displayName: 'Update Row',
|
||||
description: 'Updates an existing row.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
row_id: smartsheetCommon.row_id,
|
||||
cells: smartsheetCommon.cells,
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const { sheet_id, row_id, cells } = context.propsValue;
|
||||
|
||||
const rowObj: any = {
|
||||
id: row_id,
|
||||
};
|
||||
|
||||
// Transform dynamic cells data into proper Smartsheet format
|
||||
const cellsData = cells as Record<string, any>;
|
||||
const transformedCells: any[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(cellsData)) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
continue; // Skip empty values
|
||||
}
|
||||
|
||||
let columnId: number;
|
||||
const cellObj: any = {};
|
||||
|
||||
if (key.startsWith('column_')) {
|
||||
// Regular column value
|
||||
columnId = parseInt(key.replace('column_', ''));
|
||||
cellObj.columnId = columnId;
|
||||
cellObj.value = value;
|
||||
} else {
|
||||
continue; // Skip unknown keys
|
||||
}
|
||||
|
||||
transformedCells.push(cellObj);
|
||||
}
|
||||
|
||||
// Only add cells array if we have cells to update
|
||||
if (transformedCells.length > 0) {
|
||||
rowObj.cells = transformedCells;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await updateRowInSmartsheet(context.auth.secret_text, sheet_id as string, [
|
||||
[rowObj],
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
row: result,
|
||||
message: 'Row updated successfully',
|
||||
cells_processed: transformedCells.length,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 400) {
|
||||
const errorBody = error.response.data;
|
||||
throw new Error(`Bad Request: ${errorBody.message || 'Invalid row data or parameters'}`);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error('Insufficient permissions to update rows in this sheet');
|
||||
} else if (error.response?.status === 404) {
|
||||
throw new Error('Sheet not found or you do not have access to it');
|
||||
} else if (error.response?.status === 429) {
|
||||
throw new Error('Rate limit exceeded. Please try again later.');
|
||||
}
|
||||
|
||||
throw new Error(`Failed to update row: ${error.message}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,900 @@
|
||||
import { DynamicPropsValue, Property } from '@activepieces/pieces-framework';
|
||||
import { httpClient, HttpRequest, HttpMethod } from '@activepieces/pieces-common';
|
||||
import crypto from 'crypto';
|
||||
import { smartsheetAuth } from '../..';
|
||||
|
||||
export const smartsheetCommon = {
|
||||
baseUrl: 'https://api.smartsheet.com/2.0',
|
||||
|
||||
sheet_id:(required=true)=> Property.Dropdown({
|
||||
auth: smartsheetAuth,
|
||||
displayName: 'Sheet',
|
||||
description: 'Select a sheet',
|
||||
required,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const sheets = await listSheets(auth.secret_text);
|
||||
|
||||
if (sheets.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'No sheets found in your account.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
options: sheets.map((sheet: SmartsheetSheet) => ({
|
||||
value: sheet.id.toString(),
|
||||
label: sheet.name,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load sheets - check your connection.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
column_id: Property.Dropdown({
|
||||
auth: smartsheetAuth,
|
||||
|
||||
displayName: 'Column',
|
||||
description: 'Select a column',
|
||||
required: true,
|
||||
refreshers: ['sheet_id'],
|
||||
options: async ({ auth, sheet_id }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '⚠️ Please authenticate with Smartsheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!sheet_id) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📋 Please select a sheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const columns = await getSheetColumns(
|
||||
auth.secret_text
|
||||
,
|
||||
sheet_id as unknown as string,
|
||||
);
|
||||
|
||||
if (columns.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📄 No columns found in this sheet',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
options: columns.map((column: SmartsheetColumn) => ({
|
||||
value: column.id.toString(),
|
||||
label: column.title,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '❌ Failed to load columns - check your permissions',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Dynamic cell properties based on column types
|
||||
cells: Property.DynamicProperties({
|
||||
auth: smartsheetAuth,
|
||||
displayName: 'Cells',
|
||||
description: 'Cell data with properties based on column types',
|
||||
required: true,
|
||||
refreshers: ['sheet_id'],
|
||||
props: async ({ auth, sheet_id }) => {
|
||||
if (!auth || !sheet_id) return {};
|
||||
|
||||
const fields: DynamicPropsValue = {};
|
||||
|
||||
try {
|
||||
const columns = await getSheetColumns(
|
||||
auth.secret_text
|
||||
,
|
||||
sheet_id as unknown as string,
|
||||
);
|
||||
|
||||
if (columns.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const column of columns) {
|
||||
const baseProps = {
|
||||
displayName: column.title,
|
||||
required: false,
|
||||
};
|
||||
|
||||
// Create cell properties based on column type
|
||||
switch (column.type?.toLowerCase()) {
|
||||
case 'TEXT_NUMBER':
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
});
|
||||
break;
|
||||
case 'DATE':
|
||||
fields[`column_${column.id}`] = Property.DateTime({
|
||||
...baseProps,
|
||||
description: `Date/time value for ${column.title}`,
|
||||
});
|
||||
break;
|
||||
case 'CHECKBOX':
|
||||
fields[`column_${column.id}`] = Property.Checkbox({
|
||||
...baseProps,
|
||||
});
|
||||
break;
|
||||
case 'PICKLIST':
|
||||
case 'MULTI_PICKLIST': {
|
||||
if (column.options && column.options.length > 0) {
|
||||
const dropdownOptions = column.options.map((option) => ({
|
||||
label: option,
|
||||
value: option,
|
||||
}));
|
||||
|
||||
if (column.type?.toLowerCase() === 'multi_picklist') {
|
||||
fields[`column_${column.id}`] = Property.StaticMultiSelectDropdown({
|
||||
...baseProps,
|
||||
description: `Multiple selection for ${column.title}`,
|
||||
options: {
|
||||
options: dropdownOptions,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
fields[`column_${column.id}`] = Property.StaticDropdown({
|
||||
...baseProps,
|
||||
description: `Select option for ${column.title}`,
|
||||
options: {
|
||||
options: dropdownOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
description: `Value for ${column.title}`,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CONTACT_LIST':
|
||||
case 'MULTI_CONTACT_LIST':
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
description: `Contact email(s) for ${column.title}. For multiple contacts, separate with commas.`,
|
||||
});
|
||||
break;
|
||||
case 'DURATION':
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
description: `For example, 4d 6h 30m`,
|
||||
});
|
||||
break;
|
||||
case 'PREDECESSOR':
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
description: `Predecessor row numbers for ${column.title}. Format: "1FS+2d,3SS" etc.`,
|
||||
});
|
||||
break;
|
||||
case 'ABSTRACT_DATETIME':
|
||||
fields[`column_${column.id}`] = Property.DateTime({
|
||||
...baseProps,
|
||||
description: `Date/time value for ${column.title}`,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
fields[`column_${column.id}`] = Property.ShortText({
|
||||
...baseProps,
|
||||
description: `Value for ${column.title} (${column.type || 'unknown type'})`,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch columns for dynamic properties:', error);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Dynamic row selector
|
||||
row_id: Property.Dropdown({
|
||||
auth: smartsheetAuth,
|
||||
|
||||
displayName: 'Row',
|
||||
required: true,
|
||||
refreshers: ['sheet_id'],
|
||||
options: async ({ auth, sheet_id }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first.',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!sheet_id) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please select a sheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const sheet = await getSheet(auth.secret_text
|
||||
, sheet_id as unknown as string);
|
||||
const rows = sheet.rows || [];
|
||||
|
||||
if (rows.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'No rows found in this sheet',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disabled:false,
|
||||
options: rows.slice(0, 100).map((row: any) => {
|
||||
// Get the primary column value for display
|
||||
const primaryCell = row.cells?.find((cell: any) =>
|
||||
sheet.columns?.find((col: any) => col.id === cell.columnId && col.primary),
|
||||
);
|
||||
const displayValue =
|
||||
primaryCell?.displayValue || primaryCell?.value || `Row ${row.rowNumber}`;
|
||||
|
||||
return {
|
||||
value: row.id.toString(),
|
||||
label: `${displayValue} (Row ${row.rowNumber})`,
|
||||
};
|
||||
}),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Failed to load rows - check your permissions',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Dynamic sheet selector for hyperlinks
|
||||
hyperlink_sheet_id: Property.Dropdown({
|
||||
auth: smartsheetAuth,
|
||||
|
||||
displayName: 'Target Sheet',
|
||||
description: 'Select a sheet to link to',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '⚠️ Please authenticate with Smartsheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const sheets = await listSheets(auth.secret_text
|
||||
);
|
||||
|
||||
if (sheets.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📂 No sheets found in your account',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
options: sheets.map((sheet: SmartsheetSheet) => ({
|
||||
value: sheet.id.toString(),
|
||||
label: sheet.name,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '❌ Failed to load sheets - check your permissions',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Dynamic report selector for hyperlinks
|
||||
hyperlink_report_id: Property.Dropdown({
|
||||
auth: smartsheetAuth,
|
||||
|
||||
displayName: 'Target Report',
|
||||
description: 'Select a report to link to',
|
||||
required: false,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '⚠️ Please authenticate with Smartsheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const reports = await listReports(auth.secret_text
|
||||
);
|
||||
|
||||
if (reports.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📊 No reports found in your account',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
options: reports.map((report: SmartsheetReport) => ({
|
||||
value: report.id.toString(),
|
||||
label: `${report.name}${report.isSummaryReport ? ' (Summary)' : ' (Row Report)'}`,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '❌ Failed to load reports - check your permissions',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Dynamic column selector for search/filter operations
|
||||
search_columns: Property.MultiSelectDropdown({
|
||||
auth: smartsheetAuth,
|
||||
displayName: 'Search Columns',
|
||||
description: 'Select specific columns to search within (leave empty to search all columns)',
|
||||
required: false,
|
||||
refreshers: ['sheet_id'],
|
||||
options: async ({ auth, sheet_id }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '⚠️ Please authenticate with Smartsheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!sheet_id) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📋 Please select a sheet first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const columns = await getSheetColumns(
|
||||
auth.secret_text,
|
||||
sheet_id as unknown as string,
|
||||
);
|
||||
const searchableColumns = columns.filter(
|
||||
(column) => column.type?.toLowerCase() !== 'auto_number',
|
||||
);
|
||||
|
||||
if (searchableColumns.length === 0) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '📄 No searchable columns found in this sheet',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
options: searchableColumns.map((column: SmartsheetColumn) => ({
|
||||
value: column.id.toString(),
|
||||
label: `${column.title} (${column.type || 'unknown'})`,
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: '❌ Failed to load columns - check your permissions',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
// Interfaces
|
||||
export interface SmartsheetSheet {
|
||||
id: number;
|
||||
name: string;
|
||||
accessLevel: string;
|
||||
permalink: string;
|
||||
createdAt: string;
|
||||
modifiedAt: string;
|
||||
}
|
||||
|
||||
export interface SmartsheetColumn {
|
||||
id: number;
|
||||
index: number;
|
||||
title: string;
|
||||
type?: string;
|
||||
primary?: boolean;
|
||||
options?: string[];
|
||||
validation?: boolean;
|
||||
width?: number;
|
||||
hidden?: boolean;
|
||||
locked?: boolean;
|
||||
lockedForUser?: boolean;
|
||||
}
|
||||
|
||||
export interface SmartsheetRow {
|
||||
id: number;
|
||||
rowNumber: number;
|
||||
siblingId?: number;
|
||||
expanded?: boolean;
|
||||
createdAt: string;
|
||||
modifiedAt: string;
|
||||
cells: SmartsheetCell[];
|
||||
}
|
||||
|
||||
export interface SmartsheetCell {
|
||||
columnId: number;
|
||||
value?: any;
|
||||
displayValue?: string;
|
||||
formula?: string;
|
||||
}
|
||||
|
||||
export interface SmartsheetAttachment {
|
||||
id: number;
|
||||
name: string;
|
||||
url: string;
|
||||
attachmentType: string;
|
||||
createdAt: string;
|
||||
createdBy: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SmartsheetComment {
|
||||
id: number;
|
||||
text: string;
|
||||
createdAt: string;
|
||||
createdBy: {
|
||||
name: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SmartsheetReport {
|
||||
id: number;
|
||||
name: string;
|
||||
accessLevel: 'ADMIN' | 'COMMENTER' | 'EDITOR' | 'EDITOR_SHARE' | 'OWNER' | 'VIEWER';
|
||||
isSummaryReport: boolean;
|
||||
ownerId: number;
|
||||
createdAt: string;
|
||||
modifiedAt: string;
|
||||
permalink: string;
|
||||
owner?: string;
|
||||
totalRowCount?: number;
|
||||
version?: number;
|
||||
}
|
||||
|
||||
export interface SmartsheetReportsResponse {
|
||||
pageNumber: number;
|
||||
pageSize: number | null;
|
||||
totalPages: number;
|
||||
totalCount: number;
|
||||
data: SmartsheetReport[];
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
export async function listSheets(accessToken: string): Promise<SmartsheetSheet[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ data: SmartsheetSheet[] }>(request);
|
||||
return response.body.data;
|
||||
}
|
||||
|
||||
export async function getSheet(accessToken: string, sheetId: string): Promise<any> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest(request);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
export async function getSheetColumns(
|
||||
accessToken: string,
|
||||
sheetId: string,
|
||||
): Promise<SmartsheetColumn[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/columns?include=columnType`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ data: SmartsheetColumn[] }>(request);
|
||||
return response.body.data;
|
||||
}
|
||||
|
||||
export async function addRowToSmartsheet(
|
||||
accessToken: string,
|
||||
sheetId: string,
|
||||
rowData: any,
|
||||
queryParams: any = {},
|
||||
): Promise<SmartsheetRow> {
|
||||
// Build query string from parameters
|
||||
const queryString = new URLSearchParams();
|
||||
|
||||
if (queryParams.allowPartialSuccess) {
|
||||
queryString.append('allowPartialSuccess', 'true');
|
||||
}
|
||||
if (queryParams.overrideValidation) {
|
||||
queryString.append('overrideValidation', 'true');
|
||||
}
|
||||
if (queryParams.accessApiLevel) {
|
||||
queryString.append('accessApiLevel', queryParams.accessApiLevel.toString());
|
||||
}
|
||||
|
||||
const url = `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows${
|
||||
queryString.toString() ? '?' + queryString.toString() : ''
|
||||
}`;
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: rowData,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ result: SmartsheetRow[] }>(request);
|
||||
return response.body.result[0];
|
||||
}
|
||||
|
||||
export async function updateRowInSmartsheet(
|
||||
accessToken: string,
|
||||
sheetId: string,
|
||||
rowData: any,
|
||||
queryParams: any = {},
|
||||
): Promise<SmartsheetRow> {
|
||||
// Build query string from parameters
|
||||
const queryString = new URLSearchParams();
|
||||
|
||||
if (queryParams.allowPartialSuccess) {
|
||||
queryString.append('allowPartialSuccess', 'true');
|
||||
}
|
||||
if (queryParams.overrideValidation) {
|
||||
queryString.append('overrideValidation', 'true');
|
||||
}
|
||||
if (queryParams.accessApiLevel) {
|
||||
queryString.append('accessApiLevel', queryParams.accessApiLevel.toString());
|
||||
}
|
||||
|
||||
const url = `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows${
|
||||
queryString.toString() ? '?' + queryString.toString() : ''
|
||||
}`;
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.PUT,
|
||||
url: url,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: rowData,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ result: SmartsheetRow[] }>(request);
|
||||
return response.body.result[0];
|
||||
}
|
||||
|
||||
export async function getRowAttachments(
|
||||
accessToken: string,
|
||||
sheetId: string,
|
||||
rowId: string,
|
||||
): Promise<SmartsheetAttachment[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows/${rowId}/attachments`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{
|
||||
data: SmartsheetAttachment[];
|
||||
}>(request);
|
||||
return response.body.data || [];
|
||||
}
|
||||
|
||||
export async function findSheetsByName(
|
||||
accessToken: string,
|
||||
name: string,
|
||||
): Promise<SmartsheetSheet[]> {
|
||||
const sheets = await listSheets(accessToken);
|
||||
return sheets.filter((sheet) => sheet.name.toLowerCase().includes(name.toLowerCase()));
|
||||
}
|
||||
|
||||
export async function listReports(
|
||||
accessToken: string,
|
||||
modifiedSince?: string,
|
||||
): Promise<SmartsheetReport[]> {
|
||||
// Build query parameters
|
||||
const queryParams: any = {};
|
||||
if (modifiedSince) {
|
||||
queryParams.modifiedSince = modifiedSince;
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/reports`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
queryParams: Object.keys(queryParams).length > 0 ? queryParams : undefined,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<SmartsheetReportsResponse>(request);
|
||||
return response.body.data || [];
|
||||
}
|
||||
|
||||
// Webhook management functions
|
||||
export interface SmartsheetWebhook {
|
||||
id: number;
|
||||
name: string;
|
||||
callbackUrl: string;
|
||||
scope: string;
|
||||
scopeObjectId: number;
|
||||
events: string[];
|
||||
enabled: boolean;
|
||||
status: string;
|
||||
sharedSecret: string;
|
||||
}
|
||||
|
||||
export async function subscribeWebhook(
|
||||
accessToken: string,
|
||||
webhookUrl: string,
|
||||
sheetId: string,
|
||||
webhookName: string,
|
||||
): Promise<SmartsheetWebhook> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `${smartsheetCommon.baseUrl}/webhooks`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
name: webhookName,
|
||||
callbackUrl: webhookUrl,
|
||||
scope: 'sheet',
|
||||
scopeObjectId: parseInt(sheetId),
|
||||
events: ['*.*'],
|
||||
version: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ result: SmartsheetWebhook }>(request);
|
||||
return response.body.result;
|
||||
}
|
||||
|
||||
export async function enableWebhook(
|
||||
accessToken: string,
|
||||
webhookId: string,
|
||||
): Promise<SmartsheetWebhook> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.PUT,
|
||||
url: `${smartsheetCommon.baseUrl}/webhooks/${webhookId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ result: SmartsheetWebhook }>(request);
|
||||
return response.body.result;
|
||||
}
|
||||
|
||||
export async function unsubscribeWebhook(accessToken: string, webhookId: string): Promise<void> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.DELETE,
|
||||
url: `${smartsheetCommon.baseUrl}/webhooks/${webhookId}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
await httpClient.sendRequest(request);
|
||||
}
|
||||
|
||||
export async function listWebhooks(accessToken: string): Promise<SmartsheetWebhook[]> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/webhooks`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<{ data: SmartsheetWebhook[] }>(request);
|
||||
return response.body.data || [];
|
||||
}
|
||||
|
||||
|
||||
export interface WebhookInformation {
|
||||
webhookId: string;
|
||||
sharedSecret: string;
|
||||
webhookName: string;
|
||||
}
|
||||
|
||||
export async function findOrCreateWebhook(
|
||||
accessToken: string,
|
||||
webhookUrl: string,
|
||||
sheetId: string,
|
||||
triggerIdentifier: string,
|
||||
): Promise<SmartsheetWebhook> {
|
||||
const webhookName = `AP-${triggerIdentifier.slice(-8)}-Sheet${sheetId}`;
|
||||
|
||||
const existingWebhooks = await listWebhooks(accessToken);
|
||||
const existingWebhook = existingWebhooks.find(
|
||||
(wh) => wh.callbackUrl === webhookUrl && wh.scopeObjectId.toString() === sheetId,
|
||||
);
|
||||
|
||||
if (existingWebhook) {
|
||||
if (existingWebhook.name !== webhookName) {
|
||||
console.log(
|
||||
`Found existing webhook ${existingWebhook.id} with different name: ${existingWebhook.name}. Expected: ${webhookName}`,
|
||||
);
|
||||
}
|
||||
if (!existingWebhook.enabled || existingWebhook.status !== 'ENABLED') {
|
||||
return await enableWebhook(accessToken, existingWebhook.id.toString());
|
||||
}
|
||||
return existingWebhook;
|
||||
}
|
||||
|
||||
const newWebhook = await subscribeWebhook(accessToken, webhookUrl, sheetId, webhookName);
|
||||
|
||||
return await enableWebhook(accessToken, newWebhook.id.toString());
|
||||
}
|
||||
export function verifyWebhookSignature(
|
||||
webhookSecret?: string,
|
||||
webhookSignatureHeader?: string,
|
||||
webhookRawBody?: any,
|
||||
): boolean {
|
||||
if (!webhookSecret || !webhookSignatureHeader || !webhookRawBody) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||
hmac.update(webhookRawBody);
|
||||
const expectedSignature = hmac.digest('hex');
|
||||
|
||||
return crypto.timingSafeEqual(
|
||||
Buffer.from(webhookSignatureHeader, 'hex'),
|
||||
Buffer.from(expectedSignature, 'hex'),
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSheetRowDetails(
|
||||
accessToken: string,
|
||||
sheetId: string,
|
||||
rowId: string,
|
||||
): Promise<SmartsheetRow | null> {
|
||||
try {
|
||||
const req: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/rows/${rowId}`,
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
};
|
||||
const response = await httpClient.sendRequest<SmartsheetRow>(req);
|
||||
return response.body;
|
||||
} catch (e: any) {
|
||||
if (e.response?.status === 404) {
|
||||
console.log(`Row ${rowId} on sheet ${sheetId} not found during detail fetch.`);
|
||||
return null;
|
||||
}
|
||||
console.error(`Error fetching row ${rowId} from sheet ${sheetId}:`, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAttachmentFullDetails(accessToken: string, sheetId: string, attachmentId: string): Promise<SmartsheetAttachment | null> {
|
||||
try {
|
||||
const req: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/attachments/${attachmentId}`,
|
||||
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||
};
|
||||
const response = await httpClient.sendRequest<SmartsheetAttachment>(req);
|
||||
return response.body;
|
||||
} catch (e: any) {
|
||||
if (e.response?.status === 404) {
|
||||
console.log(`Attachment ${attachmentId} on sheet ${sheetId} not found.`);
|
||||
return null;
|
||||
}
|
||||
console.error(`Error fetching attachment ${attachmentId} from sheet ${sheetId}:`, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCommentFullDetails(accessToken: string, sheetId: string, discussionId: string, commentId: string): Promise<SmartsheetComment | null> {
|
||||
try {
|
||||
const req: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${smartsheetCommon.baseUrl}/sheets/${sheetId}/comments/${commentId}`,
|
||||
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||
};
|
||||
const response = await httpClient.sendRequest<SmartsheetComment>(req);
|
||||
return response.body;
|
||||
} catch (e: any) {
|
||||
if (e.response?.status === 404) {
|
||||
console.log(`Comment ${commentId} in discussion ${discussionId} on sheet ${sheetId} not found.`);
|
||||
return null;
|
||||
}
|
||||
console.error(`Error fetching comment ${commentId} from sheet ${sheetId}:`, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import {
|
||||
smartsheetCommon,
|
||||
findOrCreateWebhook,
|
||||
WebhookInformation,
|
||||
verifyWebhookSignature,
|
||||
getAttachmentFullDetails,
|
||||
} from '../common';
|
||||
import { WebhookHandshakeStrategy } from '@activepieces/shared';
|
||||
|
||||
const TRIGGER_KEY = 'smartsheet_new_attachment_trigger';
|
||||
|
||||
export const newAttachmentTrigger = createTrigger({
|
||||
auth: smartsheetAuth,
|
||||
name: 'new_attachment_',
|
||||
displayName: 'New Attachment Added',
|
||||
description: 'Triggers when a new attachment is added to a row or sheet.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
sheetId: '12345',
|
||||
eventType: 'created',
|
||||
objectType: 'attachment',
|
||||
id: 78901, // Attachment ID
|
||||
parentId: 67890, // e.g., Row ID if attached to a row
|
||||
parentType: 'ROW',
|
||||
timestamp: '2023-10-28T12:10:00Z',
|
||||
userId: 54321,
|
||||
attachmentData: {
|
||||
/* ... full attachment data ... */
|
||||
},
|
||||
},
|
||||
handshakeConfiguration: {
|
||||
strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT,
|
||||
paramName: 'challenge',
|
||||
},
|
||||
async onHandshake(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
smartsheetHookResponse: (context.payload.body as any)['challenge'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { sheet_id } = context.propsValue;
|
||||
if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.');
|
||||
|
||||
const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1);
|
||||
const webhook = await findOrCreateWebhook(
|
||||
context.auth.secret_text,
|
||||
context.webhookUrl,
|
||||
sheet_id as string,
|
||||
triggerIdentifier,
|
||||
);
|
||||
|
||||
await context.store.put<WebhookInformation>(TRIGGER_KEY, {
|
||||
webhookId: webhook.id.toString(),
|
||||
sharedSecret: webhook.sharedSecret,
|
||||
webhookName: webhook.name,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const { sheet_id } = context.propsValue;
|
||||
if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.');
|
||||
|
||||
const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1);
|
||||
const webhook = await findOrCreateWebhook(
|
||||
context.auth.secret_text,
|
||||
context.webhookUrl,
|
||||
sheet_id as string,
|
||||
triggerIdentifier,
|
||||
);
|
||||
|
||||
await context.store.put<WebhookInformation>(TRIGGER_KEY, {
|
||||
webhookId: webhook.id.toString(),
|
||||
sharedSecret: webhook.sharedSecret,
|
||||
webhookName: webhook.name,
|
||||
});
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payload = context.payload.body as any;
|
||||
const headers = context.payload.headers as Record<string, string | undefined>;
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (!webhookInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (headers && headers['smartsheet-hook-challenge']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webhookSecret = webhookInfo?.sharedSecret;
|
||||
const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256'];
|
||||
const rawBody = context.payload.rawBody;
|
||||
|
||||
if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (payload.newWebhookStatus) {
|
||||
return [];
|
||||
}
|
||||
if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newAttachmentEvents = [];
|
||||
for (const event of payload.events) {
|
||||
if (event.objectType === 'attachment' && event.eventType === 'created') {
|
||||
const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() };
|
||||
const objectSheetId = payload.scopeObjectId?.toString();
|
||||
if (objectSheetId) {
|
||||
try {
|
||||
eventOutput.attachmentData = await getAttachmentFullDetails(
|
||||
context.auth.secret_text,
|
||||
objectSheetId,
|
||||
event.id.toString(),
|
||||
);
|
||||
} catch (error: any) {
|
||||
eventOutput.fetchError = error.message;
|
||||
}
|
||||
} else {
|
||||
eventOutput.fetchError = 'scopeObjectId missing';
|
||||
}
|
||||
|
||||
newAttachmentEvents.push(eventOutput);
|
||||
}
|
||||
}
|
||||
return newAttachmentEvents;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { createTrigger, TriggerStrategy, Property } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import {
|
||||
smartsheetCommon,
|
||||
unsubscribeWebhook,
|
||||
WebhookInformation,
|
||||
findOrCreateWebhook,
|
||||
verifyWebhookSignature,
|
||||
getCommentFullDetails,
|
||||
} from '../common';
|
||||
import { WebhookHandshakeStrategy } from '@activepieces/shared';
|
||||
|
||||
const TRIGGER_KEY = 'smartsheet_new_comment_trigger';
|
||||
|
||||
export const newCommentTrigger = createTrigger({
|
||||
auth: smartsheetAuth,
|
||||
name: 'new_comment_webhook',
|
||||
displayName: 'New Comment Added',
|
||||
description: 'Triggers when a new comment is added to a discussion on a sheet.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
sheetId: '12345',
|
||||
eventType: 'created',
|
||||
objectType: 'comment',
|
||||
id: 89012, // Comment ID
|
||||
discussionId: 45678,
|
||||
parentId: 67890, // e.g., Row ID comment is on
|
||||
parentType: 'ROW',
|
||||
timestamp: '2023-10-28T12:15:00Z',
|
||||
userId: 54321,
|
||||
commentData: {
|
||||
/* ... full comment data ... */
|
||||
},
|
||||
},
|
||||
handshakeConfiguration: {
|
||||
strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT,
|
||||
paramName: 'challenge',
|
||||
},
|
||||
async onHandshake(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
smartsheetHookResponse: (context.payload.body as any)['challenge'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { sheet_id } = context.propsValue;
|
||||
if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.');
|
||||
|
||||
const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1);
|
||||
const webhook = await findOrCreateWebhook(
|
||||
context.auth.secret_text,
|
||||
context.webhookUrl,
|
||||
sheet_id as string,
|
||||
triggerIdentifier,
|
||||
);
|
||||
|
||||
await context.store.put<WebhookInformation>(TRIGGER_KEY, {
|
||||
webhookId: webhook.id.toString(),
|
||||
sharedSecret: webhook.sharedSecret,
|
||||
webhookName: webhook.name,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (webhookInfo && webhookInfo.webhookId) {
|
||||
try {
|
||||
await unsubscribeWebhook(context.auth.secret_text, webhookInfo.webhookId);
|
||||
} catch (error: any) {
|
||||
if (error.response?.status !== 404) {
|
||||
console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
await context.store.delete(TRIGGER_KEY);
|
||||
}
|
||||
},
|
||||
|
||||
async run(context): Promise<unknown[]> {
|
||||
const payload = context.payload.body as any;
|
||||
const headers = context.payload.headers as Record<string, string | undefined>;
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (!webhookInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (headers && headers['smartsheet-hook-challenge']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webhookSecret = webhookInfo?.sharedSecret;
|
||||
const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256'];
|
||||
const rawBody = context.payload.rawBody;
|
||||
|
||||
if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (payload.newWebhookStatus) {
|
||||
return [];
|
||||
}
|
||||
if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newCommentEvents = [];
|
||||
for (const event of payload.events) {
|
||||
if (event.objectType === 'comment' && event.eventType === 'created') {
|
||||
const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() };
|
||||
const objectSheetId = payload.scopeObjectId?.toString();
|
||||
if (objectSheetId) {
|
||||
try {
|
||||
eventOutput.commentData = await getCommentFullDetails(
|
||||
context.auth.secret_text,
|
||||
objectSheetId,
|
||||
event.discussionId.toString(),
|
||||
event.id.toString(),
|
||||
);
|
||||
} catch (error: any) {
|
||||
eventOutput.fetchError = error.message;
|
||||
}
|
||||
} else {
|
||||
eventOutput.fetchError = 'scopeObjectId missing';
|
||||
}
|
||||
|
||||
newCommentEvents.push(eventOutput);
|
||||
}
|
||||
}
|
||||
return newCommentEvents;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import {
|
||||
smartsheetCommon,
|
||||
unsubscribeWebhook,
|
||||
getSheetRowDetails,
|
||||
WebhookInformation,
|
||||
findOrCreateWebhook,
|
||||
verifyWebhookSignature,
|
||||
} from '../common';
|
||||
import { WebhookHandshakeStrategy } from '@activepieces/shared';
|
||||
|
||||
const TRIGGER_KEY = 'smartsheet_new_row_trigger';
|
||||
|
||||
export const newRowAddedTrigger = createTrigger({
|
||||
auth: smartsheetAuth,
|
||||
name: 'new_row_added',
|
||||
displayName: 'New Row Added',
|
||||
description: 'Triggers when a new row is added.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
sheetId: '12345',
|
||||
eventType: 'created',
|
||||
objectType: 'row',
|
||||
id: 67890, // Row ID
|
||||
timestamp: '2023-10-28T12:00:00Z',
|
||||
userId: 54321,
|
||||
rowData: {
|
||||
id: 67890,
|
||||
sheetId: 12345,
|
||||
rowNumber: 15,
|
||||
createdAt: '2023-10-28T12:00:00Z',
|
||||
modifiedAt: '2023-10-28T12:00:00Z',
|
||||
cells: [
|
||||
{ columnId: 111, value: 'New Task A', displayValue: 'New Task A' },
|
||||
{ columnId: 222, value: 'Pending', displayValue: 'Pending' },
|
||||
],
|
||||
},
|
||||
},
|
||||
handshakeConfiguration: {
|
||||
strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT,
|
||||
paramName: 'challenge',
|
||||
},
|
||||
async onHandshake(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
smartsheetHookResponse: (context.payload.body as any)['challenge'],
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const { sheet_id } = context.propsValue;
|
||||
if (!sheet_id) throw new Error('Sheet ID is required to enable the webhook.');
|
||||
|
||||
const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1);
|
||||
const webhook = await findOrCreateWebhook(
|
||||
context.auth.secret_text,
|
||||
context.webhookUrl,
|
||||
sheet_id as string,
|
||||
triggerIdentifier,
|
||||
);
|
||||
|
||||
await context.store.put<WebhookInformation>(TRIGGER_KEY, {
|
||||
webhookId: webhook.id.toString(),
|
||||
sharedSecret: webhook.sharedSecret,
|
||||
webhookName: webhook.name,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (webhookInfo && webhookInfo.webhookId) {
|
||||
try {
|
||||
await unsubscribeWebhook(context.auth.secret_text, webhookInfo.webhookId);
|
||||
} catch (error: any) {
|
||||
if (error.response?.status !== 404) {
|
||||
console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
await context.store.delete(TRIGGER_KEY);
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payload = context.payload.body as any;
|
||||
const headers = context.payload.headers as Record<string, string | undefined>;
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (!webhookInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (headers && headers['smartsheet-hook-challenge']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webhookSecret = webhookInfo?.sharedSecret;
|
||||
const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256'];
|
||||
const rawBody = context.payload.rawBody;
|
||||
|
||||
if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (payload.newWebhookStatus) {
|
||||
return [];
|
||||
}
|
||||
if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const newRowEvents = [];
|
||||
for (const event of payload.events) {
|
||||
if (event.objectType === 'row' && event.eventType === 'created') {
|
||||
const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() };
|
||||
const objectSheetId = payload.scopeObjectId?.toString();
|
||||
if (objectSheetId) {
|
||||
try {
|
||||
eventOutput.rowData = await getSheetRowDetails(
|
||||
context.auth.secret_text,
|
||||
objectSheetId,
|
||||
event.id.toString(),
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
`Failed to fetch full details for new row ID ${event.id}: ${error.message}`,
|
||||
);
|
||||
eventOutput.fetchError = error.message;
|
||||
}
|
||||
} else {
|
||||
eventOutput.fetchError = 'scopeObjectId missing, cannot fetch row details.';
|
||||
}
|
||||
|
||||
newRowEvents.push(eventOutput);
|
||||
}
|
||||
}
|
||||
return newRowEvents;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,134 @@
|
||||
import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { smartsheetAuth } from '../../index';
|
||||
import {
|
||||
smartsheetCommon,
|
||||
findOrCreateWebhook,
|
||||
WebhookInformation,
|
||||
getSheetRowDetails,
|
||||
verifyWebhookSignature,
|
||||
unsubscribeWebhook,
|
||||
} from '../common';
|
||||
import { WebhookHandshakeStrategy } from '@activepieces/shared';
|
||||
|
||||
const TRIGGER_KEY = 'smartsheet_updated_row_trigger';
|
||||
|
||||
export const updatedRowTrigger = createTrigger({
|
||||
auth: smartsheetAuth,
|
||||
name: 'updated_row',
|
||||
displayName: 'Row Updated',
|
||||
description: 'Triggers when an existing row is updated.',
|
||||
props: {
|
||||
sheet_id: smartsheetCommon.sheet_id(),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
sheetId: '12345',
|
||||
eventType: 'updated',
|
||||
objectType: 'row',
|
||||
id: 67890,
|
||||
columnId: 333,
|
||||
timestamp: '2023-10-28T12:05:00Z',
|
||||
userId: 54321,
|
||||
rowData: {
|
||||
/* ... full row data ... */
|
||||
},
|
||||
},
|
||||
handshakeConfiguration: {
|
||||
strategy: WebhookHandshakeStrategy.BODY_PARAM_PRESENT,
|
||||
paramName: 'challenge',
|
||||
},
|
||||
async onHandshake(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
smartsheetHookResponse: (context.payload.body as any)['challenge'],
|
||||
},
|
||||
};
|
||||
},
|
||||
async onEnable(context) {
|
||||
const { sheet_id } = context.propsValue;
|
||||
if (!sheet_id) throw new Error('Sheet ID is required.');
|
||||
|
||||
const triggerIdentifier = context.webhookUrl.substring(context.webhookUrl.lastIndexOf('/') + 1);
|
||||
const webhook = await findOrCreateWebhook(
|
||||
context.auth.secret_text,
|
||||
context.webhookUrl,
|
||||
sheet_id as string,
|
||||
triggerIdentifier,
|
||||
);
|
||||
|
||||
await context.store.put<WebhookInformation>(TRIGGER_KEY, {
|
||||
webhookId: webhook.id.toString(),
|
||||
sharedSecret: webhook.sharedSecret,
|
||||
webhookName: webhook.name,
|
||||
});
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (webhookInfo && webhookInfo.webhookId) {
|
||||
try {
|
||||
await unsubscribeWebhook(context.auth.secret_text, webhookInfo.webhookId);
|
||||
} catch (error: any) {
|
||||
if (error.response?.status !== 404) {
|
||||
console.error(`Error unsubscribing webhook ${webhookInfo.webhookId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
await context.store.delete(TRIGGER_KEY);
|
||||
}
|
||||
},
|
||||
|
||||
async run(context): Promise<unknown[]> {
|
||||
const payload = context.payload.body as any;
|
||||
const headers = context.payload.headers as Record<string, string | undefined>;
|
||||
const webhookInfo = await context.store.get<WebhookInformation>(TRIGGER_KEY);
|
||||
|
||||
if (!webhookInfo) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (headers && headers['smartsheet-hook-challenge']) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const webhookSecret = webhookInfo?.sharedSecret;
|
||||
const webhookSignatureHeader = context.payload.headers['smartsheet-hmac-sha256'];
|
||||
const rawBody = context.payload.rawBody;
|
||||
|
||||
if (!verifyWebhookSignature(webhookSecret, webhookSignatureHeader, rawBody)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (payload.newWebhookStatus) {
|
||||
return [];
|
||||
}
|
||||
if (!payload.events || !Array.isArray(payload.events) || payload.events.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const updatedRowEvents = [];
|
||||
for (const event of payload.events) {
|
||||
if (event.objectType === 'row' && event.eventType === 'updated') {
|
||||
const eventOutput: any = { ...event, sheetId: payload.scopeObjectId?.toString() };
|
||||
const objectSheetId = payload.scopeObjectId?.toString();
|
||||
if (objectSheetId) {
|
||||
try {
|
||||
eventOutput.rowData = await getSheetRowDetails(
|
||||
context.auth.secret_text,
|
||||
objectSheetId,
|
||||
event.id.toString(),
|
||||
);
|
||||
} catch (error: any) {
|
||||
eventOutput.fetchError = error.message;
|
||||
}
|
||||
} else {
|
||||
eventOutput.fetchError = 'scopeObjectId missing';
|
||||
}
|
||||
|
||||
updatedRowEvents.push(eventOutput);
|
||||
}
|
||||
}
|
||||
return updatedRowEvents;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user