n8n-nodes-steyi-ss 1.0.2 → 1.0.4

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 CHANGED
@@ -199,17 +199,65 @@ Add a new row to a sheet:
199
199
  - Attachment category (Regular Attachment or Proof)
200
200
  - Custom filename
201
201
 
202
- **Multi-Contact List Formatting:**
203
- When adding or updating rows with `MULTI_CONTACT_LIST` columns, you can provide:
204
- - Comma-separated email string: `"email1@example.com, email2@example.com"`
205
- - JSON array: `["email1@example.com", "email2@example.com"]`
206
-
207
- The node automatically formats these for the Smartsheet API.
208
-
209
- **Multi-Picklist Formatting:**
210
- For `MULTI_PICKLIST` columns, provide:
211
- - Comma-separated options: `"Option 1, Option 2"`
212
- - JSON array: `["Option 1", "Option 2"]`
202
+ ## Column Value Formatting
203
+
204
+ When adding or updating rows, different column types require specific value formats:
205
+
206
+ ### TEXT_NUMBER, DATE, DATETIME
207
+ - **Format**: Plain text or number
208
+ - **Example**: `"John Doe"`, `123`, `"2024-01-15"`
209
+
210
+ ### CHECKBOX
211
+ - **Format**: String values that are auto-converted to boolean
212
+ - **Accepted values**: `"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"` (case-insensitive)
213
+ - **Example**: `"true"` → `true`, `"false"` → `false`
214
+ - The node automatically converts these strings to boolean values
215
+
216
+ ### PICKLIST
217
+ - **Format**: Option text (must match exactly, case-sensitive)
218
+ - **Example**: `"High Priority"` (must match an option defined in the column)
219
+
220
+ ### MULTI_CONTACT_LIST
221
+ - **Format**: JSON object with `values` array containing email objects
222
+ - **Example**:
223
+ ```json
224
+ {
225
+ "values": [
226
+ { "email": "john.smith@example.com" },
227
+ { "email": "jane.doe@example.com" }
228
+ ]
229
+ }
230
+ ```
231
+ - The node automatically transforms this to the correct Smartsheet API format:
232
+ ```json
233
+ {
234
+ "objectType": "MULTI_CONTACT",
235
+ "values": [
236
+ { "objectType": "CONTACT", "email": "john.smith@example.com" },
237
+ { "objectType": "CONTACT", "email": "jane.doe@example.com" }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ ### MULTI_PICKLIST
243
+ - **Format**: JSON object with `values` array containing option strings
244
+ - **Example**:
245
+ ```json
246
+ {
247
+ "values": [
248
+ "Option 1",
249
+ "Option 2"
250
+ ]
251
+ }
252
+ ```
253
+ - **Important**: Option values must match exactly (case-sensitive) the options defined in the MULTI_PICKLIST column
254
+ - The node automatically transforms this to the correct Smartsheet API format:
255
+ ```json
256
+ {
257
+ "objectType": "MULTI_PICKLIST",
258
+ "values": ["Option 1", "Option 2"]
259
+ }
260
+ ```
213
261
 
214
262
  #### Update Row
215
263
  Update an existing row with the same options as Add Row:
@@ -413,15 +461,23 @@ For the trigger node, n8n automatically generates the webhook URL, but it must b
413
461
 
414
462
  ### Multi-Contact List Support
415
463
  The node automatically handles `MULTI_CONTACT_LIST` columns by:
416
- - Parsing comma-separated email strings
417
- - Formatting as proper Smartsheet API `objectValue` structure
418
- - Supporting `strict: false` mode for flexible updates
464
+ - Accepting JSON format: `{ "values": [{ "email": "..." }, ...] }`
465
+ - Automatically adding `objectType: "MULTI_CONTACT"` to the objectValue
466
+ - Wrapping each contact with `objectType: "CONTACT"` in the values array
467
+ - Validating email format and structure
419
468
 
420
469
  ### Multi-Picklist Support
421
470
  The node handles `MULTI_PICKLIST` columns by:
422
- - Parsing comma-separated option strings
423
- - Formatting as proper Smartsheet API `objectValue` structure
424
- - Validating against column options
471
+ - Accepting JSON format: `{ "values": ["Option 1", "Option 2"] }`
472
+ - Automatically adding `objectType: "MULTI_PICKLIST"` to the objectValue
473
+ - Keeping values as strings (not wrapped in objects)
474
+ - Validating that option values match column options (case-sensitive)
475
+
476
+ ### Checkbox Support
477
+ The node automatically handles `CHECKBOX` columns by:
478
+ - Converting string values to boolean: `"true"`, `"false"`, `"1"`, `"0"`, `"yes"`, `"no"`
479
+ - Case-insensitive conversion
480
+ - Empty values default to `false`
425
481
 
426
482
  ### Attachment Handling
427
483
  - **File Uploads**: Supports binary data from n8n's binary data system
@@ -1,4 +1,27 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  Object.defineProperty(exports, "__esModule", { value: true });
3
26
  exports.SteyiSmartsheet = void 0;
4
27
  const Sheets_1 = require("./executors/Sheets");
@@ -7,6 +30,7 @@ const Webhooks_1 = require("./executors/Webhooks");
7
30
  const Columns_1 = require("./executors/Columns");
8
31
  const Reports_1 = require("./executors/Reports");
9
32
  const Admin_1 = require("./executors/Admin");
33
+ const Converter_1 = require("./executors/Converter");
10
34
  const SteyiSmartsheetApi_1 = require("./SteyiSmartsheetApi");
11
35
  const SteyiGenericFunction_1 = require("./SteyiGenericFunction");
12
36
  class SteyiSmartsheet {
@@ -67,6 +91,11 @@ class SteyiSmartsheet {
67
91
  value: 'admin',
68
92
  description: 'Manage users, groups, and group members',
69
93
  },
94
+ {
95
+ name: 'Converter',
96
+ value: 'converter',
97
+ description: 'Utility tools for data conversion',
98
+ },
70
99
  ],
71
100
  default: 'sheet',
72
101
  required: true,
@@ -571,7 +600,7 @@ class SteyiSmartsheet {
571
600
  },
572
601
  },
573
602
  default: '',
574
- description: 'Select a column',
603
+ description: 'Select a column. The column type is shown in parentheses next to each column name (e.g., "Column Name (MULTI_CONTACT_LIST)")',
575
604
  },
576
605
  {
577
606
  displayName: 'Column ID',
@@ -588,9 +617,9 @@ class SteyiSmartsheet {
588
617
  {
589
618
  displayName: 'Value',
590
619
  name: 'value',
591
- type: 'string',
620
+ type: 'json',
592
621
  default: '',
593
- description: 'The value to set',
622
+ description: 'The value to set. Column type is shown in parentheses in the dropdown above. For MULTI_CONTACT_LIST and MULTI_PICKLIST columns, enter JSON format. For other columns, enter plain text/number. For format examples, see the <a href="https://www.npmjs.com/package/n8n-nodes-steyi-ss" target="_blank">npm package README</a>.',
594
623
  },
595
624
  ],
596
625
  },
@@ -1529,13 +1558,9 @@ class SteyiSmartsheet {
1529
1558
  type: 'options',
1530
1559
  displayOptions: {
1531
1560
  show: {
1532
- resource: ['admin'],
1533
- operation: ['group', 'groupMember'],
1534
- },
1535
- hide: {
1536
1561
  resource: ['admin'],
1537
1562
  operation: ['group'],
1538
- groupOperation: ['listGroups', 'createGroup'],
1563
+ groupOperation: ['getGroup', 'updateGroup', 'deleteGroup'],
1539
1564
  },
1540
1565
  },
1541
1566
  options: [
@@ -1562,14 +1587,10 @@ class SteyiSmartsheet {
1562
1587
  },
1563
1588
  displayOptions: {
1564
1589
  show: {
1565
- resource: ['admin'],
1566
- operation: ['group', 'groupMember'],
1567
- groupInputMethod: ['list'],
1568
- },
1569
- hide: {
1570
1590
  resource: ['admin'],
1571
1591
  operation: ['group'],
1572
- groupOperation: ['listGroups', 'createGroup'],
1592
+ groupOperation: ['getGroup', 'updateGroup', 'deleteGroup'],
1593
+ groupInputMethod: ['list'],
1573
1594
  },
1574
1595
  },
1575
1596
  default: '',
@@ -1583,13 +1604,66 @@ class SteyiSmartsheet {
1583
1604
  displayOptions: {
1584
1605
  show: {
1585
1606
  resource: ['admin'],
1586
- operation: ['group', 'groupMember'],
1607
+ operation: ['group'],
1608
+ groupOperation: ['getGroup', 'updateGroup', 'deleteGroup'],
1587
1609
  groupInputMethod: ['id'],
1588
1610
  },
1589
- hide: {
1611
+ },
1612
+ default: '',
1613
+ required: true,
1614
+ },
1615
+ {
1616
+ displayName: 'Group Input Method',
1617
+ name: 'groupMemberGroupInputMethod',
1618
+ type: 'options',
1619
+ displayOptions: {
1620
+ show: {
1590
1621
  resource: ['admin'],
1591
- operation: ['group'],
1592
- groupOperation: ['listGroups', 'createGroup'],
1622
+ operation: ['groupMember'],
1623
+ },
1624
+ },
1625
+ options: [
1626
+ {
1627
+ name: 'Select from List',
1628
+ value: 'list',
1629
+ },
1630
+ {
1631
+ name: 'Enter Group ID',
1632
+ value: 'id',
1633
+ },
1634
+ ],
1635
+ default: 'list',
1636
+ description: 'Choose how to specify the group',
1637
+ },
1638
+ {
1639
+ displayName: 'Group',
1640
+ name: 'groupMemberGroupId',
1641
+ type: 'options',
1642
+ description: 'Select a group from the list',
1643
+ typeOptions: {
1644
+ loadOptionsMethod: 'getGroups',
1645
+ searchable: true,
1646
+ },
1647
+ displayOptions: {
1648
+ show: {
1649
+ resource: ['admin'],
1650
+ operation: ['groupMember'],
1651
+ groupMemberGroupInputMethod: ['list'],
1652
+ },
1653
+ },
1654
+ default: '',
1655
+ required: true,
1656
+ },
1657
+ {
1658
+ displayName: 'Group ID',
1659
+ name: 'groupMemberGroupIdManual',
1660
+ type: 'string',
1661
+ description: 'Enter the Group ID manually',
1662
+ displayOptions: {
1663
+ show: {
1664
+ resource: ['admin'],
1665
+ operation: ['groupMember'],
1666
+ groupMemberGroupInputMethod: ['id'],
1593
1667
  },
1594
1668
  },
1595
1669
  default: '',
@@ -1749,6 +1823,46 @@ class SteyiSmartsheet {
1749
1823
  default: '',
1750
1824
  required: true,
1751
1825
  },
1826
+ {
1827
+ displayName: 'Operation',
1828
+ name: 'operation',
1829
+ type: 'options',
1830
+ noDataExpression: true,
1831
+ displayOptions: {
1832
+ show: {
1833
+ resource: ['converter'],
1834
+ },
1835
+ },
1836
+ options: [
1837
+ {
1838
+ name: 'CSV to MCL Format',
1839
+ value: 'csvToMclFormat',
1840
+ description: 'Convert comma-separated email values to MULTI_CONTACT_LIST format',
1841
+ action: 'Convert CSV to MCL format',
1842
+ },
1843
+ {
1844
+ name: 'CSV to MPL',
1845
+ value: 'csvToMplFormat',
1846
+ description: 'Convert comma-separated values to MULTI_PICKLIST format',
1847
+ action: 'Convert CSV to MPL format',
1848
+ },
1849
+ ],
1850
+ default: 'csvToMclFormat',
1851
+ },
1852
+ {
1853
+ displayName: 'CSV Values',
1854
+ name: 'csvValues',
1855
+ type: 'string',
1856
+ displayOptions: {
1857
+ show: {
1858
+ resource: ['converter'],
1859
+ operation: ['csvToMclFormat', 'csvToMplFormat'],
1860
+ },
1861
+ },
1862
+ default: '',
1863
+ required: true,
1864
+ description: 'Comma-separated values (e.g., "email1@example.com, email2@example.com" for MCL or "Option 1, Option 2" for MPL)',
1865
+ },
1752
1866
  ],
1753
1867
  };
1754
1868
  this.methods = {
@@ -1768,14 +1882,40 @@ class SteyiSmartsheet {
1768
1882
  if (sheetId === undefined || sheetId === null || sheetId === '') {
1769
1883
  return [];
1770
1884
  }
1771
- const columns = await SteyiSmartsheetApi_1.listColumns.call(this, parseInt(sheetId.toString()));
1772
- if (!columns || !columns.data) {
1885
+ // Get columns with level=2 to ensure we get the correct column types
1886
+ let columns = [];
1887
+ try {
1888
+ const columnsResponse = await SteyiSmartsheetApi_1.listColumns.call(this, parseInt(sheetId.toString()));
1889
+ if (columnsResponse && columnsResponse.data && Array.isArray(columnsResponse.data)) {
1890
+ columns = columnsResponse.data;
1891
+ }
1892
+ }
1893
+ catch (error) {
1894
+ // If columns endpoint fails, try getting the sheet with columns included and level=2
1895
+ try {
1896
+ const { smartsheetApiRequest } = await Promise.resolve().then(() => __importStar(require('./SteyiGenericFunction')));
1897
+ const sheetResponse = await smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}?include=columns&level=2`, {}, 0);
1898
+ if (sheetResponse && sheetResponse.columns && Array.isArray(sheetResponse.columns)) {
1899
+ columns = sheetResponse.columns;
1900
+ }
1901
+ }
1902
+ catch (sheetError) {
1903
+ // If both fail, return empty array
1904
+ return [];
1905
+ }
1906
+ }
1907
+ if (columns.length === 0) {
1773
1908
  return [];
1774
1909
  }
