@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.
Files changed (57) hide show
  1. package/.babelrc +3 -0
  2. package/.eslintrc.json +18 -0
  3. package/README.md +5 -0
  4. package/assets/logo.png +0 -0
  5. package/package.json +28 -0
  6. package/src/i18n/ar.json +124 -0
  7. package/src/i18n/bg.json +124 -0
  8. package/src/i18n/ca.json +132 -0
  9. package/src/i18n/de.json +165 -0
  10. package/src/i18n/es.json +165 -0
  11. package/src/i18n/fr.json +165 -0
  12. package/src/i18n/hi.json +132 -0
  13. package/src/i18n/hu.json +124 -0
  14. package/src/i18n/hy.json +124 -0
  15. package/src/i18n/id.json +132 -0
  16. package/src/i18n/it.json +124 -0
  17. package/src/i18n/ja.json +165 -0
  18. package/src/i18n/ko.json +124 -0
  19. package/src/i18n/nl.json +165 -0
  20. package/src/i18n/pl.json +124 -0
  21. package/src/i18n/pt.json +165 -0
  22. package/src/i18n/ru.json +132 -0
  23. package/src/i18n/sv.json +124 -0
  24. package/src/i18n/translation.json +165 -0
  25. package/src/i18n/uk.json +124 -0
  26. package/src/i18n/vi.json +132 -0
  27. package/src/i18n/zh.json +165 -0
  28. package/src/index.ts +93 -0
  29. package/src/lib/actions/clear-sheet.ts +60 -0
  30. package/src/lib/actions/copy-worksheet.ts +32 -0
  31. package/src/lib/actions/create-column.ts +109 -0
  32. package/src/lib/actions/create-spreadsheet.ts +122 -0
  33. package/src/lib/actions/create-worksheet.ts +62 -0
  34. package/src/lib/actions/delete-row.action.ts +40 -0
  35. package/src/lib/actions/delete-worksheet.ts +36 -0
  36. package/src/lib/actions/export-sheet.ts +86 -0
  37. package/src/lib/actions/find-row-by-num.ts +42 -0
  38. package/src/lib/actions/find-rows.ts +135 -0
  39. package/src/lib/actions/find-spreadsheets.ts +83 -0
  40. package/src/lib/actions/find-worksheet.ts +52 -0
  41. package/src/lib/actions/format-spreadsheet-row.ts +112 -0
  42. package/src/lib/actions/get-many-rows.ts +42 -0
  43. package/src/lib/actions/get-rows.ts +207 -0
  44. package/src/lib/actions/insert-multiple-rows.action.ts +542 -0
  45. package/src/lib/actions/insert-row.action.ts +111 -0
  46. package/src/lib/actions/rename-worksheet.ts +44 -0
  47. package/src/lib/actions/update-multiple-rows.ts +177 -0
  48. package/src/lib/actions/update-row.ts +93 -0
  49. package/src/lib/common/common.ts +383 -0
  50. package/src/lib/common/props.ts +274 -0
  51. package/src/lib/triggers/helpers.ts +155 -0
  52. package/src/lib/triggers/new-or-updated-row.trigger.ts +299 -0
  53. package/src/lib/triggers/new-row-added-webhook.ts +182 -0
  54. package/src/lib/triggers/new-spreadsheet.ts +88 -0
  55. package/src/lib/triggers/new-worksheet.ts +96 -0
  56. package/tsconfig.json +16 -0
  57. package/tsconfig.lib.json +15 -0
