n8n-nodes-steyi-ss 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -0
- package/dist/credentials/SteyiSmartsheetCreds.credentials.d.ts +9 -0
- package/dist/credentials/SteyiSmartsheetCreds.credentials.js +38 -0
- package/dist/nodes/SteyiSmartsheet/SteyiGenericFunction.d.ts +3 -0
- package/dist/nodes/SteyiSmartsheet/SteyiGenericFunction.js +58 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheet.node.d.ts +19 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheet.node.js +1988 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheetApi.d.ts +24 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheetApi.js +174 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheetTrigger.node.d.ts +17 -0
- package/dist/nodes/SteyiSmartsheet/SteyiSmartsheetTrigger.node.js +173 -0
- package/dist/nodes/SteyiSmartsheet/executors/Admin.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Admin.js +180 -0
- package/dist/nodes/SteyiSmartsheet/executors/Columns.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Columns.js +53 -0
- package/dist/nodes/SteyiSmartsheet/executors/Reports.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Reports.js +27 -0
- package/dist/nodes/SteyiSmartsheet/executors/Rows.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Rows.js +845 -0
- package/dist/nodes/SteyiSmartsheet/executors/Sheets.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Sheets.js +85 -0
- package/dist/nodes/SteyiSmartsheet/executors/Webhooks.d.ts +2 -0
- package/dist/nodes/SteyiSmartsheet/executors/Webhooks.js +67 -0
- package/dist/nodes/SteyiSmartsheet/steyi-smartsheet.svg +6 -0
- package/package.json +56 -0
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeRowOperation = void 0;
|
|
4
|
+
const SteyiGenericFunction_1 = require("../SteyiGenericFunction");
|
|
5
|
+
const SteyiSmartsheetApi_1 = require("../SteyiSmartsheetApi");
|
|
6
|
+
// Helper function to handle attachment uploads
|
|
7
|
+
async function handleAttachment(att, sheetId, rowId, itemIndex) {
|
|
8
|
+
if (att.attachmentType === 'FILE') {
|
|
9
|
+
// For FILE type, upload binary data
|
|
10
|
+
let binaryData;
|
|
11
|
+
let fileName = att.name;
|
|
12
|
+
let mimeType = 'application/octet-stream';
|
|
13
|
+
// Check if URL is a binary data reference
|
|
14
|
+
if (att.url && att.url !== '') {
|
|
15
|
+
// Get binary data by property name (similar to HTTP Request node's "Input Data Field Name")
|
|
16
|
+
const binaryPropertyName = att.url.trim(); // Trim whitespace
|
|
17
|
+
// Use n8n's helper to get binary data buffer - this handles all conversion automatically
|
|
18
|
+
// Similar to how HTTP Request node handles "n8n Binary File" body content type
|
|
19
|
+
try {
|
|
20
|
+
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryPropertyName);
|
|
21
|
+
const BufferClass = eval('Buffer');
|
|
22
|
+
// Check the structure - it might be the buffer directly or wrapped in an object
|
|
23
|
+
if (BufferClass && BufferClass.isBuffer && BufferClass.isBuffer(binaryDataBuffer)) {
|
|
24
|
+
// It's a Buffer directly
|
|
25
|
+
binaryData = binaryDataBuffer;
|
|
26
|
+
// Get metadata from the item
|
|
27
|
+
const items = this.getInputData();
|
|
28
|
+
const item = items[itemIndex];
|
|
29
|
+
const binary = item.binary?.[binaryPropertyName];
|
|
30
|
+
fileName = att.name || binary?.fileName || 'attachment';
|
|
31
|
+
mimeType = binary?.mimeType || 'application/octet-stream';
|
|
32
|
+
}
|
|
33
|
+
else if (binaryDataBuffer && binaryDataBuffer.data) {
|
|
34
|
+
// It's wrapped in an object with data property
|
|
35
|
+
binaryData = binaryDataBuffer.data;
|
|
36
|
+
fileName = att.name || binaryDataBuffer.fileName || 'attachment';
|
|
37
|
+
mimeType = binaryDataBuffer.mimeType || 'application/octet-stream';
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error(`Unexpected binary data structure: ${typeof binaryDataBuffer}. Expected Buffer or object with data property.`);
|
|
41
|
+
}
|
|
42
|
+
// Validate the buffer
|
|
43
|
+
if (!binaryData) {
|
|
44
|
+
throw new Error(`Invalid buffer returned. Type: ${typeof binaryData}`);
|
|
45
|
+
}
|
|
46
|
+
if (!BufferClass || !BufferClass.isBuffer || !BufferClass.isBuffer(binaryData)) {
|
|
47
|
+
throw new Error(`Data is not a Buffer. Type: ${typeof binaryData}`);
|
|
48
|
+
}
|
|
49
|
+
if (binaryData.length === 0) {
|
|
50
|
+
throw new Error(`Buffer is empty. Binary property "${binaryPropertyName}" may not contain valid data.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Fallback: manually get binary data from item
|
|
55
|
+
const items = this.getInputData();
|
|
56
|
+
const item = items[itemIndex];
|
|
57
|
+
if (!item.binary) {
|
|
58
|
+
const availableProps = 'none';
|
|
59
|
+
throw new Error(`No binary data found in input item. Available binary properties: ${availableProps}. Make sure the previous node outputs binary data.`);
|
|
60
|
+
}
|
|
61
|
+
if (!item.binary[binaryPropertyName]) {
|
|
62
|
+
// List available binary property names to help user
|
|
63
|
+
const availableProps = Object.keys(item.binary).join(', ');
|
|
64
|
+
throw new Error(`Binary property "${binaryPropertyName}" not found. Available binary properties: ${availableProps || 'none'}. Make sure you're using the correct property name from the previous node.`);
|
|
65
|
+
}
|
|
66
|
+
const binary = item.binary[binaryPropertyName];
|
|
67
|
+
// Get the base64 data
|
|
68
|
+
let base64Data = binary.data;
|
|
69
|
+
// Remove data URL prefix if present (e.g., "data:application/pdf;base64,")
|
|
70
|
+
if (base64Data.includes(',')) {
|
|
71
|
+
base64Data = base64Data.split(',')[1];
|
|
72
|
+
}
|
|
73
|
+
// Clean the base64 string - remove whitespace and newlines
|
|
74
|
+
const base64String = base64Data.replace(/\s/g, '');
|
|
75
|
+
// Convert base64 to Buffer - this is critical for proper file upload
|
|
76
|
+
const BufferClass = eval('Buffer');
|
|
77
|
+
if (!BufferClass || typeof BufferClass.from !== 'function') {
|
|
78
|
+
throw new Error('Buffer is not available. Cannot convert base64 to binary data.');
|
|
79
|
+
}
|
|
80
|
+
// Create Buffer from base64 string
|
|
81
|
+
binaryData = BufferClass.from(base64String, 'base64');
|
|
82
|
+
// Verify we have valid binary data
|
|
83
|
+
if (!binaryData || !BufferClass.isBuffer(binaryData)) {
|
|
84
|
+
throw new Error(`Failed to create Buffer from base64 data. Base64 length: ${base64String.length}`);
|
|
85
|
+
}
|
|
86
|
+
// Get file metadata
|
|
87
|
+
fileName = att.name || binary.fileName || 'attachment';
|
|
88
|
+
mimeType = binary.mimeType || 'application/octet-stream';
|
|
89
|
+
}
|
|
90
|
+
// Additional validation - check buffer size
|
|
91
|
+
if (!binaryData || binaryData.length === 0) {
|
|
92
|
+
throw new Error('Binary data buffer is empty. Cannot upload file.');
|
|
93
|
+
}
|
|
94
|
+
// Upload file using raw binary data
|
|
95
|
+
// Ensure rowId is a number
|
|
96
|
+
const rowIdNum = typeof rowId === 'number' ? rowId : parseInt(String(rowId), 10);
|
|
97
|
+
if (isNaN(rowIdNum)) {
|
|
98
|
+
throw new Error(`Invalid row ID: ${rowId}`);
|
|
99
|
+
}
|
|
100
|
+
// Check if this should be added to a proof (use /proofs endpoint) or regular attachment (use /attachments endpoint)
|
|
101
|
+
const isProof = att.attachmentCategory === 'proof';
|
|
102
|
+
const endpoint = isProof
|
|
103
|
+
? `/sheets/${sheetId}/rows/${rowIdNum}/proofs`
|
|
104
|
+
: `/sheets/${sheetId}/rows/${rowIdNum}/attachments`;
|
|
105
|
+
await SteyiGenericFunction_1.smartsheetFileUpload.call(this, endpoint, fileName, binaryData, mimeType, itemIndex);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error('File attachment requires binary data. Please specify a binary property name (e.g., "data") in the URL field.');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// For LINK type, use JSON API
|
|
113
|
+
// Ensure rowId is a number
|
|
114
|
+
const rowIdNum = typeof rowId === 'number' ? rowId : parseInt(String(rowId), 10);
|
|
115
|
+
if (isNaN(rowIdNum)) {
|
|
116
|
+
throw new Error(`Invalid row ID: ${rowId}`);
|
|
117
|
+
}
|
|
118
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', `/sheets/${sheetId}/rows/${rowIdNum}/attachments`, {
|
|
119
|
+
name: att.name,
|
|
120
|
+
url: att.url,
|
|
121
|
+
attachmentType: 'LINK',
|
|
122
|
+
}, itemIndex);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function executeRowOperation(operation, itemIndex) {
|
|
126
|
+
let responseData;
|
|
127
|
+
// Helper function to get sheetId
|
|
128
|
+
const getSheetId = (index) => {
|
|
129
|
+
return this.getNodeParameter('sheetId', index);
|
|
130
|
+
};
|
|
131
|
+
const sheetId = getSheetId(itemIndex);
|
|
132
|
+
switch (operation) {
|
|
133
|
+
case 'addRow': {
|
|
134
|
+
const cellsData = this.getNodeParameter('cells', itemIndex, {});
|
|
135
|
+
const specialOptions = this.getNodeParameter('specialOptions', itemIndex, []);
|
|
136
|
+
let toTop = false;
|
|
137
|
+
let parentId = '';
|
|
138
|
+
if (specialOptions.includes('location')) {
|
|
139
|
+
const location = this.getNodeParameter('location', itemIndex, 'bottom');
|
|
140
|
+
if (location === 'top') {
|
|
141
|
+
toTop = true;
|
|
142
|
+
}
|
|
143
|
+
else if (location === 'parent') {
|
|
144
|
+
parentId = this.getNodeParameter('parentId', itemIndex);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const discussionsData = specialOptions.includes('discussions')
|
|
148
|
+
? this.getNodeParameter('discussions', itemIndex, {})
|
|
149
|
+
: { discussion: undefined };
|
|
150
|
+
const attachmentsData = specialOptions.includes('attachments')
|
|
151
|
+
? this.getNodeParameter('attachments', itemIndex, {})
|
|
152
|
+
: { attachment: undefined };
|
|
153
|
+
// Get column information to determine column types
|
|
154
|
+
let columnsMap = new Map();
|
|
155
|
+
try {
|
|
156
|
+
const columnsResponse = await SteyiSmartsheetApi_1.listColumns.call(this, parseInt(sheetId));
|
|
157
|
+
if (columnsResponse && columnsResponse.data && Array.isArray(columnsResponse.data)) {
|
|
158
|
+
columnsResponse.data.forEach((col) => {
|
|
159
|
+
columnsMap.set(col.id, col);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// If we can't get columns, continue without column type info
|
|
165
|
+
}
|
|
166
|
+
// Helper function to check if a string looks like comma-separated emails
|
|
167
|
+
const looksLikeEmailList = (str) => {
|
|
168
|
+
if (typeof str !== 'string')
|
|
169
|
+
return false;
|
|
170
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
171
|
+
const parts = str.split(',').map(p => p.trim());
|
|
172
|
+
return parts.length > 1 && parts.every(part => emailRegex.test(part));
|
|
173
|
+
};
|
|
174
|
+
const cells = (cellsData.cell || []).map((cell) => {
|
|
175
|
+
let columnId;
|
|
176
|
+
if (cell.columnInputMethod === 'id') {
|
|
177
|
+
columnId = cell.columnIdManual || '';
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
columnId = cell.columnId || '';
|
|
181
|
+
}
|
|
182
|
+
const colIdNum = parseInt(columnId, 10);
|
|
183
|
+
const column = columnsMap.get(colIdNum);
|
|
184
|
+
const columnType = column?.type;
|
|
185
|
+
// Clean up the value - remove surrounding quotes if present
|
|
186
|
+
let cellValue = cell.value;
|
|
187
|
+
if (typeof cellValue === 'string') {
|
|
188
|
+
// Remove surrounding quotes (both single and double)
|
|
189
|
+
cellValue = cellValue.trim();
|
|
190
|
+
if ((cellValue.startsWith('"') && cellValue.endsWith('"')) ||
|
|
191
|
+
(cellValue.startsWith("'") && cellValue.endsWith("'"))) {
|
|
192
|
+
cellValue = cellValue.slice(1, -1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Handle MULTI_CONTACT_LIST and MULTI_PICKLIST columns with objectValue
|
|
196
|
+
// Also handle if value looks like comma-separated emails (fallback for when column type isn't detected)
|
|
197
|
+
if (columnType === 'MULTI_CONTACT_LIST' || columnType === 'MULTI_PICKLIST' ||
|
|
198
|
+
(columnType === undefined && typeof cellValue === 'string' && looksLikeEmailList(cellValue))) {
|
|
199
|
+
// Try to parse the value as JSON if it's a string
|
|
200
|
+
let parsedValue;
|
|
201
|
+
try {
|
|
202
|
+
if (typeof cellValue === 'string') {
|
|
203
|
+
parsedValue = JSON.parse(cellValue);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
parsedValue = cellValue;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
// If parsing fails, treat as regular value (comma-separated string)
|
|
211
|
+
parsedValue = cellValue;
|
|
212
|
+
}
|
|
213
|
+
// If it's already in the correct MULTI_CONTACT format, use it directly
|
|
214
|
+
if (parsedValue && typeof parsedValue === 'object' && parsedValue.objectType === 'MULTI_CONTACT' && parsedValue.values) {
|
|
215
|
+
return {
|
|
216
|
+
columnId: colIdNum,
|
|
217
|
+
objectValue: parsedValue,
|
|
218
|
+
strict: false,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// If it's an array of contacts (old format), wrap it in MULTI_CONTACT structure
|
|
222
|
+
if (parsedValue && Array.isArray(parsedValue) && parsedValue.length > 0) {
|
|
223
|
+
return {
|
|
224
|
+
columnId: colIdNum,
|
|
225
|
+
objectValue: {
|
|
226
|
+
objectType: 'MULTI_CONTACT',
|
|
227
|
+
values: parsedValue.map((contact) => {
|
|
228
|
+
if (typeof contact === 'string') {
|
|
229
|
+
return { email: contact.trim() };
|
|
230
|
+
}
|
|
231
|
+
if (contact.objectType === 'CONTACT') {
|
|
232
|
+
// Remove objectType from contact objects
|
|
233
|
+
const contactObj = { email: contact.email || contact };
|
|
234
|
+
if (contact.name && contact.name !== contact.email) {
|
|
235
|
+
contactObj.name = contact.name;
|
|
236
|
+
}
|
|
237
|
+
return contactObj;
|
|
238
|
+
}
|
|
239
|
+
return contact;
|
|
240
|
+
}),
|
|
241
|
+
},
|
|
242
|
+
strict: false,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// Otherwise, format it properly based on column type
|
|
246
|
+
// If columnType is undefined but value looks like emails, treat as MULTI_CONTACT_LIST
|
|
247
|
+
if (columnType === 'MULTI_CONTACT_LIST' ||
|
|
248
|
+
(columnType === undefined && typeof cellValue === 'string' && looksLikeEmailList(cellValue))) {
|
|
249
|
+
// Format for MULTI_CONTACT_LIST
|
|
250
|
+
let contacts = [];
|
|
251
|
+
if (Array.isArray(parsedValue)) {
|
|
252
|
+
contacts = parsedValue;
|
|
253
|
+
}
|
|
254
|
+
else if (typeof parsedValue === 'string') {
|
|
255
|
+
// Try to parse as JSON array first
|
|
256
|
+
try {
|
|
257
|
+
contacts = JSON.parse(parsedValue);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// If JSON parsing fails, check if it's comma-separated
|
|
261
|
+
if (parsedValue.includes(',')) {
|
|
262
|
+
// Split by comma and trim each email
|
|
263
|
+
contacts = parsedValue.split(',').map((email) => email.trim());
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// If it's a single email, wrap it
|
|
267
|
+
contacts = [parsedValue.trim()];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// For MULTI_CONTACT_LIST, objectValue should be an object with objectType and values array
|
|
272
|
+
return {
|
|
273
|
+
columnId: colIdNum,
|
|
274
|
+
objectValue: {
|
|
275
|
+
objectType: 'MULTI_CONTACT',
|
|
276
|
+
values: contacts.map((contact) => {
|
|
277
|
+
if (typeof contact === 'string') {
|
|
278
|
+
// For comma-separated emails, return only email field
|
|
279
|
+
return {
|
|
280
|
+
email: contact.trim(),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
// For object format, include email and optionally name
|
|
284
|
+
const contactObj = {
|
|
285
|
+
email: contact.email || contact,
|
|
286
|
+
};
|
|
287
|
+
// Only include name if it's different from email
|
|
288
|
+
if (contact.name && contact.name !== contact.email) {
|
|
289
|
+
contactObj.name = contact.name;
|
|
290
|
+
}
|
|
291
|
+
return contactObj;
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
strict: false,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
else if (columnType === 'MULTI_PICKLIST') {
|
|
298
|
+
// Format for MULTI_PICKLIST
|
|
299
|
+
let options = [];
|
|
300
|
+
if (Array.isArray(parsedValue)) {
|
|
301
|
+
options = parsedValue;
|
|
302
|
+
}
|
|
303
|
+
else if (typeof parsedValue === 'string') {
|
|
304
|
+
try {
|
|
305
|
+
options = JSON.parse(parsedValue);
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
options = parsedValue.split(',').map((s) => s.trim());
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
columnId: colIdNum,
|
|
313
|
+
objectValue: {
|
|
314
|
+
objectType: 'MULTI_PICKLIST',
|
|
315
|
+
values: options,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// For regular columns, use value (use cleaned cellValue)
|
|
321
|
+
return {
|
|
322
|
+
columnId: colIdNum,
|
|
323
|
+
value: cellValue,
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
const body = {
|
|
327
|
+
cells,
|
|
328
|
+
};
|
|
329
|
+
if (toTop) {
|
|
330
|
+
body.toTop = true;
|
|
331
|
+
}
|
|
332
|
+
if (parentId && parentId !== '') {
|
|
333
|
+
body.parentId = parseInt(parentId, 10);
|
|
334
|
+
}
|
|
335
|
+
// Don't include discussions in the initial row creation body
|
|
336
|
+
// They need to be added separately after row creation
|
|
337
|
+
// Don't include FILE attachments in the initial row creation body
|
|
338
|
+
// They need to be uploaded separately after row creation
|
|
339
|
+
if (attachmentsData.attachment && attachmentsData.attachment.length > 0) {
|
|
340
|
+
const linkAttachments = attachmentsData.attachment.filter(att => att.attachmentType !== 'FILE');
|
|
341
|
+
if (linkAttachments.length > 0) {
|
|
342
|
+
body.attachments = linkAttachments.map((att) => ({
|
|
343
|
+
name: att.name,
|
|
344
|
+
url: att.url,
|
|
345
|
+
attachmentType: 'LINK',
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Check if any cells use objectValue (complex column types) - if so, add level=2 parameter
|
|
350
|
+
const hasComplexColumns = body.cells && body.cells.some((cell) => cell.objectValue);
|
|
351
|
+
const endpoint = hasComplexColumns
|
|
352
|
+
? `/sheets/${sheetId}/rows?level=2`
|
|
353
|
+
: `/sheets/${sheetId}/rows`;
|
|
354
|
+
try {
|
|
355
|
+
responseData = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', endpoint, body, itemIndex);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
// Include request body in error for debugging
|
|
359
|
+
const errorMessage = error.response?.data
|
|
360
|
+
? `Smartsheet API Error: ${JSON.stringify(error.response.data)}. Request body: ${JSON.stringify(body)}`
|
|
361
|
+
: error.message;
|
|
362
|
+
throw new Error(errorMessage);
|
|
363
|
+
}
|
|
364
|
+
// Add discussion/comment/attachments separately if needed
|
|
365
|
+
// Smartsheet API returns: { result: { id: ... } } or { result: [{ id: ... }] }
|
|
366
|
+
let rowId;
|
|
367
|
+
if (responseData.result) {
|
|
368
|
+
if (Array.isArray(responseData.result)) {
|
|
369
|
+
rowId = responseData.result[0]?.id;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
rowId = responseData.result.id;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
else if (responseData.id) {
|
|
376
|
+
rowId = responseData.id;
|
|
377
|
+
}
|
|
378
|
+
else if (Array.isArray(responseData) && responseData[0]?.id) {
|
|
379
|
+
rowId = responseData[0].id;
|
|
380
|
+
}
|
|
381
|
+
if (!rowId) {
|
|
382
|
+
throw new Error('Failed to get row ID from response. Response: ' + JSON.stringify(responseData));
|
|
383
|
+
}
|
|
384
|
+
// Handle discussions separately (after row creation)
|
|
385
|
+
if (discussionsData.discussion && discussionsData.discussion.length > 0) {
|
|
386
|
+
for (const disc of discussionsData.discussion) {
|
|
387
|
+
try {
|
|
388
|
+
if (disc.action === 'addToExisting') {
|
|
389
|
+
// Use discussionId from dropdown or manual entry
|
|
390
|
+
const discussionId = disc.discussionId || disc.discussionIdManual;
|
|
391
|
+
if (!discussionId) {
|
|
392
|
+
throw new Error('Discussion ID is required when adding to existing discussion');
|
|
393
|
+
}
|
|
394
|
+
// Add comment to existing discussion
|
|
395
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', `/sheets/${sheetId}/discussions/${discussionId}/comments`, {
|
|
396
|
+
text: disc.commentText,
|
|
397
|
+
}, itemIndex);
|
|
398
|
+
}
|
|
399
|
+
else if (disc.action === 'createNew') {
|
|
400
|
+
// Create new discussion
|
|
401
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', `/sheets/${sheetId}/rows/${rowId}/discussions`, {
|
|
402
|
+
comment: {
|
|
403
|
+
text: disc.commentText,
|
|
404
|
+
},
|
|
405
|
+
}, itemIndex);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
throw new Error(`Error adding discussion: ${error.message}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Handle attachments separately (after row creation)
|
|
414
|
+
if (attachmentsData.attachment && attachmentsData.attachment.length > 0) {
|
|
415
|
+
for (const att of attachmentsData.attachment) {
|
|
416
|
+
try {
|
|
417
|
+
await handleAttachment.call(this, att, sheetId, rowId, itemIndex);
|
|
418
|
+
}
|
|
419
|
+
catch (error) {
|
|
420
|
+
throw new Error(`Error adding attachment "${att.name}": ${error.message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'getRow': {
|
|
427
|
+
const rowId = this.getNodeParameter('rowId', itemIndex);
|
|
428
|
+
const include = this.getNodeParameter('include', itemIndex, []);
|
|
429
|
+
const exclude = this.getNodeParameter('exclude', itemIndex, []);
|
|
430
|
+
const queryParams = [];
|
|
431
|
+
if (include.length > 0) {
|
|
432
|
+
queryParams.push(`include=${include.join(',')}`);
|
|
433
|
+
}
|
|
434
|
+
if (exclude.length > 0) {
|
|
435
|
+
queryParams.push(`exclude=${exclude.join(',')}`);
|
|
436
|
+
}
|
|
437
|
+
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
438
|
+
responseData = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/rows/${rowId}${queryString}`, {}, itemIndex);
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
case 'getRowMapped': {
|
|
442
|
+
const rowId = this.getNodeParameter('rowId', itemIndex);
|
|
443
|
+
const include = this.getNodeParameter('include', itemIndex, []);
|
|
444
|
+
const exclude = this.getNodeParameter('exclude', itemIndex, []);
|
|
445
|
+
const mappingType = this.getNodeParameter('mappingType', itemIndex, 'columnTitle');
|
|
446
|
+
const queryParams = [];
|
|
447
|
+
if (include.length > 0) {
|
|
448
|
+
queryParams.push(`include=${include.join(',')}`);
|
|
449
|
+
}
|
|
450
|
+
if (exclude.length > 0) {
|
|
451
|
+
queryParams.push(`exclude=${exclude.join(',')}`);
|
|
452
|
+
}
|
|
453
|
+
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
454
|
+
// Get the row
|
|
455
|
+
const rowResponse = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/rows/${rowId}${queryString}`, {}, itemIndex);
|
|
456
|
+
// Get columns for the sheet (needed for both columnTitle and columnId mapping to include title)
|
|
457
|
+
const columnsResponse = await SteyiSmartsheetApi_1.listColumns.call(this, parseInt(sheetId, 10));
|
|
458
|
+
// Helper function to extract columns from various response shapes
|
|
459
|
+
function getColumns(itemJson) {
|
|
460
|
+
if (Array.isArray(itemJson.columns))
|
|
461
|
+
return itemJson.columns;
|
|
462
|
+
if (Array.isArray(itemJson.data) && itemJson.data[0]?.title && itemJson.data[0]?.id) {
|
|
463
|
+
return itemJson.data; // columns in data[]
|
|
464
|
+
}
|
|
465
|
+
if (Array.isArray(itemJson.sheet?.columns))
|
|
466
|
+
return itemJson.sheet.columns;
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
const columns = getColumns(columnsResponse);
|
|
470
|
+
// Helper function to extract row from various response shapes
|
|
471
|
+
function getRow(itemJson) {
|
|
472
|
+
if (itemJson.row?.cells)
|
|
473
|
+
return itemJson.row;
|
|
474
|
+
if (Array.isArray(itemJson.cells))
|
|
475
|
+
return itemJson;
|
|
476
|
+
if (Array.isArray(itemJson.data) && itemJson.data[0]?.cells)
|
|
477
|
+
return itemJson.data[0];
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const row = getRow(rowResponse);
|
|
481
|
+
if (!row) {
|
|
482
|
+
throw new Error('No row found in response');
|
|
483
|
+
}
|
|
484
|
+
// Build mapped object based on mapping type
|
|
485
|
+
let mapped = {};
|
|
486
|
+
// Build lookup: columnId -> column meta (needed for both mapping types)
|
|
487
|
+
const colById = {};
|
|
488
|
+
columns.forEach((col) => {
|
|
489
|
+
colById[String(col.id)] = col;
|
|
490
|
+
});
|
|
491
|
+
if (mappingType === 'columnId') {
|
|
492
|
+
// Map by column ID, but include column title
|
|
493
|
+
for (const cell of row.cells || []) {
|
|
494
|
+
const col = colById[String(cell.columnId)];
|
|
495
|
+
const title = col?.title ?? `column_${cell.columnId}`;
|
|
496
|
+
mapped[String(cell.columnId)] = {
|
|
497
|
+
columnId: cell.columnId,
|
|
498
|
+
title,
|
|
499
|
+
value: cell.value ?? null,
|
|
500
|
+
displayValue: cell.displayValue ?? null,
|
|
501
|
+
objectValue: cell.objectValue ?? null,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// Map by column title (default)
|
|
507
|
+
for (const cell of row.cells || []) {
|
|
508
|
+
const col = colById[String(cell.columnId)];
|
|
509
|
+
const title = col?.title ?? `column_${cell.columnId}`;
|
|
510
|
+
mapped[title] = {
|
|
511
|
+
columnId: cell.columnId,
|
|
512
|
+
title,
|
|
513
|
+
value: cell.value ?? null,
|
|
514
|
+
displayValue: cell.displayValue ?? null,
|
|
515
|
+
objectValue: cell.objectValue ?? null,
|
|
516
|
+
// keep useful column metadata too
|
|
517
|
+
columnType: col?.type,
|
|
518
|
+
columnIndex: col?.index,
|
|
519
|
+
primary: col?.primary ?? false,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Include all fields from the row response, not just mapped fields
|
|
524
|
+
responseData = {
|
|
525
|
+
...rowResponse, // Include all original response data (attachments, discussions, etc. if included)
|
|
526
|
+
rowId: row.id,
|
|
527
|
+
rowNumber: row.rowNumber,
|
|
528
|
+
sheetId: row.sheetId ?? parseInt(sheetId, 10),
|
|
529
|
+
mappingType,
|
|
530
|
+
mapped, // Add the mapped fields as a convenience
|
|
531
|
+
};
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
case 'updateRow': {
|
|
535
|
+
const rowId = this.getNodeParameter('rowId', itemIndex);
|
|
536
|
+
const specialOptions = this.getNodeParameter('specialOptions', itemIndex, []);
|
|
537
|
+
let parentId = '';
|
|
538
|
+
if (specialOptions.includes('location')) {
|
|
539
|
+
const location = this.getNodeParameter('location', itemIndex, 'bottom');
|
|
540
|
+
if (location === 'parent') {
|
|
541
|
+
parentId = this.getNodeParameter('parentId', itemIndex);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const cellsData = this.getNodeParameter('cells', itemIndex, {});
|
|
545
|
+
const discussionsData = specialOptions.includes('discussions')
|
|
546
|
+
? this.getNodeParameter('discussions', itemIndex, {})
|
|
547
|
+
: { discussion: undefined };
|
|
548
|
+
const attachmentsData = specialOptions.includes('attachments')
|
|
549
|
+
? this.getNodeParameter('attachments', itemIndex, {})
|
|
550
|
+
: { attachment: undefined };
|
|
551
|
+
// First, get discussions for the row to show in response
|
|
552
|
+
let existingDiscussions = [];
|
|
553
|
+
try {
|
|
554
|
+
const discussionsResponse = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/rows/${rowId}/discussions?include=comments`, {}, itemIndex);
|
|
555
|
+
if (discussionsResponse.data && Array.isArray(discussionsResponse.data)) {
|
|
556
|
+
existingDiscussions = discussionsResponse.data;
|
|
557
|
+
}
|
|
558
|
+
else if (Array.isArray(discussionsResponse)) {
|
|
559
|
+
existingDiscussions = discussionsResponse;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
catch (error) {
|
|
563
|
+
// If we can't get discussions, continue without existing discussions
|
|
564
|
+
}
|
|
565
|
+
// Get column information to determine column types
|
|
566
|
+
let columnsMap = new Map();
|
|
567
|
+
try {
|
|
568
|
+
const columnsResponse = await SteyiSmartsheetApi_1.listColumns.call(this, parseInt(sheetId));
|
|
569
|
+
if (columnsResponse && columnsResponse.data && Array.isArray(columnsResponse.data)) {
|
|
570
|
+
columnsResponse.data.forEach((col) => {
|
|
571
|
+
columnsMap.set(col.id, col);
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
// If we can't get columns, continue without column type info
|
|
577
|
+
}
|
|
578
|
+
// Helper function to check if a string looks like comma-separated emails
|
|
579
|
+
const looksLikeEmailList = (str) => {
|
|
580
|
+
if (typeof str !== 'string')
|
|
581
|
+
return false;
|
|
582
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
583
|
+
const parts = str.split(',').map(p => p.trim());
|
|
584
|
+
return parts.length > 1 && parts.every(part => emailRegex.test(part));
|
|
585
|
+
};
|
|
586
|
+
const cells = (cellsData.cell || []).map((cell) => {
|
|
587
|
+
let columnId;
|
|
588
|
+
if (cell.columnInputMethod === 'id') {
|
|
589
|
+
columnId = cell.columnIdManual || '';
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
columnId = cell.columnId || '';
|
|
593
|
+
}
|
|
594
|
+
const colIdNum = parseInt(columnId, 10);
|
|
595
|
+
const column = columnsMap.get(colIdNum);
|
|
596
|
+
const columnType = column?.type;
|
|
597
|
+
// Clean up the value - remove surrounding quotes if present
|
|
598
|
+
let cellValue = cell.value;
|
|
599
|
+
if (typeof cellValue === 'string') {
|
|
600
|
+
// Remove surrounding quotes (both single and double)
|
|
601
|
+
cellValue = cellValue.trim();
|
|
602
|
+
if ((cellValue.startsWith('"') && cellValue.endsWith('"')) ||
|
|
603
|
+
(cellValue.startsWith("'") && cellValue.endsWith("'"))) {
|
|
604
|
+
cellValue = cellValue.slice(1, -1);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// Handle MULTI_CONTACT_LIST and MULTI_PICKLIST columns with objectValue
|
|
608
|
+
// Also handle if value looks like comma-separated emails (fallback for when column type isn't detected)
|
|
609
|
+
if (columnType === 'MULTI_CONTACT_LIST' || columnType === 'MULTI_PICKLIST' ||
|
|
610
|
+
(columnType === undefined && typeof cellValue === 'string' && looksLikeEmailList(cellValue))) {
|
|
611
|
+
// Try to parse the value as JSON if it's a string
|
|
612
|
+
let parsedValue;
|
|
613
|
+
try {
|
|
614
|
+
if (typeof cellValue === 'string') {
|
|
615
|
+
parsedValue = JSON.parse(cellValue);
|
|
616
|
+
}
|
|
617
|
+
else {
|
|
618
|
+
parsedValue = cellValue;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
// If parsing fails, treat as regular value (comma-separated string)
|
|
623
|
+
parsedValue = cellValue;
|
|
624
|
+
}
|
|
625
|
+
// If it's already in the correct MULTI_CONTACT format, use it directly
|
|
626
|
+
if (parsedValue && typeof parsedValue === 'object' && parsedValue.objectType === 'MULTI_CONTACT' && parsedValue.values) {
|
|
627
|
+
return {
|
|
628
|
+
columnId: colIdNum,
|
|
629
|
+
objectValue: parsedValue,
|
|
630
|
+
strict: false,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
// If it's an array of contacts (old format), wrap it in MULTI_CONTACT structure
|
|
634
|
+
if (parsedValue && Array.isArray(parsedValue) && parsedValue.length > 0) {
|
|
635
|
+
return {
|
|
636
|
+
columnId: colIdNum,
|
|
637
|
+
objectValue: {
|
|
638
|
+
objectType: 'MULTI_CONTACT',
|
|
639
|
+
values: parsedValue.map((contact) => {
|
|
640
|
+
if (typeof contact === 'string') {
|
|
641
|
+
return { email: contact.trim() };
|
|
642
|
+
}
|
|
643
|
+
if (contact.objectType === 'CONTACT') {
|
|
644
|
+
// Remove objectType from contact objects
|
|
645
|
+
const contactObj = { email: contact.email || contact };
|
|
646
|
+
if (contact.name && contact.name !== contact.email) {
|
|
647
|
+
contactObj.name = contact.name;
|
|
648
|
+
}
|
|
649
|
+
return contactObj;
|
|
650
|
+
}
|
|
651
|
+
return contact;
|
|
652
|
+
}),
|
|
653
|
+
},
|
|
654
|
+
strict: false,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
// Otherwise, format it properly based on column type
|
|
658
|
+
// If columnType is undefined but value looks like emails, treat as MULTI_CONTACT_LIST
|
|
659
|
+
if (columnType === 'MULTI_CONTACT_LIST' ||
|
|
660
|
+
(columnType === undefined && typeof cellValue === 'string' && looksLikeEmailList(cellValue))) {
|
|
661
|
+
// Format for MULTI_CONTACT_LIST
|
|
662
|
+
let contacts = [];
|
|
663
|
+
if (Array.isArray(parsedValue)) {
|
|
664
|
+
contacts = parsedValue;
|
|
665
|
+
}
|
|
666
|
+
else if (typeof parsedValue === 'string') {
|
|
667
|
+
// Try to parse as JSON array first
|
|
668
|
+
try {
|
|
669
|
+
contacts = JSON.parse(parsedValue);
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
// If JSON parsing fails, check if it's comma-separated
|
|
673
|
+
if (parsedValue.includes(',')) {
|
|
674
|
+
// Split by comma and trim each email
|
|
675
|
+
contacts = parsedValue.split(',').map((email) => email.trim());
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
// If it's a single email, wrap it
|
|
679
|
+
contacts = [parsedValue.trim()];
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
// For MULTI_CONTACT_LIST, objectValue should be an object with objectType and values array
|
|
684
|
+
return {
|
|
685
|
+
columnId: colIdNum,
|
|
686
|
+
objectValue: {
|
|
687
|
+
objectType: 'MULTI_CONTACT',
|
|
688
|
+
values: contacts.map((contact) => {
|
|
689
|
+
if (typeof contact === 'string') {
|
|
690
|
+
// For comma-separated emails, return only email field
|
|
691
|
+
return {
|
|
692
|
+
email: contact.trim(),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
// For object format, include email and optionally name
|
|
696
|
+
const contactObj = {
|
|
697
|
+
email: contact.email || contact,
|
|
698
|
+
};
|
|
699
|
+
// Only include name if it's different from email
|
|
700
|
+
if (contact.name && contact.name !== contact.email) {
|
|
701
|
+
contactObj.name = contact.name;
|
|
702
|
+
}
|
|
703
|
+
return contactObj;
|
|
704
|
+
}),
|
|
705
|
+
},
|
|
706
|
+
strict: false,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
else if (columnType === 'MULTI_PICKLIST') {
|
|
710
|
+
// Format for MULTI_PICKLIST
|
|
711
|
+
let options = [];
|
|
712
|
+
if (Array.isArray(parsedValue)) {
|
|
713
|
+
options = parsedValue;
|
|
714
|
+
}
|
|
715
|
+
else if (typeof parsedValue === 'string') {
|
|
716
|
+
try {
|
|
717
|
+
options = JSON.parse(parsedValue);
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
options = parsedValue.split(',').map((s) => s.trim());
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
columnId: colIdNum,
|
|
725
|
+
objectValue: {
|
|
726
|
+
objectType: 'MULTI_PICKLIST',
|
|
727
|
+
values: options,
|
|
728
|
+
},
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
// For regular columns, use value (use cleaned cellValue)
|
|
733
|
+
return {
|
|
734
|
+
columnId: colIdNum,
|
|
735
|
+
value: cellValue,
|
|
736
|
+
};
|
|
737
|
+
});
|
|
738
|
+
const body = {
|
|
739
|
+
id: parseInt(rowId, 10),
|
|
740
|
+
cells,
|
|
741
|
+
};
|
|
742
|
+
if (parentId && parentId !== '') {
|
|
743
|
+
body.parentId = parseInt(parentId, 10);
|
|
744
|
+
}
|
|
745
|
+
// Don't include FILE attachments in the row update body
|
|
746
|
+
// They need to be uploaded separately
|
|
747
|
+
if (attachmentsData.attachment && attachmentsData.attachment.length > 0) {
|
|
748
|
+
const linkAttachments = attachmentsData.attachment.filter(att => att.attachmentType !== 'FILE');
|
|
749
|
+
if (linkAttachments.length > 0) {
|
|
750
|
+
body.attachments = linkAttachments.map((att) => ({
|
|
751
|
+
name: att.name,
|
|
752
|
+
url: att.url,
|
|
753
|
+
attachmentType: 'LINK',
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Check if any cells use objectValue (complex column types) - if so, add level=2 parameter
|
|
758
|
+
const hasComplexColumns = cells.some((cell) => cell.objectValue);
|
|
759
|
+
const endpoint = hasComplexColumns
|
|
760
|
+
? `/sheets/${sheetId}/rows?level=2`
|
|
761
|
+
: `/sheets/${sheetId}/rows`;
|
|
762
|
+
try {
|
|
763
|
+
responseData = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'PUT', endpoint, [body], itemIndex);
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
// Include request body in error for debugging
|
|
767
|
+
const errorMessage = error.response?.data
|
|
768
|
+
? `Smartsheet API Error: ${JSON.stringify(error.response.data)}. Request body: ${JSON.stringify([body])}`
|
|
769
|
+
: error.message;
|
|
770
|
+
throw new Error(errorMessage);
|
|
771
|
+
}
|
|
772
|
+
// Include existing discussions in response
|
|
773
|
+
if (existingDiscussions.length > 0) {
|
|
774
|
+
responseData.discussions = existingDiscussions;
|
|
775
|
+
}
|
|
776
|
+
// Handle discussions separately (after row update)
|
|
777
|
+
if (discussionsData.discussion && discussionsData.discussion.length > 0) {
|
|
778
|
+
for (const disc of discussionsData.discussion) {
|
|
779
|
+
try {
|
|
780
|
+
if (disc.action === 'addToExisting') {
|
|
781
|
+
// Use discussionId from dropdown or manual entry
|
|
782
|
+
const discussionId = disc.discussionId || disc.discussionIdManual;
|
|
783
|
+
if (!discussionId) {
|
|
784
|
+
throw new Error('Discussion ID is required when adding to existing discussion');
|
|
785
|
+
}
|
|
786
|
+
// Add comment to existing discussion
|
|
787
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', `/sheets/${sheetId}/discussions/${discussionId}/comments`, {
|
|
788
|
+
text: disc.commentText,
|
|
789
|
+
}, itemIndex);
|
|
790
|
+
}
|
|
791
|
+
else if (disc.action === 'createNew') {
|
|
792
|
+
// Create new discussion
|
|
793
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'POST', `/sheets/${sheetId}/rows/${rowId}/discussions`, {
|
|
794
|
+
comment: {
|
|
795
|
+
text: disc.commentText,
|
|
796
|
+
},
|
|
797
|
+
}, itemIndex);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
throw new Error(`Error adding discussion: ${error.message}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// Refresh discussions in response after adding new ones
|
|
805
|
+
try {
|
|
806
|
+
const updatedDiscussions = await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/rows/${rowId}/discussions?include=comments`, {}, itemIndex);
|
|
807
|
+
if (updatedDiscussions.data && Array.isArray(updatedDiscussions.data)) {
|
|
808
|
+
responseData.discussions = updatedDiscussions.data;
|
|
809
|
+
}
|
|
810
|
+
else if (Array.isArray(updatedDiscussions)) {
|
|
811
|
+
responseData.discussions = updatedDiscussions;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
catch (error) {
|
|
815
|
+
// Continue if we can't refresh discussions
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// Handle attachments separately (after row update)
|
|
819
|
+
if (attachmentsData.attachment && attachmentsData.attachment.length > 0) {
|
|
820
|
+
for (const att of attachmentsData.attachment) {
|
|
821
|
+
try {
|
|
822
|
+
await handleAttachment.call(this, att, sheetId, parseInt(rowId, 10), itemIndex);
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
throw new Error(`Error adding attachment "${att.name}": ${error.message}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
case 'deleteRow': {
|
|
832
|
+
const rowId = this.getNodeParameter('rowId', itemIndex);
|
|
833
|
+
await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'DELETE', `/sheets/${sheetId}/rows?ids=${rowId}`, {}, itemIndex);
|
|
834
|
+
responseData = { success: true, message: 'Row deleted successfully' };
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
default:
|
|
838
|
+
throw new Error(`Unknown row operation: ${operation}`);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
json: responseData,
|
|
842
|
+
pairedItem: { item: itemIndex },
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
exports.executeRowOperation = executeRowOperation;
|