1775
- return columns.data.map((column) => ({
1776
- name: column.title,
1777
- value: column.id.toString(),
1778
- }));
1910
+ return columns.map((column) => {
1911
+ // The type field should be present in the API response with level=2
1912
+ const columnType = column.type || 'UNKNOWN';
1913
+ return {
1914
+ name: `${column.title} (${columnType})`,
1915
+ value: column.id.toString(),
1916
+ description: `Type: ${columnType}`,
1917
+ };
1918
+ });
1779
1919
  },
1780
1920
  async getDiscussions() {
1781
1921
  const sheetId = this.getNodeParameter('sheetId', 0);
@@ -1967,6 +2107,10 @@ class SteyiSmartsheet {
1967
2107
  const result = await Admin_1.executeAdminOperation.call(this, operation, i);
1968
2108
  returnData.push(result);
1969
2109
  }
2110
+ else if (resource === 'converter') {
2111
+ const result = await Converter_1.executeConverterOperation.call(this, operation, i);
2112
+ returnData.push(result);
2113
+ }
1970
2114
  else {
1971
2115
  throw new Error(`Unknown resource: ${resource}`);
1972
2116
  }
@@ -24,7 +24,7 @@ async function listSheets(includeAll = true) {
24
24
  }
25
25
  exports.listSheets = listSheets;
26
26
  async function listColumns(sheetId) {
27
- return await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/columns`, {}, 0);
27
+ return await SteyiGenericFunction_1.smartsheetApiRequest.call(this, 'GET', `/sheets/${sheetId}/columns?level=2`, {}, 0);
28
28
  }
29
29
  exports.listColumns = listColumns;
30
30
  async function listWebhooks() {
@@ -132,10 +132,10 @@ async function executeAdminOperation(operation, itemIndex) {
132
132
  }
133
133
  case 'groupMember': {
134
134
  const groupMemberOperation = this.getNodeParameter('groupMemberOperation', itemIndex);
135
- const groupInputMethod = this.getNodeParameter('groupInputMethod', itemIndex, 'list');
135
+ const groupInputMethod = this.getNodeParameter('groupMemberGroupInputMethod', itemIndex, 'list');
136
136
  const groupId = groupInputMethod === 'id'
137
- ? this.getNodeParameter('groupIdManual', itemIndex)
138
- : this.getNodeParameter('groupId', itemIndex);
137
+ ? this.getNodeParameter('groupMemberGroupIdManual', itemIndex)
138
+ : this.getNodeParameter('groupMemberGroupId', itemIndex);
139
139
  switch (groupMemberOperation) {
140
140
  case 'listGroupMembers': {
141
141
  responseData = await SteyiSmartsheetApi_1.listGroupMembers.call(this, parseInt(groupId.toString()));
@@ -0,0 +1,2 @@
1
+ import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
2
+ export declare function executeConverterOperation(this: IExecuteFunctions, operation: string, itemIndex: number): Promise<INodeExecutionData>;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeConverterOperation = void 0;
4
+ async function executeConverterOperation(operation, itemIndex) {
5
+ switch (operation) {
6
+ case 'csvToMclFormat': {
7
+ const emailsRaw = this.getNodeParameter('csvValues', itemIndex);
8
+ if (!emailsRaw || typeof emailsRaw !== 'string') {
9
+ throw new Error('CSV values input is required and must be a string');
10
+ }
11
+ // Split by comma, trim whitespace, filter out empty strings, and map to email objects
12
+ const values = emailsRaw
13
+ .split(',')
14
+ .map(e => e.trim())
15
+ .filter(e => e.length > 0)
16
+ .map(email => ({ email }));
17
+ // Return as an object with mcl-output property containing values
18
+ return {
19
+ json: {
20
+ 'mcl-output': {
21
+ values: values,
22
+ },
23
+ },
24
+ pairedItem: { item: itemIndex },
25
+ };
26
+ }
27
+ case 'csvToMplFormat': {
28
+ const optionsRaw = this.getNodeParameter('csvValues', itemIndex);
29
+ if (!optionsRaw || typeof optionsRaw !== 'string') {
30
+ throw new Error('CSV values input is required and must be a string');
31
+ }
32
+ // Split by comma, trim whitespace, filter out empty strings (returns array of strings)
33
+ const values = optionsRaw
34
+ .split(',')
35
+ .map(e => e.trim())
36
+ .filter(e => e.length > 0);
37
+ // Return as an object with mpl-output property containing values array
38
+ return {
39
+ json: {
40
+ 'mpl-output': {
41
+ values: values,
42
+ },
43
+ },
44
+ pairedItem: { item: itemIndex },
45
+ };
46
+ }
47
+ default:
48
+ throw new Error(`Unknown converter operation: ${operation}`);
49
+ }
50
+ }
51
+ exports.executeConverterOperation = executeConverterOperation;
@@ -163,7 +163,14 @@ async function executeRowOperation(operation, itemIndex) {
163
163
  catch (error) {
164
164
  // If we can't get columns, continue without column type info
165
165
  }
166
- const cells = (cellsData.cell || []).map((cell) => {
166
+ // Filter out any cells with empty columnId or invalid data
167
+ const validCells = (cellsData.cell || []).filter((cell) => {
168
+ const columnId = cell.columnInputMethod === 'id'
169
+ ? cell.columnIdManual
170
+ : cell.columnId;
171
+ return columnId && columnId !== '' && cell.value !== undefined;
172
+ });
173
+ const cells = validCells.map((cell) => {
167
174
  let columnId;
168
175
  if (cell.columnInputMethod === 'id') {
169
176
  columnId = cell.columnIdManual || '';
@@ -171,7 +178,11 @@ async function executeRowOperation(operation, itemIndex) {
171
178
  else {
172
179
  columnId = cell.columnId || '';
173
180
  }
181
+ // Parse columnId - keep as number for API, but ensure it's valid
174
182
  const colIdNum = parseInt(columnId, 10);
183
+ if (isNaN(colIdNum)) {
184
+ throw new Error(`Invalid column ID: ${columnId}`);
185
+ }
175
186
  const column = columnsMap.get(colIdNum);
176
187
  const columnType = column?.type;
177
188
  // Clean up the value - remove surrounding quotes if present
@@ -185,37 +196,188 @@ async function executeRowOperation(operation, itemIndex) {
185
196
  }
186
197
  }
187
198
  // Handle MULTI_CONTACT_LIST and MULTI_PICKLIST columns with objectValue
188
- // User must provide a JSON block that will be used directly
189
- if (columnType === 'MULTI_CONTACT_LIST' || columnType === 'MULTI_PICKLIST') {
190
- // Parse the value as JSON
191
- let objectValue;
192
- try {
193
- if (typeof cellValue === 'string') {
194
- objectValue = JSON.parse(cellValue);
199
+ // User provides a JSON object (field type is 'json' so n8n parses it automatically)
200
+ if (columnType === 'MULTI_CONTACT_LIST') {
201
+ // Parse the value - if it's a string, try to parse as JSON; if already an object, use it directly
202
+ let parsedValue;
203
+ if (typeof cellValue === 'string') {
204
+ try {
205
+ parsedValue = JSON.parse(cellValue);
206
+ }
207
+ catch (error) {
208
+ throw new Error(`Invalid JSON for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
209
+ `Please provide a valid JSON object with structure: { "values": [{ "email": "..." }, ...] }. ` +
210
+ `Error: ${error instanceof Error ? error.message : String(error)}`);
211
+ }
212
+ }
213
+ else {
214
+ // Already parsed by n8n's JSON field type
215
+ parsedValue = cellValue;
216
+ }
217
+ // Validate that it's an object with values array
218
+ if (typeof parsedValue !== 'object' || parsedValue === null) {
219
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
220
+ `Expected a JSON object with "values" array, got ${typeof parsedValue}.`);
221
+ }
222
+ // Validate that it has a values array
223
+ if (!Array.isArray(parsedValue.values)) {
224
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
225
+ `Expected a JSON object with "values" array property. Example: { "values": [{ "email": "user@example.com" }] }`);
226
+ }
227
+ // Validate that each value in the array has an email property
228
+ // Transform values to include objectType: "CONTACT" for each contact
229
+ const contactValues = parsedValue.values.map((value, i) => {
230
+ if (typeof value !== 'object' || value === null || !value.email) {
231
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
232
+ `Each item in the "values" array must be an object with an "email" property. ` +
233
+ `Example: { "values": [{ "email": "user@example.com" }] }`);
234
+ }
235
+ // Ensure each contact has objectType: "CONTACT"
236
+ return {
237
+ objectType: 'CONTACT',
238
+ email: value.email,
239
+ };
240
+ });
241
+ // For MULTI_CONTACT_LIST, objectValue should have structure:
242
+ // { "objectType": "MULTI_CONTACT", "values": [{ "objectType": "CONTACT", "email": "..." }, ...] }
243
+ // User provides the values array with emails, we add the objectType fields
244
+ // Don't include "strict" for MULTI_CONTACT_LIST as it causes parsing errors
245
+ // Ensure columnId is a number (not string) for the API
246
+ return {
247
+ columnId: Number(colIdNum),
248
+ objectValue: {
249
+ objectType: 'MULTI_CONTACT',
250
+ values: contactValues,
251
+ },
252
+ };
253
+ }
254
+ else if (columnType === 'MULTI_PICKLIST') {
255
+ // Parse the value - if it's a string, try to parse as JSON; if already an object, use it directly
256
+ // Expected format: { "values": ["Option 1", "Option 2", ...] }
257
+ let parsedValue;
258
+ if (typeof cellValue === 'string') {
259
+ try {
260
+ parsedValue = JSON.parse(cellValue);
261
+ }
262
+ catch (error) {
263
+ throw new Error(`Invalid JSON for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
264
+ `Please provide a valid JSON object with structure: { "values": ["Option 1", "Option 2"] }. ` +
265
+ `Error: ${error instanceof Error ? error.message : String(error)}`);
266
+ }
267
+ }
268
+ else {
269
+ // Already parsed by n8n's JSON field type
270
+ parsedValue = cellValue;
271
+ }
272
+ // Validate that it's an object with values array
273
+ if (typeof parsedValue !== 'object' || parsedValue === null) {
274
+ throw new Error(`Invalid objectValue for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
275
+ `Expected a JSON object with "values" array, got ${typeof parsedValue}.`);
276
+ }
277
+ // Validate that it has a values array
278
+ if (!Array.isArray(parsedValue.values)) {
279
+ throw new Error(`Invalid objectValue for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
280
+ `Expected a JSON object with "values" array property. Example: { "values": ["Option 1", "Option 2"] }`);
281
+ }
282
+ // For MULTI_PICKLIST, values should be an array of strings, not objects
283
+ // Transform values to ensure they're all strings
284
+ // Note: Picklist values must match exactly (case-sensitive) the options defined in the column
285
+ const picklistValues = parsedValue.values.map((value) => {
286
+ // Value should be a string (the option text)
287
+ if (typeof value === 'string') {
288
+ return value;
289
+ }
290
+ else if (typeof value === 'object' && value !== null && value.value) {
291
+ // If it's an object with a value property, extract the string value
292
+ return String(value.value);
195
293
  }
196
294
  else {
197
- objectValue = cellValue;
295
+ throw new Error(`Invalid value in MULTI_PICKLIST values array (Column ID: ${colIdNum}). ` +
296
+ `Each value must be a string (option text). ` +
297
+ `Note: Option values must match exactly (case-sensitive) the options defined in the MULTI_PICKLIST column.`);
198
298
  }
299
+ });
300
+ // For MULTI_PICKLIST, objectValue should have structure:
301
+ // { "objectType": "MULTI_PICKLIST", "values": ["Option 1", "Option 2", ...] }
302
+ // User provides the values array with option strings, we use them directly (not wrapped in objects)
303
+ // Don't include "strict" for MULTI_PICKLIST as it may cause parsing errors
304
+ // Ensure columnId is a number (not string) for the API
305
+ return {
306
+ columnId: Number(colIdNum),
307
+ objectValue: {
308
+ objectType: 'MULTI_PICKLIST',
309
+ values: picklistValues,
310
+ },
311
+ };
312
+ }
313
+ // For CHECKBOX columns, convert string "true"/"false" to boolean
314
+ if (columnType === 'CHECKBOX') {
315
+ let boolValue;
316
+ if (typeof cellValue === 'boolean') {
317
+ boolValue = cellValue;
199
318
  }
200
- catch (error) {
201
- throw new Error(`Invalid JSON for ${columnType} column (Column ID: ${colIdNum}). ` +
202
- `Please provide a valid JSON object. Error: ${error instanceof Error ? error.message : String(error)}`);
319
+ else if (typeof cellValue === 'string') {
320
+ const lowerValue = cellValue.toLowerCase().trim();
321
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
322
+ boolValue = true;
323
+ }
324
+ else if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no' || lowerValue === '') {
325
+ boolValue = false;
326
+ }
327
+ else {
328
+ // If it's not a recognized boolean string, try to parse it
329
+ throw new Error(`Invalid value for CHECKBOX column (Column ID: ${colIdNum}). ` +
330
+ `Expected boolean or string "true"/"false", got: ${cellValue}`);
331
+ }
332
+ }
333
+ else if (cellValue === null || cellValue === undefined || cellValue === '') {
334
+ boolValue = false;
335
+ }
336
+ else {
337
+ // Try to convert to boolean
338
+ boolValue = Boolean(cellValue);
339
+ }
340
+ return {
341
+ columnId: Number(colIdNum),
342
+ value: boolValue,
343
+ };
344
+ }
345
+ // For CHECKBOX columns, convert string "true"/"false" to boolean
346
+ if (columnType === 'CHECKBOX') {
347
+ let boolValue;
348
+ if (typeof cellValue === 'boolean') {
349
+ boolValue = cellValue;
350
+ }
351
+ else if (typeof cellValue === 'string') {
352
+ const lowerValue = cellValue.toLowerCase().trim();
353
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
354
+ boolValue = true;
355
+ }
356
+ else if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no' || lowerValue === '') {
357
+ boolValue = false;
358
+ }
359
+ else {
360
+ // If it's not a recognized boolean string, try to parse it
361
+ throw new Error(`Invalid value for CHECKBOX column (Column ID: ${colIdNum}). ` +
362
+ `Expected boolean or string "true"/"false", got: ${cellValue}`);
363
+ }
364
+ }
365
+ else if (cellValue === null || cellValue === undefined || cellValue === '') {
366
+ boolValue = false;
203
367
  }
204
- // Validate that it's an object
205
- if (typeof objectValue !== 'object' || objectValue === null) {
206
- throw new Error(`Invalid objectValue for ${columnType} column (Column ID: ${colIdNum}). ` +
207
- `Expected a JSON object, got ${typeof objectValue}.`);
368
+ else {
369
+ // Try to convert to boolean
370
+ boolValue = Boolean(cellValue);
208
371
  }
209
- // Use the parsed JSON directly as objectValue
210
372
  return {
211
- columnId: colIdNum,
212
- objectValue: objectValue,
213
- strict: false,
373
+ columnId: Number(colIdNum),
374
+ value: boolValue,
214
375
  };
215
376
  }
216
377
  // For regular columns, use value (use cleaned cellValue)
378
+ // Ensure columnId is a number (not string) for the API
217
379
  return {
218
- columnId: colIdNum,
380
+ columnId: Number(colIdNum),
219
381
  value: cellValue,
220
382
  };
221
383
  });
@@ -471,7 +633,14 @@ async function executeRowOperation(operation, itemIndex) {
471
633
  catch (error) {
472
634
  // If we can't get columns, continue without column type info
473
635
  }
474
- const cells = (cellsData.cell || []).map((cell) => {
636
+ // Filter out any cells with empty columnId or invalid data
637
+ const validCells = (cellsData.cell || []).filter((cell) => {
638
+ const columnId = cell.columnInputMethod === 'id'
639
+ ? cell.columnIdManual
640
+ : cell.columnId;
641
+ return columnId && columnId !== '' && cell.value !== undefined;
642
+ });
643
+ const cells = validCells.map((cell) => {
475
644
  let columnId;
476
645
  if (cell.columnInputMethod === 'id') {
477
646
  columnId = cell.columnIdManual || '';
@@ -479,7 +648,11 @@ async function executeRowOperation(operation, itemIndex) {
479
648
  else {
480
649
  columnId = cell.columnId || '';
481
650
  }
651
+ // Parse columnId - keep as number for API, but ensure it's valid
482
652
  const colIdNum = parseInt(columnId, 10);
653
+ if (isNaN(colIdNum)) {
654
+ throw new Error(`Invalid column ID: ${columnId}`);
655
+ }
483
656
  const column = columnsMap.get(colIdNum);
484
657
  const columnType = column?.type;
485
658
  // Clean up the value - remove surrounding quotes if present
@@ -493,37 +666,188 @@ async function executeRowOperation(operation, itemIndex) {
493
666
  }
494
667
  }
495
668
  // Handle MULTI_CONTACT_LIST and MULTI_PICKLIST columns with objectValue
496
- // User must provide a JSON block that will be used directly
497
- if (columnType === 'MULTI_CONTACT_LIST' || columnType === 'MULTI_PICKLIST') {
498
- // Parse the value as JSON
499
- let objectValue;
500
- try {
501
- if (typeof cellValue === 'string') {
502
- objectValue = JSON.parse(cellValue);
669
+ // User provides a JSON object (field type is 'json' so n8n parses it automatically)
670
+ if (columnType === 'MULTI_CONTACT_LIST') {
671
+ // Parse the value - if it's a string, try to parse as JSON; if already an object, use it directly
672
+ let parsedValue;
673
+ if (typeof cellValue === 'string') {
674
+ try {
675
+ parsedValue = JSON.parse(cellValue);
676
+ }
677
+ catch (error) {
678
+ throw new Error(`Invalid JSON for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
679
+ `Please provide a valid JSON object with structure: { "values": [{ "email": "..." }, ...] }. ` +
680
+ `Error: ${error instanceof Error ? error.message : String(error)}`);
681
+ }
682
+ }
683
+ else {
684
+ // Already parsed by n8n's JSON field type
685
+ parsedValue = cellValue;
686
+ }
687
+ // Validate that it's an object with values array
688
+ if (typeof parsedValue !== 'object' || parsedValue === null) {
689
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
690
+ `Expected a JSON object with "values" array, got ${typeof parsedValue}.`);
691
+ }
692
+ // Validate that it has a values array
693
+ if (!Array.isArray(parsedValue.values)) {
694
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
695
+ `Expected a JSON object with "values" array property. Example: { "values": [{ "email": "user@example.com" }] }`);
696
+ }
697
+ // Validate that each value in the array has an email property
698
+ // Transform values to include objectType: "CONTACT" for each contact
699
+ const contactValues = parsedValue.values.map((value, i) => {
700
+ if (typeof value !== 'object' || value === null || !value.email) {
701
+ throw new Error(`Invalid objectValue for MULTI_CONTACT_LIST column (Column ID: ${colIdNum}). ` +
702
+ `Each item in the "values" array must be an object with an "email" property. ` +
703
+ `Example: { "values": [{ "email": "user@example.com" }] }`);
704
+ }
705
+ // Ensure each contact has objectType: "CONTACT"
706
+ return {
707
+ objectType: 'CONTACT',
708
+ email: value.email,
709
+ };
710
+ });
711
+ // For MULTI_CONTACT_LIST, objectValue should have structure:
712
+ // { "objectType": "MULTI_CONTACT", "values": [{ "objectType": "CONTACT", "email": "..." }, ...] }
713
+ // User provides the values array with emails, we add the objectType fields
714
+ // Don't include "strict" for MULTI_CONTACT_LIST as it causes parsing errors
715
+ // Ensure columnId is a number (not string) for the API
716
+ return {
717
+ columnId: Number(colIdNum),
718
+ objectValue: {
719
+ objectType: 'MULTI_CONTACT',
720
+ values: contactValues,
721
+ },
722
+ };
723
+ }
724
+ else if (columnType === 'MULTI_PICKLIST') {
725
+ // Parse the value - if it's a string, try to parse as JSON; if already an object, use it directly
726
+ // Expected format: { "values": ["Option 1", "Option 2", ...] }
727
+ let parsedValue;
728
+ if (typeof cellValue === 'string') {
729
+ try {
730
+ parsedValue = JSON.parse(cellValue);
731
+ }
732
+ catch (error) {
733
+ throw new Error(`Invalid JSON for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
734
+ `Please provide a valid JSON object with structure: { "values": ["Option 1", "Option 2"] }. ` +
735
+ `Error: ${error instanceof Error ? error.message : String(error)}`);
736
+ }
737
+ }
738
+ else {
739
+ // Already parsed by n8n's JSON field type
740
+ parsedValue = cellValue;
741
+ }
742
+ // Validate that it's an object with values array
743
+ if (typeof parsedValue !== 'object' || parsedValue === null) {
744
+ throw new Error(`Invalid objectValue for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
745
+ `Expected a JSON object with "values" array, got ${typeof parsedValue}.`);
746
+ }
747
+ // Validate that it has a values array
748
+ if (!Array.isArray(parsedValue.values)) {
749
+ throw new Error(`Invalid objectValue for MULTI_PICKLIST column (Column ID: ${colIdNum}). ` +
750
+ `Expected a JSON object with "values" array property. Example: { "values": ["Option 1", "Option 2"] }`);
751
+ }
752
+ // For MULTI_PICKLIST, values should be an array of strings, not objects
753
+ // Transform values to ensure they're all strings
754
+ // Note: Picklist values must match exactly (case-sensitive) the options defined in the column
755
+ const picklistValues = parsedValue.values.map((value) => {
756
+ // Value should be a string (the option text)
757
+ if (typeof value === 'string') {
758
+ return value;
759
+ }
760
+ else if (typeof value === 'object' && value !== null && value.value) {
761
+ // If it's an object with a value property, extract the string value
762
+ return String(value.value);
503
763
  }
504
764
  else {
505
- objectValue = cellValue;
765
+ throw new Error(`Invalid value in MULTI_PICKLIST values array (Column ID: ${colIdNum}). ` +
766
+ `Each value must be a string (option text). ` +
767
+ `Note: Option values must match exactly (case-sensitive) the options defined in the MULTI_PICKLIST column.`);
506
768
  }
769
+ });
770
+ // For MULTI_PICKLIST, objectValue should have structure:
771
+ // { "objectType": "MULTI_PICKLIST", "values": ["Option 1", "Option 2", ...] }
772
+ // User provides the values array with option strings, we use them directly (not wrapped in objects)
773
+ // Don't include "strict" for MULTI_PICKLIST as it may cause parsing errors
774
+ // Ensure columnId is a number (not string) for the API
775
+ return {
776
+ columnId: Number(colIdNum),
777
+ objectValue: {
778
+ objectType: 'MULTI_PICKLIST',
779
+ values: picklistValues,
780
+ },
781
+ };
782
+ }
783
+ // For CHECKBOX columns, convert string "true"/"false" to boolean
784
+ if (columnType === 'CHECKBOX') {
785
+ let boolValue;
786
+ if (typeof cellValue === 'boolean') {
787
+ boolValue = cellValue;
507
788
  }
508
- catch (error) {
509
- throw new Error(`Invalid JSON for ${columnType} column (Column ID: ${colIdNum}). ` +
510
- `Please provide a valid JSON object. Error: ${error instanceof Error ? error.message : String(error)}`);
789
+ else if (typeof cellValue === 'string') {
790
+ const lowerValue = cellValue.toLowerCase().trim();
791
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
792
+ boolValue = true;
793
+ }
794
+ else if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no' || lowerValue === '') {
795
+ boolValue = false;
796
+ }
797
+ else {
798
+ // If it's not a recognized boolean string, try to parse it
799
+ throw new Error(`Invalid value for CHECKBOX column (Column ID: ${colIdNum}). ` +
800
+ `Expected boolean or string "true"/"false", got: ${cellValue}`);
801
+ }
802
+ }
803
+ else if (cellValue === null || cellValue === undefined || cellValue === '') {
804
+ boolValue = false;
805
+ }
806
+ else {
807
+ // Try to convert to boolean
808
+ boolValue = Boolean(cellValue);
809
+ }
810
+ return {
811
+ columnId: Number(colIdNum),
812
+ value: boolValue,
813
+ };
814
+ }
815
+ // For CHECKBOX columns, convert string "true"/"false" to boolean
816
+ if (columnType === 'CHECKBOX') {
817
+ let boolValue;
818
+ if (typeof cellValue === 'boolean') {
819
+ boolValue = cellValue;
820
+ }
821
+ else if (typeof cellValue === 'string') {
822
+ const lowerValue = cellValue.toLowerCase().trim();
823
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
824
+ boolValue = true;
825
+ }
826
+ else if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no' || lowerValue === '') {
827
+ boolValue = false;
828
+ }
829
+ else {
830
+ // If it's not a recognized boolean string, try to parse it
831
+ throw new Error(`Invalid value for CHECKBOX column (Column ID: ${colIdNum}). ` +
832
+ `Expected boolean or string "true"/"false", got: ${cellValue}`);
833
+ }
834
+ }
835
+ else if (cellValue === null || cellValue === undefined || cellValue === '') {
836
+ boolValue = false;
511
837
  }
512
- // Validate that it's an object
513
- if (typeof objectValue !== 'object' || objectValue === null) {
514
- throw new Error(`Invalid objectValue for ${columnType} column (Column ID: ${colIdNum}). ` +
515
- `Expected a JSON object, got ${typeof objectValue}.`);
838
+ else {
839
+ // Try to convert to boolean
840
+ boolValue = Boolean(cellValue);
516
841
  }
517
- // Use the parsed JSON directly as objectValue
518
842
  return {
519
- columnId: colIdNum,
520
- objectValue: objectValue,
521
- strict: false,
843
+ columnId: Number(colIdNum),
844
+ value: boolValue,
522
845
  };
523
846
  }
524
847
  // For regular columns, use value (use cleaned cellValue)
848
+ // Ensure columnId is a number (not string) for the API
525
849
  return {
526
- columnId: colIdNum,
850
+ columnId: Number(colIdNum),
527
851
  value: cellValue,
528
852
  };
529
853
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-steyi-ss",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "n8n node for Smartsheet API integration",
5
5
  "keywords": [
6
6
  "n8n-node-package",