@@ -0,0 +1,274 @@
1
+ import { DropdownOption, Property } from '@guayaba/workflows-framework';
2
+ import { google, drive_v3 } from 'googleapis';
3
+ import {
4
+ columnToLabel,
5
+ createGoogleClient,
6
+ getHeaderRow,
7
+ googleSheetsAuth,
8
+ GoogleSheetsAuthValue,
9
+ googleSheetsCommon,
10
+ } from './common';
11
+ import { isNil } from '@guayaba/workflows-shared';
12
+
13
+ const createEmptyOptionList = (message: string) => {
14
+ return {
15
+ disabled: true,
16
+ placeholder: message,
17
+ options: [],
18
+ };
19
+ };
20
+
21
+ export const includeTeamDrivesProp = () =>
22
+ Property.Checkbox({
23
+ displayName: 'Include Shared Drive Sheets ?',
24
+ description: 'Turn this on to also see spreadsheets from Shared Drives.',
25
+ defaultValue: false,
26
+ required: false,
27
+ });
28
+
29
+ export const spreadsheetIdProp = (displayName: string, description: string, required = true) =>
30
+ Property.Dropdown({
31
+ displayName,
32
+ description,
33
+ auth: googleSheetsAuth,
34
+ required,
35
+ refreshers: ['includeTeamDrives'],
36
+ options: async ({ auth, includeTeamDrives }, { searchValue }) => {
37
+ if (!auth) {
38
+ return createEmptyOptionList('please connect your account first.');
39
+ }
40
+
41
+ const authValue = auth;
42
+
43
+ const authClient = await createGoogleClient(authValue);
44
+
45
+ const drive = google.drive({ version: 'v3', auth: authClient });
46
+
47
+ const q = ["mimeType='application/vnd.google-apps.spreadsheet'", 'trashed = false'];
48
+
49
+ if (searchValue) {
50
+ q.push(`name contains '${searchValue}'`);
51
+ }
52
+
53
+ let nextPageToken;
54
+ const options: DropdownOption<string>[] = [];
55
+ do {
56
+ const response: any = await drive.files.list({
57
+ q: q.join(' and '),
58
+ pageToken: nextPageToken,
59
+ orderBy: 'createdTime desc',
60
+ fields: 'nextPageToken, files(id, name)',
61
+ supportsAllDrives: true,
62
+ includeItemsFromAllDrives: includeTeamDrives ? true : false,
63
+ });
64
+ const fileList: drive_v3.Schema$FileList = response.data;
65
+
66
+ if (fileList.files) {
67
+ for (const file of fileList.files) {
68
+ options.push({
69
+ label: file.name!,
70
+ value: file.id!,
71
+ });
72
+ }
73
+ }
74
+ nextPageToken = response.data.nextPageToken;
75
+ } while (nextPageToken);
76
+
77
+ return {
78
+ disabled: false,
79
+ options,
80
+ };
81
+ },
82
+ });
83
+
84
+ export const sheetIdProp = (displayName: string, description: string, required = true) =>
85
+ Property.Dropdown({
86
+ displayName,
87
+ description,
88
+ auth: googleSheetsAuth,
89
+ required,
90
+ refreshers: ['spreadsheetId'],
91
+ options: async ({ auth, spreadsheetId }) => {
92
+ if (!auth) {
93
+ return createEmptyOptionList('please connect your account first.');
94
+ }
95
+
96
+ if ((spreadsheetId ?? '').toString().length === 0) {
97
+ return createEmptyOptionList('please select a spreadsheet first.');
98
+ }
99
+
100
+ const authValue = auth as GoogleSheetsAuthValue;
101
+
102
+ const authClient = await createGoogleClient(authValue);
103
+
104
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
105
+
106
+ const response = await sheets.spreadsheets.get({
107
+ spreadsheetId: spreadsheetId as unknown as string,
108
+ });
109
+
110
+ const sheetsData = response.data.sheets ?? [];
111
+
112
+ const options: DropdownOption<number>[] = [];
113
+
114
+ for (const sheet of sheetsData) {
115
+ const title = sheet.properties?.title;
116
+ const sheetId = sheet.properties?.sheetId;
117
+ if (isNil(title) || isNil(sheetId)) {
118
+ continue;
119
+ }
120
+ options.push({
121
+ label: title,
122
+ value: sheetId,
123
+ });
124
+ }
125
+
126
+ return {
127
+ disabled: false,
128
+ options,
129
+ };
130
+ },
131
+ });
132
+
133
+ export const commonProps = {
134
+ includeTeamDrives: includeTeamDrivesProp(),
135
+ spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'),
136
+ sheetId: sheetIdProp('Worksheet', 'The ID of the worksheet to use.'),
137
+ };
138
+
139
+ export const rowValuesProp = () =>
140
+ Property.DynamicProperties({
141
+ displayName: 'Values',
142
+ description: 'The values to add',
143
+ required: true,
144
+ auth: googleSheetsAuth,
145
+ refreshers: ['sheetId', 'spreadsheetId', 'first_row_headers'],
146
+ props: async ({ auth, spreadsheetId, sheetId, first_row_headers }) => {
147
+ if (
148
+ !auth ||
149
+ (spreadsheetId ?? '').toString().length === 0 ||
150
+ (sheetId ?? '').toString().length === 0
151
+ ) {
152
+ return {};
153
+ }
154
+ const sheet_id = Number(sheetId);
155
+ const authValue = auth as GoogleSheetsAuthValue;
156
+
157
+ const headers = await googleSheetsCommon.getHeaderRow({
158
+ spreadsheetId: spreadsheetId as unknown as string,
159
+ auth: authValue,
160
+ sheetId: sheet_id,
161
+ });
162
+
163
+ if (!first_row_headers) {
164
+ return {
165
+ values: Property.Array({
166
+ displayName: 'Row Values',
167
+ required: true,
168
+ }),
169
+ };
170
+ }
171
+ const firstRow = headers ?? [];
172
+ const properties: {
173
+ [key: string]: any;
174
+ } = {};
175
+
176
+ for (let i = 0; i < firstRow.length; i++) {
177
+ const label = columnToLabel(i);
178
+ properties[label] = Property.ShortText({
179
+ displayName: firstRow[i].toString(),
180
+ // description: firstRow[i].toString(),
181
+ required: false,
182
+ defaultValue: '',
183
+ });
184
+ }
185
+ return properties;
186
+ },
187
+ });
188
+
189
+ export const columnNameProp = () =>
190
+ Property.Dropdown<string, true, typeof googleSheetsAuth>({
191
+ displayName: 'Column Name',
192
+ description: 'The name of the column to search in',
193
+ required: true,
194
+ auth: googleSheetsAuth,
195
+ refreshers: ['sheetId', 'spreadsheetId'],
196
+ options: async ({ auth, spreadsheetId, sheetId }) => {
197
+ const spreadsheet_id = spreadsheetId as string;
198
+ const sheet_id = Number(sheetId) as number;
199
+ if (
200
+ !auth ||
201
+ (spreadsheet_id ?? '').toString().length === 0 ||
202
+ (sheet_id ?? '').toString().length === 0
203
+ ) {
204
+ return {
205
+ disabled: true,
206
+ options: [],
207
+ placeholder: 'Please select a sheet first',
208
+ };
209
+ }
210
+
211
+ const sheetName = await googleSheetsCommon.findSheetName(auth, spreadsheet_id, sheet_id);
212
+
213
+ if (!sheetName) {
214
+ throw Error('Sheet not found in spreadsheet');
215
+ }
216
+
217
+ const headers = await getHeaderRow({
218
+ spreadsheetId: spreadsheet_id,
219
+ auth,
220
+ sheetId: sheet_id,
221
+ });
222
+
223
+ const ret = [];
224
+
225
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
226
+
227
+ if (isNil(headers)) {
228
+ return {
229
+ options: [],
230
+ disabled: false,
231
+ };
232
+ }
233
+ if (headers.length === 0) {
234
+ const columnSize = headers.length;
235
+
236
+ for (let i = 0; i < columnSize; i++) {
237
+ ret.push({
238
+ label: alphabet[i].toUpperCase(),
239
+ value: alphabet[i],
240
+ });
241
+ }
242
+ } else {
243
+ let index = 0;
244
+ for (let i = 0; i < headers.length; i++) {
245
+ let value = 'A';
246
+ if (index >= alphabet.length) {
247
+ // if the index is greater than the length of the alphabet, we need to add another letter
248
+ const firstLetter = alphabet[Math.floor(index / alphabet.length) - 1];
249
+ const secondLetter = alphabet[index % alphabet.length];
250
+ value = firstLetter + secondLetter;
251
+ } else {
252
+ value = alphabet[index];
253
+ }
254
+
255
+ ret.push({
256
+ label: headers[i].toString(),
257
+ value: value,
258
+ });
259
+ index++;
260
+ }
261
+ }
262
+ return {
263
+ options: ret,
264
+ disabled: false,
265
+ };
266
+ },
267
+ });
268
+
269
+ export const isFirstRowHeaderProp = () =>
270
+ Property.Checkbox({
271
+ displayName: 'First Row Contains Headers ?',
272
+ required: true,
273
+ defaultValue: false,
274
+ });
@@ -0,0 +1,155 @@
1
+ import { google } from 'googleapis';
2
+ import { nanoid } from 'nanoid';
3
+ import dayjs from 'dayjs';
4
+ import crypto from 'crypto';
5
+ import { columnToLabel, createGoogleClient, GoogleSheetsAuthValue } from '../common/common';
6
+ import { isNil } from '@guayaba/workflows-shared';
7
+
8
+ export async function getWorkSheetName(
9
+ auth: GoogleSheetsAuthValue,
10
+ spreadSheetId: string,
11
+ sheetId: number,
12
+ ) {
13
+ const authClient = await createGoogleClient(auth);
14
+
15
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
16
+
17
+ const res = await sheets.spreadsheets.get({ spreadsheetId: spreadSheetId });
18
+ const sheetName = res.data.sheets?.find((f) => f.properties?.sheetId == sheetId)?.properties
19
+ ?.title;
20
+
21
+ if (!sheetName) {
22
+ throw Error(`Sheet with ID ${sheetId} not found in spreadsheet ${spreadSheetId}`);
23
+ }
24
+
25
+ return sheetName;
26
+ }
27
+
28
+ export async function getWorkSheetGridSize(
29
+ auth: GoogleSheetsAuthValue,
30
+ spreadSheetId: string,
31
+ sheetId: number,
32
+ ) {
33
+ const authClient = await createGoogleClient(auth);
34
+
35
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
36
+
37
+ const res = await sheets.spreadsheets.get({ spreadsheetId: spreadSheetId, includeGridData: true, fields: 'sheets.properties(sheetId,title,sheetType,gridProperties)' });
38
+ const sheetRange = res.data.sheets?.find((f) => f.properties?.sheetId == sheetId)?.properties?.gridProperties;
39
+
40
+ if (!sheetRange) {
41
+ throw Error(`Unable to get grid size for sheet ${sheetId} in spreadsheet ${spreadSheetId}`);
42
+ }
43
+
44
+ return sheetRange
45
+ }
46
+
47
+ export async function getWorkSheetValues(
48
+ auth: GoogleSheetsAuthValue,
49
+ spreadsheetId: string,
50
+ range?: string,
51
+ ) {
52
+ const authClient = await createGoogleClient(auth);
53
+
54
+ const sheets = google.sheets({ version: 'v4', auth: authClient });
55
+
56
+ const res = await sheets.spreadsheets.values.get({
57
+ spreadsheetId: spreadsheetId,
58
+ range: range,
59
+ });
60
+
61
+ return res.data.values ?? [];
62
+ }
63
+
64
+ export async function createFileNotification(
65
+ auth: GoogleSheetsAuthValue,
66
+ fileId: string,
67
+ url: string,
68
+ includeTeamDrives?: boolean,
69
+ ) {
70
+ const authClient = await createGoogleClient(auth);
71
+
72
+ const drive = google.drive({ version: 'v3', auth: authClient });
73
+
74
+ // create unique UUID for channel
75
+ const channelId = nanoid();
76
+ return await drive.files.watch({
77
+ fileId: fileId,
78
+ supportsAllDrives: includeTeamDrives,
79
+ requestBody: {
80
+ id: channelId,
81
+ expiration: (dayjs().add(6, 'day').unix() * 1000).toString(),
82
+ type: 'web_hook',
83
+ address: url,
84
+ },
85
+ });
86
+ }
87
+
88
+ export async function deleteFileNotification(
89
+ auth: GoogleSheetsAuthValue,
90
+ channelId: string,
91
+ resourceId: string,
92
+ ) {
93
+ const authClient = await createGoogleClient(auth);
94
+
95
+ const drive = google.drive({ version: 'v3', auth: authClient });
96
+
97
+ return await drive.channels.stop({
98
+ requestBody: {
99
+ id: channelId,
100
+ resourceId: resourceId,
101
+ },
102
+ });
103
+ }
104
+
105
+ export function isSyncMessage(headers: Record<string, string>) {
106
+ return headers['x-goog-resource-state'] === 'sync';
107
+ }
108
+
109
+ export function isChangeContentMessage(headers: Record<string, string>) {
110
+ // https://developers.google.com/drive/api/guides/push#respond-to-notifications
111
+ return (
112
+ headers['x-goog-resource-state'] === 'update' &&
113
+ ['content', 'properties', 'content,properties'].includes(headers['x-goog-changed'])
114
+ );
115
+ }
116
+
117
+ export function hashObject(obj: Record<string, unknown>): string {
118
+ const hash = crypto.createHash('sha256');
119
+ hash.update(JSON.stringify(obj));
120
+ return hash.digest('hex');
121
+ }
122
+ // returns an array of row number and cells values mapped to column labels
123
+ export function mapRowsToColumnLabels(rowValues: any[][], oldRowCount: number, headerCount: number) {
124
+ const result = [];
125
+ for (let i = 0; i < rowValues.length; i++) {
126
+ const values: Record<string, string> = {};
127
+ for (let j = 0; j < Math.max(headerCount, rowValues[i].length); j++) {
128
+ const columnLabel = columnToLabel(j);
129
+ if (isNil(rowValues[i][j])) {
130
+ values[columnLabel] = "";
131
+ } else if (typeof rowValues[i][j] === "string") {
132
+ values[columnLabel] = rowValues[i][j];
133
+ }
134
+ else if ('toString' in rowValues[i][j]) {
135
+ values[columnLabel] = rowValues[i][j].toString();
136
+ }
137
+ else {
138
+ values[columnLabel] = `${rowValues[i][j]}`;
139
+ }
140
+ }
141
+ result.push({
142
+ row: oldRowCount + i + 1,
143
+ values,
144
+ });
145
+ }
146
+ return result;
147
+ }
148
+
149
+ export interface WebhookInformation {
150
+ kind?: string | null;
151
+ id?: string | null;
152
+ resourceId?: string | null;
153
+ resourceUri?: string | null;
154
+ expiration?: string | null;
155
+ }