@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,177 @@
|
|
|
1
|
+
import { googleSheetsAuth } from '../common/common';
|
|
2
|
+
import {
|
|
3
|
+
createAction,
|
|
4
|
+
DynamicPropsValue,
|
|
5
|
+
Property,
|
|
6
|
+
} from '@guayaba/workflows-framework';
|
|
7
|
+
import {
|
|
8
|
+
areSheetIdsValid,
|
|
9
|
+
createGoogleClient,
|
|
10
|
+
Dimension,
|
|
11
|
+
GoogleSheetsAuthValue,
|
|
12
|
+
googleSheetsCommon,
|
|
13
|
+
objectToArray,
|
|
14
|
+
ValueInputOption,
|
|
15
|
+
} from '../common/common';
|
|
16
|
+
import { isString, MarkdownVariant } from '@guayaba/workflows-shared';
|
|
17
|
+
import { getWorkSheetName } from '../triggers/helpers';
|
|
18
|
+
import { google, sheets_v4 } from 'googleapis';
|
|
19
|
+
import { commonProps } from '../common/props';
|
|
20
|
+
|
|
21
|
+
export const updateMultipleRowsAction = createAction({
|
|
22
|
+
auth: googleSheetsAuth,
|
|
23
|
+
name: 'update-multiple-rows',
|
|
24
|
+
displayName: 'Update Multiple Rows',
|
|
25
|
+
description: 'Updates multiple rows in a specific spreadsheet.',
|
|
26
|
+
props: {
|
|
27
|
+
...commonProps,
|
|
28
|
+
values: Property.DynamicProperties({
|
|
29
|
+
auth: googleSheetsAuth,
|
|
30
|
+
displayName: 'Values',
|
|
31
|
+
description: 'The values to update.',
|
|
32
|
+
required: true,
|
|
33
|
+
refreshers: ['sheetId', 'spreadsheetId', 'headerRow'],
|
|
34
|
+
props: async ({ auth, spreadsheetId, sheetId, headerRow }) => {
|
|
35
|
+
const sheet_Id = Number(sheetId);
|
|
36
|
+
const spreadsheet_Id = spreadsheetId as unknown as string;
|
|
37
|
+
const authentication = auth;
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
!auth ||
|
|
41
|
+
(spreadsheet_Id ?? '').toString().length === 0 ||
|
|
42
|
+
(sheet_Id ?? '').toString().length === 0
|
|
43
|
+
) {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const fields: DynamicPropsValue = {};
|
|
48
|
+
|
|
49
|
+
const headers = await googleSheetsCommon.getGoogleSheetRows({
|
|
50
|
+
spreadsheetId: spreadsheet_Id,
|
|
51
|
+
auth: auth as GoogleSheetsAuthValue,
|
|
52
|
+
sheetId: sheet_Id,
|
|
53
|
+
rowIndex_s: 1,
|
|
54
|
+
rowIndex_e: 1,
|
|
55
|
+
headerRow: (headerRow as unknown as number) || 1,
|
|
56
|
+
});
|
|
57
|
+
const firstRow = headers[0].values ?? {};
|
|
58
|
+
|
|
59
|
+
//check for empty headers
|
|
60
|
+
if (Object.keys(firstRow).length === 0) {
|
|
61
|
+
fields['markdown'] = Property.MarkDown({
|
|
62
|
+
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.`,
|
|
63
|
+
variant: MarkdownVariant.INFO,
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
const columns: {
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
} = {
|
|
69
|
+
rowId: Property.Number({
|
|
70
|
+
displayName: 'Row Id',
|
|
71
|
+
description: 'The row id to update',
|
|
72
|
+
required: true,
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
for (const key in firstRow) {
|
|
77
|
+
columns[key] = Property.ShortText({
|
|
78
|
+
displayName: firstRow[key].toString(),
|
|
79
|
+
description: firstRow[key].toString(),
|
|
80
|
+
required: false,
|
|
81
|
+
defaultValue: '',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
fields['values'] = Property.Array({
|
|
85
|
+
displayName: 'Values',
|
|
86
|
+
required: false,
|
|
87
|
+
properties: columns,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return fields;
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
as_string: Property.Checkbox({
|
|
95
|
+
displayName: 'As String',
|
|
96
|
+
description:
|
|
97
|
+
'Inserted values that are dates and formulas will be entered as strings and have no effect',
|
|
98
|
+
required: false,
|
|
99
|
+
}),
|
|
100
|
+
headerRow: Property.Number({
|
|
101
|
+
displayName: 'Header Row',
|
|
102
|
+
description: 'Which row contains the headers?',
|
|
103
|
+
required: true,
|
|
104
|
+
defaultValue: 1,
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
async run(context) {
|
|
108
|
+
const {
|
|
109
|
+
spreadsheetId: inputSpreadsheetId,
|
|
110
|
+
sheetId: inputSheetId,
|
|
111
|
+
values: { values: rowValuesInput },
|
|
112
|
+
as_string: asString,
|
|
113
|
+
headerRow,
|
|
114
|
+
} = context.propsValue;
|
|
115
|
+
|
|
116
|
+
if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) {
|
|
117
|
+
throw new Error('Please select a spreadsheet and sheet first.');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const sheetId = Number(inputSheetId);
|
|
121
|
+
const spreadsheetId = inputSpreadsheetId as string;
|
|
122
|
+
|
|
123
|
+
const sheetName = await getWorkSheetName(
|
|
124
|
+
context.auth,
|
|
125
|
+
spreadsheetId,
|
|
126
|
+
sheetId
|
|
127
|
+
);
|
|
128
|
+
const valueInputOption = asString
|
|
129
|
+
? ValueInputOption.RAW
|
|
130
|
+
: ValueInputOption.USER_ENTERED;
|
|
131
|
+
|
|
132
|
+
const authClient = await createGoogleClient(context.auth);
|
|
133
|
+
const sheets = google.sheets({ version: 'v4', auth: authClient });
|
|
134
|
+
|
|
135
|
+
const values: sheets_v4.Schema$ValueRange[] = [];
|
|
136
|
+
|
|
137
|
+
for (const row of rowValuesInput) {
|
|
138
|
+
const { rowId, ...rowValues } = row;
|
|
139
|
+
if (rowId === undefined || rowId === null) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const formattedValues = objectToArray(rowValues).map(
|
|
144
|
+
(value: string | null | undefined) => {
|
|
145
|
+
if (value === '' || value === null || value === undefined) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
if (isString(value)) {
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
return JSON.stringify(value, null, 2);
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (formattedValues.length === 0) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
values.push({
|
|
160
|
+
range: `${sheetName}!A${rowId}:ZZZ${rowId}`,
|
|
161
|
+
majorDimension: Dimension.ROWS,
|
|
162
|
+
values: [formattedValues],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const response = await sheets.spreadsheets.values.batchUpdate({
|
|
167
|
+
spreadsheetId: spreadsheetId,
|
|
168
|
+
|
|
169
|
+
requestBody: {
|
|
170
|
+
valueInputOption: valueInputOption,
|
|
171
|
+
data: values,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return response.data;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createAction, Property } from '@guayaba/workflows-framework';
|
|
2
|
+
import { areSheetIdsValid, createGoogleClient, Dimension, objectToArray, ValueInputOption } from '../common/common';
|
|
3
|
+
import { googleSheetsAuth } from '../common/common';
|
|
4
|
+
import { getWorkSheetName } from '../triggers/helpers';
|
|
5
|
+
import { google } from 'googleapis';
|
|
6
|
+
import { isString } from '@guayaba/workflows-shared';
|
|
7
|
+
import { commonProps, isFirstRowHeaderProp, rowValuesProp } from '../common/props';
|
|
8
|
+
|
|
9
|
+
export const updateRowAction = createAction({
|
|
10
|
+
auth: googleSheetsAuth,
|
|
11
|
+
name: 'update_row',
|
|
12
|
+
description: 'Update the data in an existing row.',
|
|
13
|
+
displayName: 'Update Row',
|
|
14
|
+
props: {
|
|
15
|
+
...commonProps,
|
|
16
|
+
row_id: Property.Number({
|
|
17
|
+
displayName: 'Row Number',
|
|
18
|
+
description: 'The row number to update',
|
|
19
|
+
required: true,
|
|
20
|
+
}),
|
|
21
|
+
first_row_headers: isFirstRowHeaderProp(),
|
|
22
|
+
values: rowValuesProp(),
|
|
23
|
+
},
|
|
24
|
+
async run(context) {
|
|
25
|
+
const inputSpreadsheetId = context.propsValue.spreadsheetId;
|
|
26
|
+
const inputSheetId = context.propsValue.sheetId;
|
|
27
|
+
const rowId = context.propsValue.row_id;
|
|
28
|
+
const isFirstRowHeaders = context.propsValue.first_row_headers;
|
|
29
|
+
const rowValuesInput = context.propsValue.values;
|
|
30
|
+
|
|
31
|
+
if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) {
|
|
32
|
+
throw new Error('Please select a spreadsheet and sheet first.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sheetId = Number(inputSheetId);
|
|
36
|
+
const spreadsheetId = inputSpreadsheetId as string;
|
|
37
|
+
|
|
38
|
+
const authClient = await createGoogleClient(context.auth);
|
|
39
|
+
|
|
40
|
+
const sheets = google.sheets({ version: 'v4', auth: authClient });
|
|
41
|
+
|
|
42
|
+
const sheetName = await getWorkSheetName(
|
|
43
|
+
context.auth,
|
|
44
|
+
spreadsheetId,
|
|
45
|
+
sheetId
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// replace empty string with null to skip the cell value
|
|
49
|
+
const formattedValues = (
|
|
50
|
+
isFirstRowHeaders
|
|
51
|
+
? objectToArray(rowValuesInput)
|
|
52
|
+
: rowValuesInput['values']
|
|
53
|
+
).map((value: string | null | undefined) => {
|
|
54
|
+
if (value === '' || value === null || value === undefined) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (isString(value)) {
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
return JSON.stringify(value, null, 2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if (formattedValues.length > 0) {
|
|
65
|
+
const response = await sheets.spreadsheets.values.update({
|
|
66
|
+
range: `${sheetName}!A${rowId}:ZZZ${rowId}`,
|
|
67
|
+
spreadsheetId: spreadsheetId,
|
|
68
|
+
valueInputOption: ValueInputOption.USER_ENTERED,
|
|
69
|
+
requestBody: {
|
|
70
|
+
values: [formattedValues],
|
|
71
|
+
majorDimension: Dimension.ROWS,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
//Split the updatedRange string to extract the row number
|
|
76
|
+
const updatedRangeParts = response.data.updatedRange?.split(
|
|
77
|
+
'!'
|
|
78
|
+
);
|
|
79
|
+
const updatedRowRange = updatedRangeParts?.[1];
|
|
80
|
+
const updatedRowNumber = parseInt(
|
|
81
|
+
updatedRowRange?.split(':')[0].substring(1) ?? '0',
|
|
82
|
+
10
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return { updates: { ...response.data }, row: updatedRowNumber };
|
|
86
|
+
} else {
|
|
87
|
+
throw Error(
|
|
88
|
+
'Values passed are empty or not array ' +
|
|
89
|
+
JSON.stringify(formattedValues)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { AppConnectionValueForAuthProperty, OAuth2PropertyValue, OAuth2Props, PieceAuth, PiecePropValueSchema, Property, ShortTextProperty, StaticPropsValue } from '@guayaba/workflows-framework';
|
|
2
|
+
import {
|
|
3
|
+
httpClient,
|
|
4
|
+
HttpMethod,
|
|
5
|
+
AuthenticationType,
|
|
6
|
+
HttpRequest,
|
|
7
|
+
} from '@guayaba/workflows-common';
|
|
8
|
+
import { AppConnectionType, isNil, isString } from '@guayaba/workflows-shared';
|
|
9
|
+
import { google } from 'googleapis';
|
|
10
|
+
import { OAuth2Client } from 'googleapis-common';
|
|
11
|
+
import { mapRowsToColumnLabels } from '../triggers/helpers';
|
|
12
|
+
|
|
13
|
+
export type GoogleSheetsAuthValue = AppConnectionValueForAuthProperty<typeof googleSheetsAuth>
|
|
14
|
+
export const googleSheetsCommon = {
|
|
15
|
+
baseUrl: 'https://sheets.googleapis.com/v4/spreadsheets',
|
|
16
|
+
getGoogleSheetRows,
|
|
17
|
+
findSheetName,
|
|
18
|
+
deleteRow,
|
|
19
|
+
clearSheet,
|
|
20
|
+
getHeaderRow,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function findSheetName(
|
|
24
|
+
auth: GoogleSheetsAuthValue,
|
|
25
|
+
spreadsheetId: string,
|
|
26
|
+
sheetId: string | number,
|
|
27
|
+
) {
|
|
28
|
+
const sheets = await listSheetsName(auth, spreadsheetId);
|
|
29
|
+
const sheetName = sheets.find((f) => f.properties.sheetId == sheetId)?.properties.title;
|
|
30
|
+
if (!sheetName) {
|
|
31
|
+
throw Error(`Sheet with ID ${sheetId} not found in spreadsheet ${spreadsheetId}`);
|
|
32
|
+
}
|
|
33
|
+
return sheetName;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function listSheetsName(auth: GoogleSheetsAuthValue, spreadsheet_id: string) {
|
|
37
|
+
return (
|
|
38
|
+
await httpClient.sendRequest<{
|
|
39
|
+
sheets: { properties: { title: string; sheetId: number } }[];
|
|
40
|
+
}>({
|
|
41
|
+
method: HttpMethod.GET,
|
|
42
|
+
url: `https://sheets.googleapis.com/v4/spreadsheets/` + spreadsheet_id,
|
|
43
|
+
authentication: {
|
|
44
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
45
|
+
token: await getAccessToken(auth),
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
).body.sheets;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type GetGoogleSheetRowsProps = {
|
|
52
|
+
spreadsheetId: string;
|
|
53
|
+
auth: GoogleSheetsAuthValue;
|
|
54
|
+
sheetId: number;
|
|
55
|
+
rowIndex_s: number | undefined;
|
|
56
|
+
rowIndex_e: number | undefined;
|
|
57
|
+
headerRow?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
async function getGoogleSheetRows({
|
|
61
|
+
spreadsheetId,
|
|
62
|
+
auth,
|
|
63
|
+
sheetId,
|
|
64
|
+
rowIndex_s,
|
|
65
|
+
rowIndex_e,
|
|
66
|
+
headerRow = 1,
|
|
67
|
+
}: GetGoogleSheetRowsProps): Promise<{ row: number; values: { [x: string]: string } }[]> {
|
|
68
|
+
const sheetName = await findSheetName(auth, spreadsheetId, sheetId);
|
|
69
|
+
if (!sheetName) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let range = '';
|
|
74
|
+
if (rowIndex_s !== undefined) {
|
|
75
|
+
range = `!A${rowIndex_s}:ZZZ`;
|
|
76
|
+
}
|
|
77
|
+
if (rowIndex_s !== undefined && rowIndex_e !== undefined) {
|
|
78
|
+
range = `!A${rowIndex_s}:ZZZ${rowIndex_e}`;
|
|
79
|
+
}
|
|
80
|
+
const rowsResponse = await httpClient.sendRequest<{ values: [string[]][] }>({
|
|
81
|
+
method: HttpMethod.GET,
|
|
82
|
+
url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/values/${encodeURIComponent(`${sheetName}${range}`)}`,
|
|
83
|
+
authentication: {
|
|
84
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
85
|
+
token: await getAccessToken(auth),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
if (rowsResponse.body.values === undefined) return [];
|
|
89
|
+
|
|
90
|
+
const headerResponse = await httpClient.sendRequest<{ values: [string[]][] }>({
|
|
91
|
+
method: HttpMethod.GET,
|
|
92
|
+
url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/values/${encodeURIComponent(`${sheetName}!A${headerRow}:ZZZ${headerRow}`)}`,
|
|
93
|
+
authentication: {
|
|
94
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
95
|
+
token: await getAccessToken(auth),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (!headerResponse.body.values) {
|
|
100
|
+
throw new Error(`Unable to read headers from row ${headerRow} in sheet "${sheetName}". The row appears to be empty or inaccessible.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const headers = headerResponse.body.values[0] ?? [];
|
|
104
|
+
const headerCount = headers.length;
|
|
105
|
+
|
|
106
|
+
const startingRow = rowIndex_s ? rowIndex_s - 1 : 0;
|
|
107
|
+
|
|
108
|
+
const labeledRowValues = mapRowsToColumnLabels(
|
|
109
|
+
rowsResponse.body.values,
|
|
110
|
+
startingRow,
|
|
111
|
+
headerCount,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return labeledRowValues;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type GetHeaderRowProps = {
|
|
118
|
+
spreadsheetId: string;
|
|
119
|
+
auth: GoogleSheetsAuthValue;
|
|
120
|
+
sheetId: number;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export async function getHeaderRow({
|
|
124
|
+
spreadsheetId,
|
|
125
|
+
auth,
|
|
126
|
+
sheetId,
|
|
127
|
+
}: GetHeaderRowProps): Promise<string[] | undefined> {
|
|
128
|
+
const rows = await getGoogleSheetRows({
|
|
129
|
+
spreadsheetId,
|
|
130
|
+
auth,
|
|
131
|
+
sheetId,
|
|
132
|
+
rowIndex_s: 1,
|
|
133
|
+
rowIndex_e: 1,
|
|
134
|
+
headerRow: 1,
|
|
135
|
+
});
|
|
136
|
+
if (rows.length === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
return objectToArray(rows[0].values);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const columnToLabel = (columnIndex: number) => {
|
|
143
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
144
|
+
let label = '';
|
|
145
|
+
|
|
146
|
+
while (columnIndex >= 0) {
|
|
147
|
+
label = alphabet[columnIndex % 26] + label;
|
|
148
|
+
columnIndex = Math.floor(columnIndex / 26) - 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return label;
|
|
152
|
+
};
|
|
153
|
+
export const labelToColumn = (label: string) => {
|
|
154
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
155
|
+
let column = 0;
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < label.length; i++) {
|
|
158
|
+
column += (alphabet.indexOf(label[i]) + 1) * Math.pow(26, label.length - i - 1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return column - 1;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export function objectToArray(obj: { [x: string]: any }) {
|
|
165
|
+
const maxIndex = Math.max(...Object.keys(obj).map((key) => labelToColumn(key)));
|
|
166
|
+
const arr = new Array(maxIndex + 1);
|
|
167
|
+
for (const key in obj) {
|
|
168
|
+
arr[labelToColumn(key)] = obj[key];
|
|
169
|
+
}
|
|
170
|
+
return arr;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function stringifyArray(object: unknown[]): string[] {
|
|
174
|
+
return object.map((val) => {
|
|
175
|
+
if (isString(val)) {
|
|
176
|
+
return val;
|
|
177
|
+
}
|
|
178
|
+
return JSON.stringify(val);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function mapRowsToHeaderNames(
|
|
183
|
+
rows:any[],
|
|
184
|
+
useHeaderNames: boolean,
|
|
185
|
+
spreadsheetId: string,
|
|
186
|
+
sheetId: number,
|
|
187
|
+
headerRow: number,
|
|
188
|
+
auth: GoogleSheetsAuthValue,
|
|
189
|
+
): Promise<any[]> {
|
|
190
|
+
if (!useHeaderNames) {
|
|
191
|
+
return rows;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const headerRows = await getGoogleSheetRows({
|
|
195
|
+
spreadsheetId,
|
|
196
|
+
auth,
|
|
197
|
+
sheetId,
|
|
198
|
+
rowIndex_s: headerRow,
|
|
199
|
+
rowIndex_e: headerRow,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (headerRows.length === 0) {
|
|
203
|
+
return rows;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const headers = Object.values(headerRows[0].values);
|
|
207
|
+
if (headers.length === 0) {
|
|
208
|
+
return rows;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// map rows to use header names as keys instead of column letters
|
|
212
|
+
return rows.map(row => {
|
|
213
|
+
const newValues: Record<string, any> = {};
|
|
214
|
+
Object.keys(row.values).forEach((columnLetter) => {
|
|
215
|
+
const columnIndex = labelToColumn(columnLetter);
|
|
216
|
+
const headerName = headers[columnIndex];
|
|
217
|
+
if (headerName) {
|
|
218
|
+
newValues[headerName] = row.values[columnLetter];
|
|
219
|
+
}
|
|
220
|
+
else{
|
|
221
|
+
newValues[columnLetter] = row.values[columnLetter];
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
return { ...row, values: newValues };
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function deleteRow(
|
|
229
|
+
spreadsheetId: string,
|
|
230
|
+
sheetId: number,
|
|
231
|
+
rowIndex: number,
|
|
232
|
+
auth: GoogleSheetsAuthValue,
|
|
233
|
+
) {
|
|
234
|
+
const request: HttpRequest = {
|
|
235
|
+
method: HttpMethod.POST,
|
|
236
|
+
url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/:batchUpdate`,
|
|
237
|
+
authentication: {
|
|
238
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
239
|
+
token: await getAccessToken(auth),
|
|
240
|
+
},
|
|
241
|
+
body: {
|
|
242
|
+
requests: [
|
|
243
|
+
{
|
|
244
|
+
deleteDimension: {
|
|
245
|
+
range: {
|
|
246
|
+
sheetId: sheetId,
|
|
247
|
+
dimension: 'ROWS',
|
|
248
|
+
startIndex: rowIndex,
|
|
249
|
+
endIndex: rowIndex + 1,
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
await httpClient.sendRequest(request);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function clearSheet(
|
|
260
|
+
spreadsheetId: string,
|
|
261
|
+
sheetId: number,
|
|
262
|
+
auth: GoogleSheetsAuthValue,
|
|
263
|
+
rowIndex: number,
|
|
264
|
+
numOfRows: number,
|
|
265
|
+
) {
|
|
266
|
+
const request: HttpRequest = {
|
|
267
|
+
method: HttpMethod.POST,
|
|
268
|
+
url: `${googleSheetsCommon.baseUrl}/${spreadsheetId}/:batchUpdate`,
|
|
269
|
+
authentication: {
|
|
270
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
271
|
+
token: await getAccessToken(auth),
|
|
272
|
+
},
|
|
273
|
+
body: {
|
|
274
|
+
requests: [
|
|
275
|
+
{
|
|
276
|
+
deleteDimension: {
|
|
277
|
+
range: {
|
|
278
|
+
sheetId: sheetId,
|
|
279
|
+
dimension: 'ROWS',
|
|
280
|
+
startIndex: rowIndex,
|
|
281
|
+
endIndex: rowIndex + numOfRows + 1,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
return await httpClient.sendRequest(request);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export enum ValueInputOption {
|
|
292
|
+
RAW = 'RAW',
|
|
293
|
+
USER_ENTERED = 'USER_ENTERED',
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export enum Dimension {
|
|
297
|
+
ROWS = 'ROWS',
|
|
298
|
+
COLUMNS = 'COLUMNS',
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function createGoogleClient(auth: GoogleSheetsAuthValue): Promise<OAuth2Client> {
|
|
302
|
+
if(auth.type === AppConnectionType.CUSTOM_AUTH)
|
|
303
|
+
{
|
|
304
|
+
const serviceAccount = JSON.parse(auth.props.serviceAccount);
|
|
305
|
+
return new google.auth.JWT({
|
|
306
|
+
email: serviceAccount.client_email,
|
|
307
|
+
key: serviceAccount.private_key,
|
|
308
|
+
scopes: googleSheetsScopes,
|
|
309
|
+
subject: auth.props.userEmail,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const authClient = new OAuth2Client();
|
|
313
|
+
authClient.setCredentials(auth);
|
|
314
|
+
return authClient;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export const getAccessToken = async (auth: GoogleSheetsAuthValue): Promise<string> => {
|
|
318
|
+
if(auth.type === AppConnectionType.CUSTOM_AUTH)
|
|
319
|
+
{
|
|
320
|
+
const googleClient = await createGoogleClient(auth);
|
|
321
|
+
const response = await googleClient.getAccessToken();
|
|
322
|
+
if(response.token)
|
|
323
|
+
{
|
|
324
|
+
return response.token;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
throw new Error('Could not retrieve access token from service account json');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return auth.access_token;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function areSheetIdsValid(spreadsheetId: string | null | undefined, sheetId: string | number | null | undefined): boolean {
|
|
334
|
+
return !isNil(spreadsheetId) && spreadsheetId !== "" &&
|
|
335
|
+
!isNil(sheetId) && sheetId !== "";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const googleSheetsScopes = [
|
|
339
|
+
'https://www.googleapis.com/auth/spreadsheets',
|
|
340
|
+
'https://www.googleapis.com/auth/drive.readonly',
|
|
341
|
+
'https://www.googleapis.com/auth/drive',
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
export const googleSheetsAuth =[PieceAuth.OAuth2({
|
|
345
|
+
description: '',
|
|
346
|
+
authUrl: 'https://accounts.google.com/o/oauth2/auth',
|
|
347
|
+
tokenUrl: 'https://oauth2.googleapis.com/token',
|
|
348
|
+
required: true,
|
|
349
|
+
scope:googleSheetsScopes ,
|
|
350
|
+
}), PieceAuth.CustomAuth({
|
|
351
|
+
displayName: 'Service Account (Advanced)',
|
|
352
|
+
description: 'Authenticate via service account from https://console.cloud.google.com/ > IAM & Admin > Service Accounts > Create Service Account > Keys > Add key. <br> <br> You can optionally use domain-wide delegation (https://support.google.com/a/answer/162106?hl=en#zippy=%2Cset-up-domain-wide-delegation-for-a-client) to access spreadsheets without adding the service account to each one. <br> <br> **Note:** Without a user email, the service account only has access to files/folders you explicitly share with it.',
|
|
353
|
+
required: true,
|
|
354
|
+
props: {
|
|
355
|
+
serviceAccount: Property.ShortText({
|
|
356
|
+
displayName: 'Service Account JSON Key',
|
|
357
|
+
required: true,
|
|
358
|
+
}
|
|
359
|
+
) ,
|
|
360
|
+
userEmail: Property.ShortText({
|
|
361
|
+
displayName: 'User Email',
|
|
362
|
+
required: false,
|
|
363
|
+
description: 'Email address of the user to impersonate for domain-wide delegation.',
|
|
364
|
+
}),},
|
|
365
|
+
validate: async ({auth})=>{
|
|
366
|
+
try{
|
|
367
|
+
await getAccessToken({
|
|
368
|
+
type: AppConnectionType.CUSTOM_AUTH,
|
|
369
|
+
props: {...auth}
|
|
370
|
+
});
|
|
371
|
+
}catch(e){
|
|
372
|
+
return {
|
|
373
|
+
valid: false,
|
|
374
|
+
error: (e as Error).message,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
return {
|
|
378
|
+
valid: true,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
})];
|
|
382
|
+
|
|
383
|
+
|