@guayaba/workflow-piece-google-sheets 0.14.6
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/.babelrc +3 -0
- package/.eslintrc.json +18 -0
- package/README.md +5 -0
- package/assets/logo.png +0 -0
- package/package.json +28 -0
- package/src/i18n/ar.json +124 -0
- package/src/i18n/bg.json +124 -0
- package/src/i18n/ca.json +132 -0
- package/src/i18n/de.json +165 -0
- package/src/i18n/es.json +165 -0
- package/src/i18n/fr.json +165 -0
- package/src/i18n/hi.json +132 -0
- package/src/i18n/hu.json +124 -0
- package/src/i18n/hy.json +124 -0
- package/src/i18n/id.json +132 -0
- package/src/i18n/it.json +124 -0
- package/src/i18n/ja.json +165 -0
- package/src/i18n/ko.json +124 -0
- package/src/i18n/nl.json +165 -0
- package/src/i18n/pl.json +124 -0
- package/src/i18n/pt.json +165 -0
- package/src/i18n/ru.json +132 -0
- package/src/i18n/sv.json +124 -0
- package/src/i18n/translation.json +165 -0
- package/src/i18n/uk.json +124 -0
- package/src/i18n/vi.json +132 -0
- package/src/i18n/zh.json +165 -0
- package/src/index.ts +93 -0
- package/src/lib/actions/clear-sheet.ts +60 -0
- package/src/lib/actions/copy-worksheet.ts +32 -0
- package/src/lib/actions/create-column.ts +109 -0
- package/src/lib/actions/create-spreadsheet.ts +122 -0
- package/src/lib/actions/create-worksheet.ts +62 -0
- package/src/lib/actions/delete-row.action.ts +40 -0
- package/src/lib/actions/delete-worksheet.ts +36 -0
- package/src/lib/actions/export-sheet.ts +86 -0
- package/src/lib/actions/find-row-by-num.ts +42 -0
- package/src/lib/actions/find-rows.ts +135 -0
- package/src/lib/actions/find-spreadsheets.ts +83 -0
- package/src/lib/actions/find-worksheet.ts +52 -0
- package/src/lib/actions/format-spreadsheet-row.ts +112 -0
- package/src/lib/actions/get-many-rows.ts +42 -0
- package/src/lib/actions/get-rows.ts +207 -0
- package/src/lib/actions/insert-multiple-rows.action.ts +542 -0
- package/src/lib/actions/insert-row.action.ts +111 -0
- package/src/lib/actions/rename-worksheet.ts +44 -0
- package/src/lib/actions/update-multiple-rows.ts +177 -0
- package/src/lib/actions/update-row.ts +93 -0
- package/src/lib/common/common.ts +383 -0
- package/src/lib/common/props.ts +274 -0
- package/src/lib/triggers/helpers.ts +155 -0
- package/src/lib/triggers/new-or-updated-row.trigger.ts +299 -0
- package/src/lib/triggers/new-row-added-webhook.ts +182 -0
- package/src/lib/triggers/new-spreadsheet.ts +88 -0
- package/src/lib/triggers/new-worksheet.ts +96 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +15 -0
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { googleSheetsAuth } from '../common/common';
|
|
2
|
+
import {
|
|
3
|
+
createAction,
|
|
4
|
+
DropdownOption,
|
|
5
|
+
DynamicPropsValue,
|
|
6
|
+
OAuth2PropertyValue,
|
|
7
|
+
Property,
|
|
8
|
+
} from '@guayaba/workflows-framework';
|
|
9
|
+
import {
|
|
10
|
+
Dimension,
|
|
11
|
+
googleSheetsCommon,
|
|
12
|
+
objectToArray,
|
|
13
|
+
ValueInputOption,
|
|
14
|
+
columnToLabel,
|
|
15
|
+
areSheetIdsValid,
|
|
16
|
+
GoogleSheetsAuthValue,
|
|
17
|
+
createGoogleClient,
|
|
18
|
+
} from '../common/common';
|
|
19
|
+
import { getWorkSheetName, getWorkSheetGridSize } from '../triggers/helpers';
|
|
20
|
+
import { google, sheets_v4 } from 'googleapis';
|
|
21
|
+
import { MarkdownVariant } from '@guayaba/workflows-shared';
|
|
22
|
+
import { parse } from 'csv-parse/sync';
|
|
23
|
+
import { commonProps } from '../common/props';
|
|
24
|
+
|
|
25
|
+
type RowValueType = Record<string, any>;
|
|
26
|
+
|
|
27
|
+
export const insertMultipleRowsAction = createAction({
|
|
28
|
+
auth: googleSheetsAuth,
|
|
29
|
+
name: 'google-sheets-insert-multiple-rows',
|
|
30
|
+
displayName: 'Add Multiple Rows',
|
|
31
|
+
description: 'Add multiple rows of data at once to a specific spreadsheet.',
|
|
32
|
+
props: {
|
|
33
|
+
...commonProps,
|
|
34
|
+
input_type: Property.StaticDropdown({
|
|
35
|
+
displayName: 'Rows Data Format',
|
|
36
|
+
description: 'Select the format of the input values to be added into the worksheet.',
|
|
37
|
+
required: true,
|
|
38
|
+
defaultValue: 'column_names',
|
|
39
|
+
options: {
|
|
40
|
+
disabled: false,
|
|
41
|
+
options: [
|
|
42
|
+
{
|
|
43
|
+
value: 'csv',
|
|
44
|
+
label: 'CSV',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
value: 'json',
|
|
48
|
+
label: 'JSON',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: 'column_names',
|
|
52
|
+
label: 'Column Names',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
values: Property.DynamicProperties({
|
|
58
|
+
auth: googleSheetsAuth,
|
|
59
|
+
displayName: 'Values',
|
|
60
|
+
description: 'The values to add.',
|
|
61
|
+
required: true,
|
|
62
|
+
refreshers: ['sheetId', 'spreadsheetId', 'input_type', 'headerRow'],
|
|
63
|
+
props: async ({ auth, sheetId, spreadsheetId, input_type, headerRow }) => {
|
|
64
|
+
const sheet_id = Number(sheetId);
|
|
65
|
+
const spreadsheet_id = spreadsheetId as unknown as string;
|
|
66
|
+
const valuesInputType = input_type as unknown as string;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
!auth ||
|
|
70
|
+
(spreadsheet_id ?? '').toString().length === 0 ||
|
|
71
|
+
(sheet_id ?? '').toString().length === 0 ||
|
|
72
|
+
!['csv', 'json', 'column_names'].includes(valuesInputType)
|
|
73
|
+
) {
|
|
74
|
+
return {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const fields: DynamicPropsValue = {};
|
|
78
|
+
|
|
79
|
+
switch (valuesInputType) {
|
|
80
|
+
case 'csv':
|
|
81
|
+
fields['markdown'] = Property.MarkDown({
|
|
82
|
+
value: `Ensure the first row contains column headers that match the sheet's column names.`,
|
|
83
|
+
variant: MarkdownVariant.INFO,
|
|
84
|
+
});
|
|
85
|
+
fields['values'] = Property.LongText({
|
|
86
|
+
displayName: 'CSV',
|
|
87
|
+
required: true,
|
|
88
|
+
});
|
|
89
|
+
break;
|
|
90
|
+
case 'json':
|
|
91
|
+
fields['markdown'] = Property.MarkDown({
|
|
92
|
+
value: `Provide values in JSON format. Ensure the column names match the sheet's header.`,
|
|
93
|
+
variant: MarkdownVariant.INFO,
|
|
94
|
+
});
|
|
95
|
+
fields['values'] = Property.Json({
|
|
96
|
+
displayName: 'JSON',
|
|
97
|
+
required: true,
|
|
98
|
+
defaultValue: [
|
|
99
|
+
{
|
|
100
|
+
column1: 'value1',
|
|
101
|
+
column2: 'value2',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
column1: 'value3',
|
|
105
|
+
column2: 'value4',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
case 'column_names': {
|
|
111
|
+
const headers = await googleSheetsCommon.getGoogleSheetRows({
|
|
112
|
+
spreadsheetId: spreadsheet_id,
|
|
113
|
+
auth: auth,
|
|
114
|
+
sheetId: sheet_id,
|
|
115
|
+
rowIndex_s: 1,
|
|
116
|
+
rowIndex_e: 1,
|
|
117
|
+
headerRow: (headerRow as unknown as number) || 1,
|
|
118
|
+
});
|
|
119
|
+
const firstRow = headers[0].values ?? {};
|
|
120
|
+
|
|
121
|
+
//check for empty headers
|
|
122
|
+
if (Object.keys(firstRow).length === 0) {
|
|
123
|
+
fields['markdown'] = Property.MarkDown({
|
|
124
|
+
value: `We couldn't find any headers in the selected spreadsheet or worksheet. Please add headers to the sheet and refresh the page to reflect the columns.`,
|
|
125
|
+
variant: MarkdownVariant.INFO,
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
const columns: {
|
|
129
|
+
[key: string]: any;
|
|
130
|
+
} = {};
|
|
131
|
+
|
|
132
|
+
for (const key in firstRow) {
|
|
133
|
+
columns[key] = Property.ShortText({
|
|
134
|
+
displayName: firstRow[key],
|
|
135
|
+
// description: firstRow[key],
|
|
136
|
+
required: false,
|
|
137
|
+
defaultValue: '',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
fields['values'] = Property.Array({
|
|
141
|
+
displayName: 'Values',
|
|
142
|
+
required: false,
|
|
143
|
+
properties: columns,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return fields;
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
overwrite: Property.Checkbox({
|
|
152
|
+
displayName: 'Overwrite Existing Data?',
|
|
153
|
+
description:
|
|
154
|
+
'Enable this option to replace all existing data in the sheet with new data from your input. This will clear any extra rows beyond the updated range.',
|
|
155
|
+
required: false,
|
|
156
|
+
defaultValue: false,
|
|
157
|
+
}),
|
|
158
|
+
check_for_duplicate: Property.Checkbox({
|
|
159
|
+
displayName: 'Avoid Duplicates?',
|
|
160
|
+
description:
|
|
161
|
+
'Enable this option to check for duplicate values before inserting data into the sheet. Only unique rows will be added based on the selected column.',
|
|
162
|
+
required: false,
|
|
163
|
+
defaultValue: false,
|
|
164
|
+
}),
|
|
165
|
+
check_for_duplicate_column: Property.DynamicProperties({
|
|
166
|
+
auth: googleSheetsAuth,
|
|
167
|
+
displayName: 'Duplicate Value Column',
|
|
168
|
+
description: 'The column to check for duplicate values.',
|
|
169
|
+
refreshers: ['spreadsheetId', 'sheetId', 'check_for_duplicate', 'headerRow'],
|
|
170
|
+
required: false,
|
|
171
|
+
props: async ({ auth, spreadsheetId, sheetId, check_for_duplicate, headerRow }) => {
|
|
172
|
+
const sheet_id = Number(sheetId);
|
|
173
|
+
const spreadsheet_id = spreadsheetId as unknown as string;
|
|
174
|
+
const checkForExisting = check_for_duplicate as unknown as boolean;
|
|
175
|
+
if (
|
|
176
|
+
!auth ||
|
|
177
|
+
(spreadsheet_id ?? '').toString().length === 0 ||
|
|
178
|
+
(sheet_id ?? '').toString().length === 0
|
|
179
|
+
) {
|
|
180
|
+
return {};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const fields: DynamicPropsValue = {};
|
|
184
|
+
|
|
185
|
+
if (checkForExisting) {
|
|
186
|
+
const headers = await googleSheetsCommon.getGoogleSheetRows({
|
|
187
|
+
spreadsheetId: spreadsheet_id,
|
|
188
|
+
auth: auth as GoogleSheetsAuthValue,
|
|
189
|
+
sheetId: sheet_id,
|
|
190
|
+
rowIndex_s: 1,
|
|
191
|
+
rowIndex_e: 1,
|
|
192
|
+
headerRow: (headerRow as unknown as number) || 1,
|
|
193
|
+
});
|
|
194
|
+
const firstRow = headers[0].values ?? {};
|
|
195
|
+
|
|
196
|
+
//check for empty headers
|
|
197
|
+
if (Object.keys(firstRow).length === 0) {
|
|
198
|
+
fields['markdown'] = Property.MarkDown({
|
|
199
|
+
value: `No headers were found in the selected spreadsheet or worksheet. Please ensure that headers are added to the sheet and refresh the page to display the available columns.`,
|
|
200
|
+
variant: MarkdownVariant.INFO,
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
const headers: DropdownOption<string>[] = [];
|
|
204
|
+
for (const key in firstRow) {
|
|
205
|
+
headers.push({
|
|
206
|
+
label: firstRow[key].toString(),
|
|
207
|
+
value: key.toString(),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
fields['column_name'] = Property.StaticDropdown({
|
|
212
|
+
displayName: 'Column to Check for Duplicates',
|
|
213
|
+
description:
|
|
214
|
+
'Select the column to use for detecting duplicate values. Only rows with unique values in this column will be added to the sheet.',
|
|
215
|
+
required: true,
|
|
216
|
+
options: {
|
|
217
|
+
disabled: false,
|
|
218
|
+
options: headers,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return fields;
|
|
225
|
+
},
|
|
226
|
+
}),
|
|
227
|
+
as_string: Property.Checkbox({
|
|
228
|
+
displayName: 'As String',
|
|
229
|
+
description:
|
|
230
|
+
'Inserted values that are dates and formulas will be entered as strings and have no effect',
|
|
231
|
+
required: false,
|
|
232
|
+
}),
|
|
233
|
+
headerRow: Property.Number({
|
|
234
|
+
displayName: 'Header Row Number',
|
|
235
|
+
description: 'Enter the row number where your column headers are located (usually row 1).',
|
|
236
|
+
required: true,
|
|
237
|
+
defaultValue: 1,
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async run(context) {
|
|
242
|
+
const {
|
|
243
|
+
spreadsheetId: inputSpreadsheetId,
|
|
244
|
+
sheetId: inputSheetId,
|
|
245
|
+
input_type: valuesInputType,
|
|
246
|
+
overwrite: overwriteValues,
|
|
247
|
+
check_for_duplicate: checkForDuplicateValues,
|
|
248
|
+
values: { values: rowValuesInput },
|
|
249
|
+
as_string: asString,
|
|
250
|
+
headerRow,
|
|
251
|
+
} = context.propsValue;
|
|
252
|
+
|
|
253
|
+
if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) {
|
|
254
|
+
throw new Error('Please select a spreadsheet and sheet first.');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const sheetId = Number(inputSheetId);
|
|
258
|
+
const spreadsheetId = inputSpreadsheetId as string;
|
|
259
|
+
|
|
260
|
+
const duplicateColumn = context.propsValue.check_for_duplicate_column?.['column_name'];
|
|
261
|
+
const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId);
|
|
262
|
+
|
|
263
|
+
const rowHeaders = await googleSheetsCommon.getGoogleSheetRows({
|
|
264
|
+
spreadsheetId: spreadsheetId,
|
|
265
|
+
auth: context.auth,
|
|
266
|
+
sheetId: sheetId,
|
|
267
|
+
rowIndex_s: 1,
|
|
268
|
+
rowIndex_e: 1,
|
|
269
|
+
headerRow: headerRow,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const sheetHeaders = rowHeaders[0]?.values ?? {};
|
|
273
|
+
|
|
274
|
+
const authClient = await createGoogleClient(context.auth);
|
|
275
|
+
const sheets = google.sheets({ version: 'v4', auth: authClient });
|
|
276
|
+
|
|
277
|
+
const formattedValues = await formatInputRows(
|
|
278
|
+
sheets,
|
|
279
|
+
spreadsheetId,
|
|
280
|
+
sheetName,
|
|
281
|
+
valuesInputType,
|
|
282
|
+
rowValuesInput,
|
|
283
|
+
sheetHeaders,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED;
|
|
287
|
+
|
|
288
|
+
if (overwriteValues) {
|
|
289
|
+
const sheetGridRange = await getWorkSheetGridSize(context.auth, spreadsheetId, sheetId);
|
|
290
|
+
const existingGridRowCount = sheetGridRange.rowCount ?? 0;
|
|
291
|
+
return handleOverwrite(
|
|
292
|
+
sheets,
|
|
293
|
+
spreadsheetId,
|
|
294
|
+
sheetName,
|
|
295
|
+
formattedValues,
|
|
296
|
+
existingGridRowCount,
|
|
297
|
+
valueInputOption,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (checkForDuplicateValues) {
|
|
302
|
+
const existingSheetValues = await googleSheetsCommon.getGoogleSheetRows({
|
|
303
|
+
spreadsheetId: spreadsheetId,
|
|
304
|
+
auth: context.auth,
|
|
305
|
+
sheetId: sheetId,
|
|
306
|
+
rowIndex_s: 1,
|
|
307
|
+
rowIndex_e: undefined,
|
|
308
|
+
headerRow: headerRow,
|
|
309
|
+
});
|
|
310
|
+
return handleDuplicates(
|
|
311
|
+
sheets,
|
|
312
|
+
spreadsheetId,
|
|
313
|
+
sheetName,
|
|
314
|
+
formattedValues,
|
|
315
|
+
existingSheetValues,
|
|
316
|
+
duplicateColumn,
|
|
317
|
+
valueInputOption,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return normalInsert(sheets, spreadsheetId, sheetName, formattedValues, valueInputOption);
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
async function handleOverwrite(
|
|
326
|
+
sheets: sheets_v4.Sheets,
|
|
327
|
+
spreadSheetId: string,
|
|
328
|
+
sheetName: string,
|
|
329
|
+
formattedValues: any[],
|
|
330
|
+
existingGridRowCount: number,
|
|
331
|
+
valueInputOption: ValueInputOption,
|
|
332
|
+
) {
|
|
333
|
+
const existingRowCount = existingGridRowCount;
|
|
334
|
+
const inputRowCount = formattedValues.length;
|
|
335
|
+
|
|
336
|
+
const updateResponse = await sheets.spreadsheets.values.batchUpdate({
|
|
337
|
+
spreadsheetId: spreadSheetId,
|
|
338
|
+
requestBody: {
|
|
339
|
+
data: [
|
|
340
|
+
{
|
|
341
|
+
range: `${sheetName}!A2:ZZZ${inputRowCount + 1}`,
|
|
342
|
+
majorDimension: Dimension.ROWS,
|
|
343
|
+
values: formattedValues.map((row) => objectToArray(row)),
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
valueInputOption,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Determine if clearing rows is necessary and within grid size
|
|
351
|
+
const clearStartRow = inputRowCount + 2; // Start clearing after the last input row
|
|
352
|
+
const clearEndRow = Math.max(clearStartRow, existingRowCount);
|
|
353
|
+
|
|
354
|
+
if (clearStartRow <= existingGridRowCount) {
|
|
355
|
+
const boundedClearEndRow = Math.min(clearEndRow, existingGridRowCount);
|
|
356
|
+
const clearRowsResponse = await sheets.spreadsheets.values.batchClear({
|
|
357
|
+
spreadsheetId: spreadSheetId,
|
|
358
|
+
requestBody: {
|
|
359
|
+
ranges: [`${sheetName}!A${clearStartRow}:ZZZ${boundedClearEndRow}`],
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
...updateResponse.data,
|
|
365
|
+
...clearRowsResponse.data,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return updateResponse.data;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function handleDuplicates(
|
|
372
|
+
sheets: sheets_v4.Sheets,
|
|
373
|
+
spreadSheetId: string,
|
|
374
|
+
sheetName: string,
|
|
375
|
+
formattedInputRows: any[],
|
|
376
|
+
existingSheetValues: any[],
|
|
377
|
+
duplicateColumn: string,
|
|
378
|
+
valueInputOption: ValueInputOption,
|
|
379
|
+
) {
|
|
380
|
+
const uniqueValues = formattedInputRows.filter(
|
|
381
|
+
(inputRow) =>
|
|
382
|
+
!existingSheetValues.some((existingRow) => {
|
|
383
|
+
const existingValue = existingRow?.values?.[duplicateColumn];
|
|
384
|
+
const inputValue = inputRow?.[duplicateColumn];
|
|
385
|
+
return (
|
|
386
|
+
existingValue != null &&
|
|
387
|
+
inputValue != null &&
|
|
388
|
+
String(existingValue).toLowerCase().trim() === String(inputValue).toLowerCase().trim()
|
|
389
|
+
);
|
|
390
|
+
}),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const response = await sheets.spreadsheets.values.append({
|
|
394
|
+
range: sheetName + '!A:A',
|
|
395
|
+
spreadsheetId: spreadSheetId,
|
|
396
|
+
valueInputOption,
|
|
397
|
+
requestBody: {
|
|
398
|
+
values: uniqueValues.map((row) => objectToArray(row)),
|
|
399
|
+
majorDimension: Dimension.ROWS,
|
|
400
|
+
},
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
return response.data;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
async function normalInsert(
|
|
407
|
+
sheets: sheets_v4.Sheets,
|
|
408
|
+
spreadSheetId: string,
|
|
409
|
+
sheetName: string,
|
|
410
|
+
formattedValues: any[],
|
|
411
|
+
valueInputOption: ValueInputOption,
|
|
412
|
+
) {
|
|
413
|
+
const response = await sheets.spreadsheets.values.append({
|
|
414
|
+
range: sheetName + '!A:A',
|
|
415
|
+
spreadsheetId: spreadSheetId,
|
|
416
|
+
valueInputOption,
|
|
417
|
+
requestBody: {
|
|
418
|
+
values: formattedValues.map((row) => objectToArray(row)),
|
|
419
|
+
majorDimension: Dimension.ROWS,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
return response.data;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function formatInputRows(
|
|
426
|
+
sheets: sheets_v4.Sheets,
|
|
427
|
+
spreadSheetId: string,
|
|
428
|
+
sheetName: string,
|
|
429
|
+
valuesInputType: string,
|
|
430
|
+
rowValuesInput: any,
|
|
431
|
+
sheetHeaders: RowValueType,
|
|
432
|
+
): Promise<RowValueType[]> {
|
|
433
|
+
let formattedInputRows: any[] = [];
|
|
434
|
+
|
|
435
|
+
switch (valuesInputType) {
|
|
436
|
+
case 'csv':
|
|
437
|
+
formattedInputRows = convertCsvToRawValues(rowValuesInput as string, ',', sheetHeaders);
|
|
438
|
+
break;
|
|
439
|
+
case 'json':
|
|
440
|
+
formattedInputRows = await convertJsonToRawValues(
|
|
441
|
+
sheets,
|
|
442
|
+
spreadSheetId,
|
|
443
|
+
sheetName,
|
|
444
|
+
rowValuesInput as string,
|
|
445
|
+
sheetHeaders,
|
|
446
|
+
);
|
|
447
|
+
break;
|
|
448
|
+
case 'column_names':
|
|
449
|
+
formattedInputRows = rowValuesInput as RowValueType[];
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return formattedInputRows;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function convertJsonToRawValues(
|
|
457
|
+
sheets: sheets_v4.Sheets,
|
|
458
|
+
spreadSheetId: string,
|
|
459
|
+
sheetName: string,
|
|
460
|
+
json: string | Record<string, any>[],
|
|
461
|
+
labelHeaders: RowValueType,
|
|
462
|
+
): Promise<RowValueType[]> {
|
|
463
|
+
let data: RowValueType[];
|
|
464
|
+
|
|
465
|
+
// If the input is a JSON string
|
|
466
|
+
if (typeof json === 'string') {
|
|
467
|
+
try {
|
|
468
|
+
data = JSON.parse(json);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
throw new Error('Invalid JSON format for row values');
|
|
471
|
+
}
|
|
472
|
+
} else {
|
|
473
|
+
// If the input is already an array of objects, use it directly
|
|
474
|
+
data = json;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Ensure the input is an array of objects
|
|
478
|
+
if (!Array.isArray(data) || typeof data[0] !== 'object') {
|
|
479
|
+
throw new Error('Input must be an array of objects or a valid JSON string representing it.');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Collect all possible headers from the data
|
|
483
|
+
const allHeaders = new Set<string>();
|
|
484
|
+
data.forEach((row) => {
|
|
485
|
+
Object.keys(row).forEach((key) => allHeaders.add(key));
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Identify headers not present in labelHeaders
|
|
489
|
+
const additionalHeaders = Array.from(allHeaders).filter(
|
|
490
|
+
(header) => !Object.values(labelHeaders).includes(header),
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
//add missing headers to labelHeaders
|
|
494
|
+
additionalHeaders.forEach((header) => {
|
|
495
|
+
labelHeaders[columnToLabel(Object.keys(labelHeaders).length)] = header;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// update sheets with new headers
|
|
499
|
+
if (additionalHeaders.length > 0) {
|
|
500
|
+
await sheets.spreadsheets.values.update({
|
|
501
|
+
range: `${sheetName}!A1:ZZZ1`,
|
|
502
|
+
spreadsheetId: spreadSheetId,
|
|
503
|
+
valueInputOption: ValueInputOption.USER_ENTERED,
|
|
504
|
+
requestBody: {
|
|
505
|
+
majorDimension: Dimension.ROWS,
|
|
506
|
+
values: [objectToArray(labelHeaders)],
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return data.map((row: RowValueType) => {
|
|
512
|
+
return Object.entries(labelHeaders).reduce((acc, [labelColumn, csvHeader]) => {
|
|
513
|
+
acc[labelColumn] = row[csvHeader] ?? '';
|
|
514
|
+
return acc;
|
|
515
|
+
}, {} as RowValueType);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function convertCsvToRawValues(csvText: string, delimiter: string, labelHeaders: RowValueType) {
|
|
520
|
+
// Split CSV into rows
|
|
521
|
+
const rows: Record<string, any>[] = parse(csvText, {
|
|
522
|
+
delimiter: delimiter,
|
|
523
|
+
columns: true,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const result = rows.map((row) => {
|
|
527
|
+
// Normalize record keys to lowercase
|
|
528
|
+
const normalizedRow = Object.keys(row).reduce((acc, key) => {
|
|
529
|
+
acc[key.toLowerCase().trim()] = row[key];
|
|
530
|
+
return acc;
|
|
531
|
+
}, {} as Record<string, any>);
|
|
532
|
+
|
|
533
|
+
const transformedRow: Record<string, any> = {};
|
|
534
|
+
for (const key in labelHeaders) {
|
|
535
|
+
// Match labels to normalized keys
|
|
536
|
+
const normalizedKey = labelHeaders[key].toLowerCase();
|
|
537
|
+
transformedRow[key] = normalizedRow[normalizedKey] || '';
|
|
538
|
+
}
|
|
539
|
+
return transformedRow;
|
|
540
|
+
});
|
|
541
|
+
return result;
|
|
542
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createAction, Property } from '@guayaba/workflows-framework';
|
|
2
|
+
import {
|
|
3
|
+
areSheetIdsValid,
|
|
4
|
+
Dimension,
|
|
5
|
+
getAccessToken,
|
|
6
|
+
GoogleSheetsAuthValue,
|
|
7
|
+
googleSheetsCommon,
|
|
8
|
+
objectToArray,
|
|
9
|
+
stringifyArray,
|
|
10
|
+
ValueInputOption,
|
|
11
|
+
} from '../common/common';
|
|
12
|
+
import { googleSheetsAuth } from '../common/common';
|
|
13
|
+
import { isNil } from '@guayaba/workflows-shared';
|
|
14
|
+
import {
|
|
15
|
+
AuthenticationType,
|
|
16
|
+
httpClient,
|
|
17
|
+
HttpMethod,
|
|
18
|
+
HttpRequest,
|
|
19
|
+
} from '@guayaba/workflows-common';
|
|
20
|
+
import { commonProps, isFirstRowHeaderProp, rowValuesProp } from '../common/props';
|
|
21
|
+
|
|
22
|
+
export const insertRowAction = createAction({
|
|
23
|
+
auth: googleSheetsAuth,
|
|
24
|
+
name: 'insert_row',
|
|
25
|
+
description: 'Add a new row of data to a specific spreadsheet.',
|
|
26
|
+
displayName: 'Add Row',
|
|
27
|
+
props: {
|
|
28
|
+
...commonProps,
|
|
29
|
+
first_row_headers: isFirstRowHeaderProp(),
|
|
30
|
+
as_string: Property.Checkbox({
|
|
31
|
+
displayName: 'As String',
|
|
32
|
+
description:
|
|
33
|
+
'Inserted values that are dates and formulas will be entered strings and have no effect',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
values: rowValuesProp(),
|
|
37
|
+
},
|
|
38
|
+
async run({ propsValue, auth }) {
|
|
39
|
+
const {
|
|
40
|
+
values,
|
|
41
|
+
spreadsheetId: inputSpreadsheetId,
|
|
42
|
+
sheetId: inputSheetId,
|
|
43
|
+
as_string,
|
|
44
|
+
first_row_headers,
|
|
45
|
+
} = propsValue;
|
|
46
|
+
|
|
47
|
+
if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) {
|
|
48
|
+
throw new Error('Please select a spreadsheet and sheet first.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sheetId = Number(inputSheetId);
|
|
52
|
+
const spreadsheetId = inputSpreadsheetId as string;
|
|
53
|
+
|
|
54
|
+
const sheetName = await googleSheetsCommon.findSheetName(auth, spreadsheetId, sheetId);
|
|
55
|
+
|
|
56
|
+
const formattedValues = first_row_headers
|
|
57
|
+
? objectToArray(values).map((val) => (isNil(val) ? '' : val))
|
|
58
|
+
: values.values;
|
|
59
|
+
|
|
60
|
+
const res = await appendGoogleSheetValues({
|
|
61
|
+
auth,
|
|
62
|
+
majorDimension: Dimension.COLUMNS,
|
|
63
|
+
range: sheetName,
|
|
64
|
+
spreadSheetId: spreadsheetId,
|
|
65
|
+
valueInputOption: as_string ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED,
|
|
66
|
+
values: stringifyArray(formattedValues),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const updatedRowNumber = extractRowNumber(res.body.updates.updatedRange);
|
|
70
|
+
return { ...res.body, row: updatedRowNumber };
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
function extractRowNumber(updatedRange: string): number {
|
|
75
|
+
const rowRange = updatedRange.split('!')[1];
|
|
76
|
+
return parseInt(rowRange.split(':')[0].substring(1), 10);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function appendGoogleSheetValues(params: AppendGoogleSheetValuesParams) {
|
|
80
|
+
const { auth, majorDimension, range, spreadSheetId, valueInputOption, values } = params;
|
|
81
|
+
const accessToken = await getAccessToken(auth);
|
|
82
|
+
const request: HttpRequest = {
|
|
83
|
+
method: HttpMethod.POST,
|
|
84
|
+
url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadSheetId}/values/${encodeURIComponent(
|
|
85
|
+
`${range}!A:A`,
|
|
86
|
+
)}:append`,
|
|
87
|
+
body: {
|
|
88
|
+
majorDimension,
|
|
89
|
+
range: `${range}!A:A`,
|
|
90
|
+
values: values.map((val) => ({ values: val })),
|
|
91
|
+
},
|
|
92
|
+
authentication: {
|
|
93
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
94
|
+
token: accessToken,
|
|
95
|
+
},
|
|
96
|
+
queryParams: {
|
|
97
|
+
valueInputOption,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return httpClient.sendRequest(request);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type AppendGoogleSheetValuesParams = {
|
|
105
|
+
values: string[];
|
|
106
|
+
spreadSheetId: string;
|
|
107
|
+
range: string;
|
|
108
|
+
valueInputOption: ValueInputOption;
|
|
109
|
+
majorDimension: Dimension;
|
|
110
|
+
auth: GoogleSheetsAuthValue;
|
|
111
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { googleSheetsAuth } from '../common/common';
|
|
2
|
+
import { createAction, Property } from '@guayaba/workflows-framework';
|
|
3
|
+
import { includeTeamDrivesProp, sheetIdProp, spreadsheetIdProp } from '../common/props';
|
|
4
|
+
import { google } from 'googleapis';
|
|
5
|
+
import { createGoogleClient } from '../common/common';
|
|
6
|
+
|
|
7
|
+
export const renameWorksheetAction = createAction({
|
|
8
|
+
auth: googleSheetsAuth,
|
|
9
|
+
name: 'rename-worksheet',
|
|
10
|
+
displayName: 'Rename Worksheet',
|
|
11
|
+
description: 'Rename specific worksheet.',
|
|
12
|
+
props: {
|
|
13
|
+
includeTeamDrives: includeTeamDrivesProp(),
|
|
14
|
+
spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'),
|
|
15
|
+
sheetId: sheetIdProp('Worksheet', 'The ID of the worksheet to rename.'),
|
|
16
|
+
newName:Property.ShortText({
|
|
17
|
+
displayName:'New Sheet Name',
|
|
18
|
+
required:true
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
async run(context) {
|
|
22
|
+
const authClient = await createGoogleClient(context.auth);
|
|
23
|
+
const sheets = google.sheets({ version: 'v4', auth: authClient });
|
|
24
|
+
|
|
25
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
26
|
+
spreadsheetId: context.propsValue.spreadsheetId,
|
|
27
|
+
requestBody: {
|
|
28
|
+
requests:[
|
|
29
|
+
{
|
|
30
|
+
updateSheetProperties:{
|
|
31
|
+
properties:{
|
|
32
|
+
sheetId:context.propsValue.sheetId,
|
|
33
|
+
title:context.propsValue.newName,
|
|
34
|
+
},
|
|
35
|
+
fields:'title'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return response.data;
|
|
43
|
+
},
|
|
44
|
+
});
|