diff --git a/docker-compose.yml b/docker-compose.yml index 55f8978e2ae..3613153df37 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: activepieces: - image: ghcr.io/activepieces/activepieces:0.77.4 + image: ghcr.io/activepieces/activepieces:0.77.5 container_name: activepieces restart: unless-stopped ## Enable the following line if you already use AP_EXECUTION_MODE with SANDBOX_PROCESS or old activepieces, checking the breaking change documentation for more info. diff --git a/package.json b/package.json index 8f7bfc67915..343a3e3004d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "activepieces", - "version": "0.77.4", + "version": "0.77.5", "rcVersion": "0.78.0-rc.0", "scripts": { "prepare": "husky install", diff --git a/packages/pieces/community/google-sheets/package.json b/packages/pieces/community/google-sheets/package.json index e3351a885da..643766b77e0 100644 --- a/packages/pieces/community/google-sheets/package.json +++ b/packages/pieces/community/google-sheets/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-google-sheets", - "version": "0.14.0", + "version": "0.14.1", "dependencies": { "csv-parse": "5.6.0", "googleapis": "129.0.0", diff --git a/packages/pieces/community/google-sheets/src/index.ts b/packages/pieces/community/google-sheets/src/index.ts index 4ac67aa6674..9163940f46b 100644 --- a/packages/pieces/community/google-sheets/src/index.ts +++ b/packages/pieces/community/google-sheets/src/index.ts @@ -1,7 +1,5 @@ import { createCustomApiCallAction } from '@activepieces/pieces-common'; -import { - createPiece, -} from '@activepieces/pieces-framework'; +import { createPiece } from '@activepieces/pieces-framework'; import { PieceCategory } from '@activepieces/shared'; import { clearSheetAction } from './lib/actions/clear-sheet'; import { deleteRowAction } from './lib/actions/delete-row.action'; @@ -10,7 +8,12 @@ import { findRowsAction } from './lib/actions/find-rows'; import { getRowsAction } from './lib/actions/get-rows'; import { insertRowAction } from './lib/actions/insert-row.action'; import { updateRowAction } from './lib/actions/update-row'; -import { getAccessToken, googleSheetsAuth, GoogleSheetsAuthValue, googleSheetsCommon } from './lib/common/common'; +import { + getAccessToken, + googleSheetsAuth, + GoogleSheetsAuthValue, + googleSheetsCommon, +} from './lib/common/common'; import { newRowAddedTrigger } from './lib/triggers/new-row-added-webhook'; import { newOrUpdatedRowTrigger } from './lib/triggers/new-or-updated-row.trigger'; import { insertMultipleRowsAction } from './lib/actions/insert-multiple-rows.action'; @@ -25,55 +28,66 @@ import { updateMultipleRowsAction } from './lib/actions/update-multiple-rows'; import { createColumnAction } from './lib/actions/create-column'; import { exportSheetAction } from './lib/actions/export-sheet'; import { getManyRowsAction } from './lib/actions/get-many-rows'; +import { renameWorksheetAction } from './lib/actions/rename-worksheet'; +import { deleteWorksheetAction } from './lib/actions/delete-worksheet'; +import { formatRowAction } from './lib/actions/format-spreadsheet-row'; export const googleSheets = createPiece({ - minimumSupportedRelease: '0.71.4', - logoUrl: 'https://cdn.activepieces.com/pieces/google-sheets.png', - categories: [PieceCategory.PRODUCTIVITY], - authors: [ - 'ShayPunter', - 'Ozak93', - 'Abdallah-Alwarawreh', - 'Salem-Alaa', - 'kishanprmr', - 'MoShizzle', - 'AbdulTheActivePiecer', - 'khaledmashaly', - 'abuaboud', - 'geekyme', - ], - actions: [ - insertRowAction, - insertMultipleRowsAction, - deleteRowAction, - updateRowAction, - findRowsAction, - createSpreadsheetAction, - createWorksheetAction, - clearSheetAction, - findRowByNumAction, - getRowsAction, - getManyRowsAction, - findSpreadsheets, - findWorksheetAction, - copyWorksheetAction, - updateMultipleRowsAction, - createColumnAction, - exportSheetAction, - createCustomApiCallAction({ - auth: googleSheetsAuth, - baseUrl: () => { - return googleSheetsCommon.baseUrl; - }, - authMapping: async (auth) => { - return { - Authorization: `Bearer ${(await getAccessToken(auth as GoogleSheetsAuthValue))}`, - }; - }, - }), - ], - displayName: 'Google Sheets', - description: 'Create, edit, and collaborate on spreadsheets online', - triggers: [newOrUpdatedRowTrigger,newRowAddedTrigger,newSpreadsheetTrigger,newWorksheetTrigger], - auth: googleSheetsAuth, + minimumSupportedRelease: '0.71.4', + logoUrl: 'https://cdn.activepieces.com/pieces/google-sheets.png', + categories: [PieceCategory.PRODUCTIVITY], + authors: [ + 'ShayPunter', + 'Ozak93', + 'Abdallah-Alwarawreh', + 'Salem-Alaa', + 'kishanprmr', + 'MoShizzle', + 'AbdulTheActivePiecer', + 'khaledmashaly', + 'abuaboud', + 'geekyme', + ], + actions: [ + insertRowAction, + insertMultipleRowsAction, + updateRowAction, + updateMultipleRowsAction, + deleteRowAction, + findRowsAction, + createSpreadsheetAction, + createWorksheetAction, + clearSheetAction, + deleteWorksheetAction, + renameWorksheetAction, + formatRowAction, + findRowByNumAction, + getRowsAction, + getManyRowsAction, + findSpreadsheets, + findWorksheetAction, + copyWorksheetAction, + createColumnAction, + exportSheetAction, + createCustomApiCallAction({ + auth: googleSheetsAuth, + baseUrl: () => { + return googleSheetsCommon.baseUrl; + }, + authMapping: async (auth) => { + return { + Authorization: `Bearer ${await getAccessToken(auth as GoogleSheetsAuthValue)}`, + }; + }, + }), + ], + displayName: 'Google Sheets', + description: 'Create, edit, and collaborate on spreadsheets online', + triggers: [ + newOrUpdatedRowTrigger, + newRowAddedTrigger, + newSpreadsheetTrigger, + newWorksheetTrigger, + ], + auth: googleSheetsAuth, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts index 0a08b589ede..18eee3e0c51 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/clear-sheet.ts @@ -1,64 +1,60 @@ import { createAction, Property } from '@activepieces/pieces-framework'; import { areSheetIdsValid, googleSheetsCommon } from '../common/common'; import { googleSheetsAuth } from '../common/common'; -import { commonProps } from '../common/props'; +import { commonProps, isFirstRowHeaderProp } from '../common/props'; export const clearSheetAction = createAction({ - auth: googleSheetsAuth, - name: 'clear_sheet', - description: 'Clears all rows on an existing sheet', - displayName: 'Clear Sheet', - props: { - ...commonProps, - is_first_row_headers: Property.Checkbox({ - displayName: 'Is First row Headers?', - description: 'If the first row is headers', - required: true, - defaultValue: true, - }), - headerRow: Property.Number({ - displayName: 'Header Row', - description: 'Which row contains the headers?', - required: true, - defaultValue: 1, - }), - }, - async run({ propsValue, auth }) { - const { spreadsheetId, sheetId, is_first_row_headers:isFirstRowHeaders, headerRow } = propsValue; + auth: googleSheetsAuth, + name: 'clear_sheet', + description: 'Clears all rows on an existing sheet.', + displayName: 'Clear Sheet', + props: { + ...commonProps, + is_first_row_headers: isFirstRowHeaderProp(), + headerRow: Property.Number({ + displayName: 'Header Row Number', + description: 'Enter the row number where your column headers are located (usually row 1).', + required: true, + defaultValue: 1, + }), + }, + async run({ propsValue, auth }) { + const { + spreadsheetId, + sheetId, + is_first_row_headers: isFirstRowHeaders, + headerRow, + } = propsValue; - if (!areSheetIdsValid(spreadsheetId, sheetId)) { + if (!areSheetIdsValid(spreadsheetId, sheetId)) { throw new Error('Please select a spreadsheet and sheet first.'); } - await googleSheetsCommon.findSheetName( - auth, - spreadsheetId as string, - sheetId as number - ); + await googleSheetsCommon.findSheetName(auth, spreadsheetId as string, sheetId as number); - const rowsToDelete: number[] = []; - const values = await googleSheetsCommon.getGoogleSheetRows({ - spreadsheetId: spreadsheetId as string, - auth: auth, - sheetId: sheetId as number, - rowIndex_s: 1, - rowIndex_e: undefined, - headerRow: headerRow, - }); - for (const key in values) { - if (key === '0' && isFirstRowHeaders) { - continue; - } - rowsToDelete.push(parseInt(key) + 1); - } + const rowsToDelete: number[] = []; + const values = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId as string, + auth: auth, + sheetId: sheetId as number, + rowIndex_s: 1, + rowIndex_e: undefined, + headerRow: headerRow, + }); + for (const key in values) { + if (key === '0' && isFirstRowHeaders) { + continue; + } + rowsToDelete.push(parseInt(key) + 1); + } - const response = await googleSheetsCommon.clearSheet( - spreadsheetId as string, - sheetId as number, - auth, - isFirstRowHeaders ? 1 : 0, - rowsToDelete.length - ); + const response = await googleSheetsCommon.clearSheet( + spreadsheetId as string, + sheetId as number, + auth, + isFirstRowHeaders ? 1 : 0, + rowsToDelete.length, + ); - return response.body; - }, + return response.body; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts b/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts index 09c4df4f1e0..fa3a6f1a9ec 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/create-column.ts @@ -8,7 +8,6 @@ import { ValueInputOption, } from '../common/common'; import { google } from 'googleapis'; -import { OAuth2Client } from 'googleapis-common'; import { getWorkSheetName } from '../triggers/helpers'; import { commonProps } from '../common/props'; @@ -16,7 +15,7 @@ export const createColumnAction = createAction({ auth: googleSheetsAuth, name: 'create-column', displayName: 'Create Spreadsheet Column', - description: 'Adds a new column to a spreadsheet.', + description: 'Creates a new column in a specific spreadsheet.', props: { ...commonProps, columnName: Property.ShortText({ @@ -52,7 +51,7 @@ export const createColumnAction = createAction({ range: { sheetId, dimension: 'COLUMNS', - startIndex: columnIndex -1, + startIndex: columnIndex - 1, endIndex: columnIndex, }, }, @@ -60,11 +59,11 @@ export const createColumnAction = createAction({ ], }, }); - columnLabel = columnToLabel(columnIndex-1); + columnLabel = columnToLabel(columnIndex - 1); } else { const headers = await getHeaderRow({ - spreadsheetId:spreadsheetId as string, - sheetId :sheetId as number, + spreadsheetId: spreadsheetId as string, + sheetId: sheetId as number, auth: context.auth, }); @@ -90,7 +89,11 @@ export const createColumnAction = createAction({ columnLabel = columnToLabel(newColumnIndex); } - const sheetName = await getWorkSheetName(context.auth, spreadsheetId as string , sheetId as number); + const sheetName = await getWorkSheetName( + context.auth, + spreadsheetId as string, + sheetId as number, + ); const response = await sheets.spreadsheets.values.update({ range: `${sheetName}!${columnLabel}1`, diff --git a/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts index 3c5e4f010af..16f4f8adfbd 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/create-worksheet.ts @@ -8,7 +8,7 @@ export const createWorksheetAction = createAction({ auth: googleSheetsAuth, name: 'create-worksheet', displayName: 'Create Worksheet', - description:'Create a blank worksheet with a title.', + description:'Create a new blank worksheet with a title.', props: { includeTeamDrives: includeTeamDrivesProp(), spreadsheetId: spreadsheetIdProp('Spreadsheet',''), diff --git a/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts index 8f25521303c..10deffaa98e 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/delete-row.action.ts @@ -6,13 +6,13 @@ import { commonProps } from '../common/props'; export const deleteRowAction = createAction({ auth: googleSheetsAuth, name: 'delete_row', - description: 'Delete a row on an existing sheet you have access to', + description: 'Delete a specific row from the selected sheet.', displayName: 'Delete Row', props: { ...commonProps, rowId: Property.Number({ displayName: 'Row Number', - description: 'The row number to remove', + description: 'The number of the row you want to delete.', required: true, }), }, diff --git a/packages/pieces/community/google-sheets/src/lib/actions/delete-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/delete-worksheet.ts new file mode 100644 index 00000000000..f99817a6970 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/delete-worksheet.ts @@ -0,0 +1,36 @@ +import { googleSheetsAuth } from '../common/common'; +import { createAction } from '@activepieces/pieces-framework'; +import { includeTeamDrivesProp, sheetIdProp, spreadsheetIdProp } from '../common/props'; +import { google } from 'googleapis'; +import { createGoogleClient } from '../common/common'; + +export const deleteWorksheetAction = createAction({ + auth: googleSheetsAuth, + name: 'delete-worksheet', + displayName: 'Delete Worksheet', + description: 'Permanently delete a specific worksheet.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'), + sheetId: sheetIdProp('Worksheet', 'The ID of the worksheet to delete.'), + }, + async run(context) { + const authClient = await createGoogleClient(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: context.propsValue.spreadsheetId, + requestBody: { + requests:[ + { + deleteSheet:{ + sheetId:context.propsValue.sheetId + } + } + ] + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts index 338c4b07abb..d25755fa677 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/export-sheet.ts @@ -10,14 +10,14 @@ import { areSheetIdsValid, getAccessToken } from '../common/common'; export const exportSheetAction = createAction({ name: 'export_sheet', - displayName: 'Export Sheet', - description: 'Export a Google Sheets tab to CSV or TSV format.', + displayName: 'Export Worksheet', + description: 'Download a worksheet as a CSV or TSV file.', auth: googleSheetsAuth, props: { ...commonProps, format: Property.StaticDropdown({ displayName: 'Export Format', - description: 'The format to export the sheet to.', + description: 'Select the file type to export the sheet as.', required: true, defaultValue: 'csv', options: { diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts index c8c59e2efde..1e21f887ce7 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-row-by-num.ts @@ -4,39 +4,39 @@ import { googleSheetsAuth } from '../common/common'; import { commonProps } from '../common/props'; export const findRowByNumAction = createAction({ - auth: googleSheetsAuth, - name: 'find_row_by_num', - description: 'Get a row in a Google Sheet by row number', - displayName: 'Get Row', - props: { - ...commonProps, - rowNumber: Property.Number({ - displayName: 'Row Number', - description: 'The row number to get from the sheet', - required: true, - }), - headerRow: Property.Number({ - displayName: 'Header Row', - description: 'Which row contains the headers?', - required: true, - defaultValue: 1, - }), - }, - async run(context) { - const {spreadsheetId,sheetId,rowNumber,headerRow} = context.propsValue; + auth: googleSheetsAuth, + name: 'find_row_by_num', + displayName: 'Get Single Row by ID', + description: 'Retrieve a specific row using its unique ID.', + props: { + ...commonProps, + rowNumber: Property.Number({ + displayName: 'Row Number', + description: 'Enter the row number you want to retrieve', + required: true, + }), + headerRow: Property.Number({ + displayName: 'Header Row Number', + description: 'Enter the row number where your column headers are located (usually row 1).', + required: true, + defaultValue: 1, + }), + }, + async run(context) { + const { spreadsheetId, sheetId, rowNumber, headerRow } = context.propsValue; - if (!areSheetIdsValid(spreadsheetId,sheetId)) { + if (!areSheetIdsValid(spreadsheetId, sheetId)) { throw new Error('Please select a spreadsheet and sheet first.'); } - const row = await googleSheetsCommon.getGoogleSheetRows({ - auth: context.auth, - sheetId: sheetId as number, - spreadsheetId: spreadsheetId as string, - rowIndex_s: rowNumber, - rowIndex_e: rowNumber, - headerRow: headerRow, - }); - return row[0]; - }, + const row = await googleSheetsCommon.getGoogleSheetRows({ + auth: context.auth, + sheetId: sheetId as number, + spreadsheetId: spreadsheetId as string, + rowIndex_s: rowNumber, + rowIndex_e: rowNumber, + headerRow: headerRow, + }); + return row[0]; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts index 715785fcc47..aa06b80cb95 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-rows.ts @@ -1,12 +1,9 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; import { - createAction, - Property, -} from '@activepieces/pieces-framework'; -import { - areSheetIdsValid, - googleSheetsCommon, - labelToColumn, - mapRowsToHeaderNames + areSheetIdsValid, + googleSheetsCommon, + labelToColumn, + mapRowsToHeaderNames, } from '../common/common'; import { googleSheetsAuth } from '../common/common'; import { z } from 'zod'; @@ -14,129 +11,125 @@ import { propsValidation } from '@activepieces/pieces-common'; import { columnNameProp, commonProps } from '../common/props'; export const findRowsAction = createAction({ - auth: googleSheetsAuth, - name: 'find_rows', - description: - 'Find or get rows in a Google Sheet by column name and search value', - displayName: 'Find Rows', - props: { - ...commonProps, - columnName: columnNameProp(), - searchValue: Property.ShortText({ - displayName: 'Search Value', - description: - 'The value to search for in the specified column. If left empty, all rows will be returned.', - required: false, - }), - matchCase: Property.Checkbox({ - displayName: 'Exact match', - description: - 'Whether to choose the rows with exact match or choose the rows that contain the search value', - required: true, - defaultValue: false, - }), - startingRow: Property.Number({ - displayName: 'Starting Row', - description: 'The row number to start searching from', - required: false, - }), - numberOfRows: Property.Number({ - displayName: 'Number of Rows', - description: - 'The number of rows to return ( the default is 1 if not specified )', - required: false, - defaultValue: 1, - }), - headerRow: Property.Number({ - displayName: 'Header Row', - description: 'Which row contains the headers?', - required: true, - defaultValue: 1, - }), - useHeaderNames: Property.Checkbox({ - displayName: 'Use header names for keys', - description: 'Map A/B/C… to the actual column headers (row specified above).', - required: false, - defaultValue: false, - }), - }, - async run({ propsValue, auth }) { - await propsValidation.validateZod(propsValue, { - startingRow: z.number().min(1).optional(), - numberOfRows: z.number().min(1).optional(), - }); + auth: googleSheetsAuth, + name: 'find_rows', +description: 'Look up rows in a worksheet based on a column value.', + displayName: 'Find Rows', + props: { + ...commonProps, + columnName: columnNameProp(), + searchValue: Property.ShortText({ + displayName: 'Search Value', + description: 'The value to look for in the selected column. Leave empty to return all rows.', + required: false, + }), + matchCase: Property.Checkbox({ + displayName: 'Exact Match', + description: 'Only return rows where the cell value exactly matches the search value.', + required: true, + defaultValue: false, + }), + startingRow: Property.Number({ + displayName: 'Starting Row', + description: 'Start searching from this row number.', + required: false, + }), + numberOfRows: Property.Number({ + displayName: 'Number of Rows', + description: 'How many rows to return. Defaults to 1 if not specified.', + required: false, + defaultValue: 1, + }), + headerRow: Property.Number({ + displayName: 'Header Row', + description: 'The row number that contains the column names.', + required: true, + defaultValue: 1, + }), + useHeaderNames: Property.Checkbox({ + displayName: 'Use Column Names', + description: 'Use column names as keys instead of A, B, C.', + required: false, + defaultValue: false, + }), + }, + async run({ propsValue, auth }) { + await propsValidation.validateZod(propsValue, { + startingRow: z.number().min(1).optional(), + numberOfRows: z.number().min(1).optional(), + }); - const spreadsheetId = propsValue.spreadsheetId; - const sheetId = propsValue.sheetId; - const startingRow = propsValue.startingRow ?? 1; - const numberOfRowsToReturn = propsValue.numberOfRows ?? 1; - const headerRow = propsValue.headerRow; - const useHeaderNames = propsValue.useHeaderNames as boolean; + const spreadsheetId = propsValue.spreadsheetId; + const sheetId = propsValue.sheetId; + const startingRow = propsValue.startingRow ?? 1; + const numberOfRowsToReturn = propsValue.numberOfRows ?? 1; + const headerRow = propsValue.headerRow; + const useHeaderNames = propsValue.useHeaderNames as boolean; - if (!areSheetIdsValid(spreadsheetId,sheetId)) { + if (!areSheetIdsValid(spreadsheetId, sheetId)) { throw new Error('Please select a spreadsheet and sheet first.'); } - const rows = await googleSheetsCommon.getGoogleSheetRows({ - spreadsheetId: spreadsheetId as string, - auth: auth, - sheetId: sheetId as number, - rowIndex_s: startingRow, - rowIndex_e: undefined, - headerRow: headerRow, - }); + const rows = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheetId as string, + auth: auth, + sheetId: sheetId as number, + rowIndex_s: startingRow, + rowIndex_e: undefined, + headerRow: headerRow, + }); + + const values = rows.map((row) => { + return row.values; + }); - const values = rows.map((row) => { - return row.values; - }); + const matchingRows: any[] = []; + const columnName = propsValue.columnName ? propsValue.columnName : 'A'; + const columnNumber: number = labelToColumn(columnName); + const searchValue = propsValue.searchValue ?? ''; - const matchingRows: any[] = []; - const columnName = propsValue.columnName ? propsValue.columnName : 'A'; - const columnNumber:number = labelToColumn(columnName); - const searchValue = propsValue.searchValue ?? ''; + let matchedRowCount = 0; - let matchedRowCount = 0; + for (let i = 0; i < values.length; i++) { + const row: Record = values[i]; - for (let i = 0; i < values.length; i++) { - const row:Record = values[i]; + if (matchedRowCount === numberOfRowsToReturn) break; - if (matchedRowCount === numberOfRowsToReturn) break; + if (searchValue === '') { + matchingRows.push(rows[i]); + matchedRowCount += 1; + continue; + } - if (searchValue === '') { - matchingRows.push(rows[i]); - matchedRowCount += 1; - continue; - } + const keys = Object.keys(row); + if (keys.length <= columnNumber) continue; + const entry_value = row[keys[columnNumber]]; - const keys = Object.keys(row); - if (keys.length <= columnNumber) continue; - const entry_value = row[keys[columnNumber]]; + if (entry_value === undefined) { + continue; + } + if (propsValue.matchCase) { + if (entry_value === searchValue) { + matchedRowCount += 1; + matchingRows.push(rows[i]); + } + } else { + if (entry_value.toLowerCase().includes(searchValue.toLowerCase())) { + matchedRowCount += 1; + matchingRows.push(rows[i]); + } + } + } - if (entry_value === undefined) { - continue; - } - if (propsValue.matchCase) { - if (entry_value === searchValue) { - matchedRowCount += 1; - matchingRows.push(rows[i]); - } - } else { - if (entry_value.toLowerCase().includes(searchValue.toLowerCase())) { - matchedRowCount += 1; - matchingRows.push(rows[i]); - } - } - } + const finalRows = await mapRowsToHeaderNames( + matchingRows, + useHeaderNames, + spreadsheetId as string, + sheetId as number, + headerRow, + auth, + ); - const finalRows = await mapRowsToHeaderNames( - matchingRows, - useHeaderNames, - spreadsheetId as string, - sheetId as number, - headerRow, - auth, - ); - - return finalRows; - }, + return finalRows; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts index 6755c20d06f..8ed5bba5810 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-spreadsheets.ts @@ -1,83 +1,83 @@ import { createAction, Property } from '@activepieces/pieces-framework'; -import { httpClient, HttpMethod, AuthenticationType, HttpRequest } from '@activepieces/pieces-common'; +import { + httpClient, + HttpMethod, + AuthenticationType, + HttpRequest, +} from '@activepieces/pieces-common'; import { googleSheetsAuth } from '../common/common'; import { includeTeamDrivesProp } from '../common/props'; import { getAccessToken } from '../common/common'; export const findSpreadsheets = createAction({ - name: 'find_spreadsheets', - displayName: 'Find Spreadsheet(s)', - description: 'Find spreadsheet(s) by name.', - auth: googleSheetsAuth, - props: { - includeTeamDrives: includeTeamDrivesProp(), - spreadsheet_name: Property.ShortText({ - displayName: 'Spreadsheet Name', - description: 'The name of the spreadsheet(s) to find.', - required: true, - }), - exact_match: Property.Checkbox({ - displayName: 'Exact Match', - description: 'If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.', - required: false, - defaultValue: false, - }), - }, - async run({ propsValue, auth }) { - const searchValue = propsValue.spreadsheet_name; - const queries = [ - "mimeType='application/vnd.google-apps.spreadsheet'", - 'trashed=false', - ]; + name: 'find_spreadsheets', + displayName: 'Find Spreadsheet(s)', + description: 'Find spreadsheet(s) by name.', + auth: googleSheetsAuth, + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheet_name: Property.ShortText({ + displayName: 'Spreadsheet Name', + description: 'Enter the name of the spreadsheet to search for', + required: true, + }), + exact_match: Property.Checkbox({ + displayName: 'Exact Match', + description: + 'If true, only return spreadsheets that exactly match the name. If false, return spreadsheets that contain the name.', + required: false, + defaultValue: false, + }), + }, + async run({ propsValue, auth }) { + const searchValue = propsValue.spreadsheet_name; + const queries = ["mimeType='application/vnd.google-apps.spreadsheet'", 'trashed=false']; - if (propsValue.exact_match) { - queries.push(`name = '${searchValue}'`); - } else { - queries.push(`name contains '${searchValue}'`); - } + if (propsValue.exact_match) { + queries.push(`name = '${searchValue}'`); + } else { + queries.push(`name contains '${searchValue}'`); + } - const files = []; - let pageToken = null; + const files = []; + let pageToken = null; - do - { - const request :HttpRequest = { - method:HttpMethod.GET, - url: 'https://www.googleapis.com/drive/v3/files', - queryParams:{ - q: queries.join(' and '), - includeItemsFromAllDrives: propsValue.includeTeamDrives ? 'true' : 'false', - supportsAllDrives: 'true', - fields: 'files(id,name,webViewLink,createdTime,modifiedTime),nextPageToken', - }, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: await getAccessToken(auth), - }, + do { + const request: HttpRequest = { + method: HttpMethod.GET, + url: 'https://www.googleapis.com/drive/v3/files', + queryParams: { + q: queries.join(' and '), + includeItemsFromAllDrives: propsValue.includeTeamDrives ? 'true' : 'false', + supportsAllDrives: 'true', + fields: 'files(id,name,webViewLink,createdTime,modifiedTime),nextPageToken', + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: await getAccessToken(auth), + }, + }; + if (pageToken) { + if (request.queryParams !== undefined) { + request.queryParams['pageToken'] = pageToken; + } + } + try { + const response = await httpClient.sendRequest<{ + files: { id: string; name: string }[]; + nextPageToken: string; + }>(request); - } - if (pageToken) { - if (request.queryParams !== undefined) { - request.queryParams['pageToken'] = pageToken; - } - } - try { - const response = await httpClient.sendRequest<{ - files: { id: string; name: string }[]; - nextPageToken: string; - }>(request); + files.push(...response.body.files); + pageToken = response.body.nextPageToken; + } catch (e) { + throw new Error(`Failed to get folders\nError:${e}`); + } + } while (pageToken); - files.push(...response.body.files); - pageToken = response.body.nextPageToken; - } catch (e) { - throw new Error(`Failed to get folders\nError:${e}`); - } - - }while(pageToken); - - return { - found: files.length > 0, - spreadsheets:files, - }; - }, + return { + found: files.length > 0, + spreadsheets: files, + }; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts index cca93444b0d..f46b01636ab 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/find-worksheet.ts @@ -1,52 +1,52 @@ import { googleSheetsAuth } from '../common/common'; import { createAction, Property } from '@activepieces/pieces-framework'; import { google } from 'googleapis'; -import { OAuth2Client } from 'googleapis-common'; import { includeTeamDrivesProp, spreadsheetIdProp } from '../common/props'; import { createGoogleClient } from '../common/common'; export const findWorksheetAction = createAction({ - auth: googleSheetsAuth, - name: 'find-worksheet', - displayName: 'Find Worksheet(s)', - description: 'Finds a worksheet(s) by title.', - props: { - includeTeamDrives: includeTeamDrivesProp(), - spreadsheetId:spreadsheetIdProp('Spreadsheet',''), - title: Property.ShortText({ - displayName: 'Title', - required: true, - }), - exact_match: Property.Checkbox({ - displayName: 'Exact Match', - description: 'If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.', - required: false, - defaultValue: false, - }), - }, - async run(context) { - const spreadsheetId = context.propsValue.spreadsheetId; - const title = context.propsValue.title; - const exactMatch = context.propsValue.exact_match ?? false; + auth: googleSheetsAuth, + name: 'find-worksheet', + displayName: 'Find Worksheet(s)', + description: 'Finds a worksheet(s) by title.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet', ''), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + exact_match: Property.Checkbox({ + displayName: 'Exact Match', + description: + 'If true, only return worksheets that exactly match the name. If false, return worksheets that contain the name.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const spreadsheetId = context.propsValue.spreadsheetId; + const title = context.propsValue.title; + const exactMatch = context.propsValue.exact_match ?? false; - const authClient = await createGoogleClient(context.auth); + const authClient = await createGoogleClient(context.auth); - const sheets = google.sheets({ version: 'v4', auth: authClient }); + const sheets = google.sheets({ version: 'v4', auth: authClient }); - const response = await sheets.spreadsheets.get({ - spreadsheetId, - }); + const response = await sheets.spreadsheets.get({ + spreadsheetId, + }); - const sheetsData = response.data.sheets ?? []; + const sheetsData = response.data.sheets ?? []; - const matchedSheets = sheetsData.filter((sheet) => { - const sheetTitle = sheet.properties?.title ?? ""; - return exactMatch ? sheetTitle === title : sheetTitle.includes(title); - }); + const matchedSheets = sheetsData.filter((sheet) => { + const sheetTitle = sheet.properties?.title ?? ''; + return exactMatch ? sheetTitle === title : sheetTitle.includes(title); + }); - return { - found: matchedSheets.length > 0, - worksheets: matchedSheets , - }; - }, + return { + found: matchedSheets.length > 0, + worksheets: matchedSheets, + }; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/format-spreadsheet-row.ts b/packages/pieces/community/google-sheets/src/lib/actions/format-spreadsheet-row.ts new file mode 100644 index 00000000000..b5ca60a1643 --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/format-spreadsheet-row.ts @@ -0,0 +1,112 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { areSheetIdsValid, createGoogleClient } from '../common/common'; +import { googleSheetsAuth } from '../common/common'; +import { commonProps } from '../common/props'; +import { google } from 'googleapis'; +import { isNil } from '@activepieces/shared'; + +export const formatRowAction = createAction({ + auth: googleSheetsAuth, + name: 'format-row', + description: 'Format one or multiple rows in specific spreadsheet.', + displayName: 'Format Row(s)', + props: { + ...commonProps, + startingRow: Property.Number({ + displayName: 'Starting row', + description: 'The first row number where formatting should begin.', + required: true, + }), + endingRow: Property.Number({ + displayName: 'Ending row', + description: + 'The last row number where formatting should stop (leave empty to format only the starting row).', + required: false, + }), + bgColor: Property.ShortText({ + displayName: 'Background Color', + description: 'Provide a HEX color code (example: #FFD966)', + required: false, + }), + textColor: Property.ShortText({ + displayName: 'Text Color', + description: 'Provide a HEX color code (example: #FFD966)', + required: false, + }), + bold: Property.Checkbox({ + displayName: 'Make text bold', + required: false, + }), + italic: Property.Checkbox({ + displayName: 'Make text Italic', + required: false, + }), + strikethrough: Property.Checkbox({ + displayName: 'Make text Strikethrough', + required: false, + }), + }, + async run(context) { + const { spreadsheetId, sheetId, startingRow,endingRow, bgColor, textColor, italic, bold, strikethrough } = + context.propsValue; + + if (!areSheetIdsValid(spreadsheetId, sheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const authClient = await createGoogleClient(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: context.propsValue.spreadsheetId, + requestBody: { + requests: [ + { + repeatCell: { + range: { + sheetId, + startRowIndex: startingRow - 1, + endRowIndex: endingRow ? endingRow : startingRow, + }, + cell: { + userEnteredFormat: { + backgroundColor: hexToRgb(bgColor), + textFormat: { + bold, + italic, + strikethrough, + foregroundColor: hexToRgb(textColor), + }, + }, + }, + fields: 'userEnteredFormat(backgroundColor,textFormat)', + }, + }, + ], + }, + }); + + return { + success: true, + ...response.data, + }; + }, +}); + +function hexToRgb(hex?: string) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + if (isNil(hex)) return undefined; + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function (m, r, g, b) { + return r + r + g + g + b + b; + }); + + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? { + red: parseInt(result[1], 16), + green: parseInt(result[2], 16), + blue: parseInt(result[3], 16), + } + : undefined; +} diff --git a/packages/pieces/community/google-sheets/src/lib/actions/get-many-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/get-many-rows.ts index f9945e54854..5df74affa6c 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/get-many-rows.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/get-many-rows.ts @@ -1,19 +1,15 @@ -import { createAction, Property } from "@activepieces/pieces-framework"; +import { createAction } from "@activepieces/pieces-framework"; import { areSheetIdsValid, googleSheetsAuth, googleSheetsCommon, mapRowsToHeaderNames } from "../common/common"; -import { commonProps } from "../common/props"; +import { commonProps, isFirstRowHeaderProp } from "../common/props"; export const getManyRowsAction = createAction({ name: 'get-many-rows', auth: googleSheetsAuth, - displayName: 'Get Many Rows', - description: 'Get all values from the selected sheet.', + displayName: 'Get All Rows', + description: 'Get all the rows from a specific sheet.', props: { ...commonProps, - first_row_headers: Property.Checkbox({ - displayName: 'Does the first row contain headers?', - required: true, - defaultValue: false, - }), + first_row_headers: isFirstRowHeaderProp() }, async run(context) { const {first_row_headers,sheetId,spreadsheetId} = context.propsValue; diff --git a/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts index 137f44baf65..41e67639887 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/get-rows.ts @@ -105,7 +105,7 @@ const notes = ` export const getRowsAction = createAction({ auth: googleSheetsAuth, name: 'get_next_rows', - description: 'Get next group of rows from a Google Sheet', + description: 'Get next group of rows from a specifiec workheet', displayName: 'Get next row(s)', props: { ...commonProps, diff --git a/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts index 8eed8e904c5..2f5ebfcec31 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/insert-multiple-rows.action.ts @@ -6,26 +6,34 @@ import { OAuth2PropertyValue, Property, } from '@activepieces/pieces-framework'; -import { Dimension, googleSheetsCommon, objectToArray, ValueInputOption,columnToLabel, areSheetIdsValid, GoogleSheetsAuthValue, createGoogleClient } from '../common/common'; +import { + Dimension, + googleSheetsCommon, + objectToArray, + ValueInputOption, + columnToLabel, + areSheetIdsValid, + GoogleSheetsAuthValue, + createGoogleClient, +} from '../common/common'; import { getWorkSheetName, getWorkSheetGridSize } from '../triggers/helpers'; import { google, sheets_v4 } from 'googleapis'; import { MarkdownVariant } from '@activepieces/shared'; -import {parse} from 'csv-parse/sync'; +import { parse } from 'csv-parse/sync'; import { commonProps } from '../common/props'; - -type RowValueType = Record +type RowValueType = Record; export const insertMultipleRowsAction = createAction({ auth: googleSheetsAuth, name: 'google-sheets-insert-multiple-rows', - displayName: 'Insert Multiple Rows', - description: 'Add one or more new rows in a specific spreadsheet.', + displayName: 'Add Multiple Rows', + description: 'Add multiple rows of data at once to a specific spreadsheet.', props: { ...commonProps, input_type: Property.StaticDropdown({ - displayName: 'Rows Input Format', - description: 'Select the format of the input values to be inserted into the sheet.', + displayName: 'Rows Data Format', + description: 'Select the format of the input values to be added into the worksheet.', required: true, defaultValue: 'column_names', options: { @@ -49,7 +57,7 @@ export const insertMultipleRowsAction = createAction({ values: Property.DynamicProperties({ auth: googleSheetsAuth, displayName: 'Values', - description: 'The values to insert.', + description: 'The values to add.', required: true, refreshers: ['sheetId', 'spreadsheetId', 'input_type', 'headerRow'], props: async ({ auth, sheetId, spreadsheetId, input_type, headerRow }) => { @@ -99,15 +107,15 @@ export const insertMultipleRowsAction = createAction({ ], }); break; - case 'column_names': { - const headers = await googleSheetsCommon.getGoogleSheetRows({ - spreadsheetId: spreadsheet_id, - auth: auth, - sheetId: sheet_id, - rowIndex_s: 1, - rowIndex_e: 1, - headerRow: (headerRow as unknown as number) || 1, - }); + case 'column_names': { + const headers = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheet_id, + auth: auth, + sheetId: sheet_id, + rowIndex_s: 1, + rowIndex_e: 1, + headerRow: (headerRow as unknown as number) || 1, + }); const firstRow = headers[0].values ?? {}; //check for empty headers @@ -124,7 +132,7 @@ export const insertMultipleRowsAction = createAction({ for (const key in firstRow) { columns[key] = Property.ShortText({ displayName: firstRow[key], - description: firstRow[key], + // description: firstRow[key], required: false, defaultValue: '', }); @@ -194,7 +202,10 @@ export const insertMultipleRowsAction = createAction({ } else { const headers: DropdownOption[] = []; for (const key in firstRow) { - headers.push({ label: firstRow[key].toString(), value: key.toString() }); + headers.push({ + label: firstRow[key].toString(), + value: key.toString(), + }); } fields['column_name'] = Property.StaticDropdown({ @@ -220,8 +231,8 @@ export const insertMultipleRowsAction = createAction({ required: false, }), headerRow: Property.Number({ - displayName: 'Header Row', - description: 'Which row contains the headers?', + displayName: 'Header Row Number', + description: 'Enter the row number where your column headers are located (usually row 1).', required: true, defaultValue: 1, }), @@ -229,8 +240,8 @@ export const insertMultipleRowsAction = createAction({ async run(context) { const { - spreadsheetId:inputSpreadsheetId, - sheetId:inputSheetId, + spreadsheetId: inputSpreadsheetId, + sheetId: inputSheetId, input_type: valuesInputType, overwrite: overwriteValues, check_for_duplicate: checkForDuplicateValues, @@ -263,15 +274,28 @@ export const insertMultipleRowsAction = createAction({ const authClient = await createGoogleClient(context.auth); const sheets = google.sheets({ version: 'v4', auth: authClient }); - const formattedValues = await formatInputRows(sheets,spreadsheetId, sheetName,valuesInputType, rowValuesInput, sheetHeaders); + const formattedValues = await formatInputRows( + sheets, + spreadsheetId, + sheetName, + valuesInputType, + rowValuesInput, + sheetHeaders, + ); const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED; - if (overwriteValues) { const sheetGridRange = await getWorkSheetGridSize(context.auth, spreadsheetId, sheetId); const existingGridRowCount = sheetGridRange.rowCount ?? 0; - return handleOverwrite(sheets, spreadsheetId, sheetName, formattedValues, existingGridRowCount, valueInputOption); + return handleOverwrite( + sheets, + spreadsheetId, + sheetName, + formattedValues, + existingGridRowCount, + valueInputOption, + ); } if (checkForDuplicateValues) { @@ -290,7 +314,7 @@ export const insertMultipleRowsAction = createAction({ formattedValues, existingSheetValues, duplicateColumn, - valueInputOption + valueInputOption, ); } @@ -304,7 +328,7 @@ async function handleOverwrite( sheetName: string, formattedValues: any[], existingGridRowCount: number, - valueInputOption: ValueInputOption + valueInputOption: ValueInputOption, ) { const existingRowCount = existingGridRowCount; const inputRowCount = formattedValues.length; @@ -312,18 +336,20 @@ async function handleOverwrite( const updateResponse = await sheets.spreadsheets.values.batchUpdate({ spreadsheetId: spreadSheetId, requestBody: { - data: [{ - range: `${sheetName}!A2:ZZZ${inputRowCount + 1}`, - majorDimension: Dimension.ROWS, - values: formattedValues.map(row => objectToArray(row)), - }], - valueInputOption + data: [ + { + range: `${sheetName}!A2:ZZZ${inputRowCount + 1}`, + majorDimension: Dimension.ROWS, + values: formattedValues.map((row) => objectToArray(row)), + }, + ], + valueInputOption, }, }); // Determine if clearing rows is necessary and within grid size - const clearStartRow = inputRowCount + 2; // Start clearing after the last input row - const clearEndRow = Math.max(clearStartRow, existingRowCount); + const clearStartRow = inputRowCount + 2; // Start clearing after the last input row + const clearEndRow = Math.max(clearStartRow, existingRowCount); if (clearStartRow <= existingGridRowCount) { const boundedClearEndRow = Math.min(clearEndRow, existingGridRowCount); @@ -340,8 +366,6 @@ async function handleOverwrite( }; } return updateResponse.data; - - } async function handleDuplicates( @@ -351,18 +375,19 @@ async function handleDuplicates( formattedInputRows: any[], existingSheetValues: any[], duplicateColumn: string, - valueInputOption: ValueInputOption + valueInputOption: ValueInputOption, ) { - const uniqueValues = formattedInputRows.filter( - (inputRow) => !existingSheetValues.some( - (existingRow) => { + (inputRow) => + !existingSheetValues.some((existingRow) => { const existingValue = existingRow?.values?.[duplicateColumn]; const inputValue = inputRow?.[duplicateColumn]; - return existingValue != null && inputValue != null && - String(existingValue).toLowerCase().trim() === String(inputValue).toLowerCase().trim(); - } - ) + return ( + existingValue != null && + inputValue != null && + String(existingValue).toLowerCase().trim() === String(inputValue).toLowerCase().trim() + ); + }), ); const response = await sheets.spreadsheets.values.append({ @@ -383,14 +408,14 @@ async function normalInsert( spreadSheetId: string, sheetName: string, formattedValues: any[], - valueInputOption: ValueInputOption + valueInputOption: ValueInputOption, ) { const response = await sheets.spreadsheets.values.append({ range: sheetName + '!A:A', spreadsheetId: spreadSheetId, valueInputOption, requestBody: { - values: formattedValues.map(row => objectToArray(row)), + values: formattedValues.map((row) => objectToArray(row)), majorDimension: Dimension.ROWS, }, }); @@ -403,7 +428,7 @@ async function formatInputRows( sheetName: string, valuesInputType: string, rowValuesInput: any, - sheetHeaders: RowValueType + sheetHeaders: RowValueType, ): Promise { let formattedInputRows: any[] = []; @@ -412,7 +437,13 @@ async function formatInputRows( formattedInputRows = convertCsvToRawValues(rowValuesInput as string, ',', sheetHeaders); break; case 'json': - formattedInputRows = await convertJsonToRawValues(sheets,spreadSheetId, sheetName, rowValuesInput as string, sheetHeaders); + formattedInputRows = await convertJsonToRawValues( + sheets, + spreadSheetId, + sheetName, + rowValuesInput as string, + sheetHeaders, + ); break; case 'column_names': formattedInputRows = rowValuesInput as RowValueType[]; @@ -427,9 +458,8 @@ async function convertJsonToRawValues( spreadSheetId: string, sheetName: string, json: string | Record[], - labelHeaders: RowValueType + labelHeaders: RowValueType, ): Promise { - let data: RowValueType[]; // If the input is a JSON string @@ -450,15 +480,15 @@ async function convertJsonToRawValues( } // Collect all possible headers from the data - const allHeaders = new Set(); - data.forEach((row) => { - Object.keys(row).forEach((key) => allHeaders.add(key)); - }); + const allHeaders = new Set(); + data.forEach((row) => { + Object.keys(row).forEach((key) => allHeaders.add(key)); + }); // Identify headers not present in labelHeaders - const additionalHeaders = Array.from(allHeaders).filter( - (header) => !Object.values(labelHeaders).includes(header) - ); + const additionalHeaders = Array.from(allHeaders).filter( + (header) => !Object.values(labelHeaders).includes(header), + ); //add missing headers to labelHeaders additionalHeaders.forEach((header) => { @@ -468,47 +498,45 @@ async function convertJsonToRawValues( // update sheets with new headers if (additionalHeaders.length > 0) { await sheets.spreadsheets.values.update({ - range: `${sheetName}!A1:ZZZ1`, - spreadsheetId: spreadSheetId, - valueInputOption: ValueInputOption.USER_ENTERED, - requestBody: { - majorDimension:Dimension.ROWS, - values: [objectToArray(labelHeaders)] - } + range: `${sheetName}!A1:ZZZ1`, + spreadsheetId: spreadSheetId, + valueInputOption: ValueInputOption.USER_ENTERED, + requestBody: { + majorDimension: Dimension.ROWS, + values: [objectToArray(labelHeaders)], + }, }); - } + } return data.map((row: RowValueType) => { return Object.entries(labelHeaders).reduce((acc, [labelColumn, csvHeader]) => { - acc[labelColumn] = row[csvHeader] ?? ""; + acc[labelColumn] = row[csvHeader] ?? ''; return acc; }, {} as RowValueType); - - }) + }); } -function convertCsvToRawValues( - csvText: string, - delimiter: string, - labelHeaders: RowValueType, -) { +function convertCsvToRawValues(csvText: string, delimiter: string, labelHeaders: RowValueType) { // Split CSV into rows - const rows:Record[] = parse(csvText,{delimiter: delimiter, columns: true}); + const rows: Record[] = parse(csvText, { + delimiter: delimiter, + columns: true, + }); - const result = rows.map((row)=>{ + const result = rows.map((row) => { // Normalize record keys to lowercase const normalizedRow = Object.keys(row).reduce((acc, key) => { acc[key.toLowerCase().trim()] = row[key]; - return acc; - },{} as Record); + return acc; + }, {} as Record); - const transformedRow :Record= {}; - for(const key in labelHeaders){ + const transformedRow: Record = {}; + for (const key in labelHeaders) { // Match labels to normalized keys const normalizedKey = labelHeaders[key].toLowerCase(); transformedRow[key] = normalizedRow[normalizedKey] || ''; } return transformedRow; - }) + }); return result; } diff --git a/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts b/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts index e627d580a55..f4df8b37591 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/insert-row.action.ts @@ -1,107 +1,111 @@ import { createAction, Property } from '@activepieces/pieces-framework'; import { - areSheetIdsValid, - Dimension, - getAccessToken, - GoogleSheetsAuthValue, - googleSheetsCommon, - objectToArray, - stringifyArray, - ValueInputOption, + areSheetIdsValid, + Dimension, + getAccessToken, + GoogleSheetsAuthValue, + googleSheetsCommon, + objectToArray, + stringifyArray, + ValueInputOption, } from '../common/common'; import { googleSheetsAuth } from '../common/common'; import { isNil } from '@activepieces/shared'; -import { AuthenticationType, httpClient, HttpMethod, HttpRequest } from '@activepieces/pieces-common'; -import { commonProps, rowValuesProp } from '../common/props'; +import { + AuthenticationType, + httpClient, + HttpMethod, + HttpRequest, +} from '@activepieces/pieces-common'; +import { commonProps, isFirstRowHeaderProp, rowValuesProp } from '../common/props'; export const insertRowAction = createAction({ - auth: googleSheetsAuth, - name: 'insert_row', - description: 'Append a row of values to an existing sheet', - displayName: 'Insert Row', - props: { - ...commonProps, - as_string: Property.Checkbox({ - displayName: 'As String', - description: 'Inserted values that are dates and formulas will be entered strings and have no effect', - required: false, - }), - first_row_headers: Property.Checkbox({ - displayName: 'Does the first row contain headers?', - description: 'If the first row is headers', - required: true, - defaultValue: false, - }), - values: rowValuesProp(), - }, - async run({ propsValue, auth }) { - const { values, spreadsheetId:inputSpreadsheetId, sheetId:inputSheetId, as_string, first_row_headers } = propsValue; - + auth: googleSheetsAuth, + name: 'insert_row', + description: 'Add a new row of data to a specific spreadsheet.', + displayName: 'Add Row', + props: { + ...commonProps, + first_row_headers: isFirstRowHeaderProp(), + as_string: Property.Checkbox({ + displayName: 'As String', + description: + 'Inserted values that are dates and formulas will be entered strings and have no effect', + required: false, + }), + values: rowValuesProp(), + }, + async run({ propsValue, auth }) { + const { + values, + spreadsheetId: inputSpreadsheetId, + sheetId: inputSheetId, + as_string, + first_row_headers, + } = propsValue; - if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { throw new Error('Please select a spreadsheet and sheet first.'); } - const sheetId = Number(inputSheetId); + const sheetId = Number(inputSheetId); const spreadsheetId = inputSpreadsheetId as string; - const sheetName = await googleSheetsCommon.findSheetName( - auth, - spreadsheetId, - sheetId - ); + const sheetName = await googleSheetsCommon.findSheetName(auth, spreadsheetId, sheetId); - const formattedValues = first_row_headers - ? objectToArray(values).map(val => isNil(val) ? '' : val) - : values.values; + const formattedValues = first_row_headers + ? objectToArray(values).map((val) => (isNil(val) ? '' : val)) + : values.values; - const res = await appendGoogleSheetValues({ - auth, - majorDimension: Dimension.COLUMNS, - range: sheetName, - spreadSheetId: spreadsheetId, - valueInputOption: as_string ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED, - values: stringifyArray(formattedValues), - }); + const res = await appendGoogleSheetValues({ + auth, + majorDimension: Dimension.COLUMNS, + range: sheetName, + spreadSheetId: spreadsheetId, + valueInputOption: as_string ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED, + values: stringifyArray(formattedValues), + }); - const updatedRowNumber = extractRowNumber(res.body.updates.updatedRange); - return { ...res.body, row: updatedRowNumber }; - }, + const updatedRowNumber = extractRowNumber(res.body.updates.updatedRange); + return { ...res.body, row: updatedRowNumber }; + }, }); function extractRowNumber(updatedRange: string): number { - const rowRange = updatedRange.split('!')[1]; - return parseInt(rowRange.split(':')[0].substring(1), 10); + const rowRange = updatedRange.split('!')[1]; + return parseInt(rowRange.split(':')[0].substring(1), 10); } async function appendGoogleSheetValues(params: AppendGoogleSheetValuesParams) { - const { auth, majorDimension, range, spreadSheetId, valueInputOption, values } = params; - const accessToken = await getAccessToken(auth); - const request: HttpRequest = { - method: HttpMethod.POST, - url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadSheetId}/values/${encodeURIComponent(`${range}!A:A`)}:append`, - body: { - majorDimension, - range: `${range}!A:A`, - values: values.map(val => ({ values: val })), - }, - authentication: { - type: AuthenticationType.BEARER_TOKEN, - token: accessToken, - }, - queryParams: { - valueInputOption, - }, - }; + const { auth, majorDimension, range, spreadSheetId, valueInputOption, values } = params; + const accessToken = await getAccessToken(auth); + const request: HttpRequest = { + method: HttpMethod.POST, + url: `https://sheets.googleapis.com/v4/spreadsheets/${spreadSheetId}/values/${encodeURIComponent( + `${range}!A:A`, + )}:append`, + body: { + majorDimension, + range: `${range}!A:A`, + values: values.map((val) => ({ values: val })), + }, + authentication: { + type: AuthenticationType.BEARER_TOKEN, + token: accessToken, + }, + queryParams: { + valueInputOption, + }, + }; - return httpClient.sendRequest(request); + return httpClient.sendRequest(request); } type AppendGoogleSheetValuesParams = { - values: string[]; - spreadSheetId: string; - range: string; - valueInputOption: ValueInputOption; - majorDimension: Dimension; - auth: GoogleSheetsAuthValue; + values: string[]; + spreadSheetId: string; + range: string; + valueInputOption: ValueInputOption; + majorDimension: Dimension; + auth: GoogleSheetsAuthValue; }; diff --git a/packages/pieces/community/google-sheets/src/lib/actions/rename-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/actions/rename-worksheet.ts new file mode 100644 index 00000000000..bf219ed3eab --- /dev/null +++ b/packages/pieces/community/google-sheets/src/lib/actions/rename-worksheet.ts @@ -0,0 +1,44 @@ +import { googleSheetsAuth } from '../common/common'; +import { createAction, Property } from '@activepieces/pieces-framework'; +import { includeTeamDrivesProp, sheetIdProp, spreadsheetIdProp } from '../common/props'; +import { google } from 'googleapis'; +import { createGoogleClient } from '../common/common'; + +export const renameWorksheetAction = createAction({ + auth: googleSheetsAuth, + name: 'rename-worksheet', + displayName: 'Rename Worksheet', + description: 'Rename specific worksheet.', + props: { + includeTeamDrives: includeTeamDrivesProp(), + spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'), + sheetId: sheetIdProp('Worksheet', 'The ID of the worksheet to rename.'), + newName:Property.ShortText({ + displayName:'New Sheet Name', + required:true + }) + }, + async run(context) { + const authClient = await createGoogleClient(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const response = await sheets.spreadsheets.batchUpdate({ + spreadsheetId: context.propsValue.spreadsheetId, + requestBody: { + requests:[ + { + updateSheetProperties:{ + properties:{ + sheetId:context.propsValue.sheetId, + title:context.propsValue.newName, + }, + fields:'title' + } + } + ] + }, + }); + + return response.data; + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts b/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts index 25de75948be..8ff56e9bb9d 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/update-multiple-rows.ts @@ -1,164 +1,177 @@ import { googleSheetsAuth } from '../common/common'; import { - createAction, - DynamicPropsValue, - OAuth2PropertyValue, - Property, + createAction, + DynamicPropsValue, + Property, } from '@activepieces/pieces-framework'; -import { areSheetIdsValid, createGoogleClient, Dimension, GoogleSheetsAuthValue, googleSheetsCommon, objectToArray, ValueInputOption } from '../common/common'; -import { getAccessTokenOrThrow } from '@activepieces/pieces-common'; -import { isNil, isString, MarkdownVariant } from '@activepieces/shared'; +import { + areSheetIdsValid, + createGoogleClient, + Dimension, + GoogleSheetsAuthValue, + googleSheetsCommon, + objectToArray, + ValueInputOption, +} from '../common/common'; +import { isString, MarkdownVariant } from '@activepieces/shared'; import { getWorkSheetName } from '../triggers/helpers'; import { google, sheets_v4 } from 'googleapis'; -import { OAuth2Client } from 'googleapis-common'; import { commonProps } from '../common/props'; export const updateMultipleRowsAction = createAction({ - auth: googleSheetsAuth, - name: 'update-multiple-rows', - displayName: 'Update Multiple Rows', - description: 'Updates multiple rows in a specific spreadsheet.', - props: { - ...commonProps, - values: Property.DynamicProperties({ - auth: googleSheetsAuth, - displayName: 'Values', - description: 'The values to update.', - required: true, - refreshers: ['sheetId', 'spreadsheetId', 'headerRow'], - props: async ({ auth, spreadsheetId, sheetId, headerRow }) => { - const sheet_Id = Number(sheetId); - const spreadsheet_Id = spreadsheetId as unknown as string; - const authentication = auth; - - if ( - !auth || - (spreadsheet_Id ?? '').toString().length === 0 || - (sheet_Id ?? '').toString().length === 0 - ) { - return {}; - } - - const fields: DynamicPropsValue = {}; - - const headers = await googleSheetsCommon.getGoogleSheetRows({ - spreadsheetId: spreadsheet_Id, - auth: auth as GoogleSheetsAuthValue, - sheetId: sheet_Id, - rowIndex_s: 1, - rowIndex_e: 1, - headerRow: (headerRow as unknown as number) || 1, - }); - const firstRow = headers[0].values ?? {}; - - //check for empty headers - if (Object.keys(firstRow).length === 0) { - fields['markdown'] = Property.MarkDown({ - 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.`, - variant: MarkdownVariant.INFO, - }); - } else { - const columns: { - [key: string]: any; - } = { - rowId: Property.Number({ - displayName: 'Row Id', - description: 'The row id to update', - required: true, - }), - }; - - for (const key in firstRow) { - columns[key] = Property.ShortText({ - displayName: firstRow[key].toString(), - description: firstRow[key].toString(), - required: false, - defaultValue: '', - }); - } - fields['values'] = Property.Array({ - displayName: 'Values', - required: false, - properties: columns, - }); - } - - return fields; - }, - }), - as_string: Property.Checkbox({ - displayName: 'As String', - description: - 'Inserted values that are dates and formulas will be entered as strings and have no effect', - required: false, - }), - headerRow: Property.Number({ - displayName: 'Header Row', - description: 'Which row contains the headers?', - required: true, - defaultValue: 1, - }), - }, - async run(context) { - const { - spreadsheetId:inputSpreadsheetId, - sheetId:inputSheetId, - values: { values: rowValuesInput }, - as_string: asString, - headerRow, - } = context.propsValue; - - if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { - throw new Error('Please select a spreadsheet and sheet first.'); - } - - const sheetId = Number(inputSheetId); - const spreadsheetId = inputSpreadsheetId as string; - - const sheetName = await getWorkSheetName(context.auth, spreadsheetId, sheetId); - const valueInputOption = asString ? ValueInputOption.RAW : ValueInputOption.USER_ENTERED; - - const authClient = await createGoogleClient(context.auth); - const sheets = google.sheets({ version: 'v4', auth: authClient }); - - const values: sheets_v4.Schema$ValueRange[] = []; - - for (const row of rowValuesInput) { - const { rowId, ...rowValues } = row; - if (rowId === undefined || rowId === null) { - continue; - } - - const formattedValues = objectToArray(rowValues).map((value: string | null | undefined) => { - if (value === '' || value === null || value === undefined) { - return null; - } - if (isString(value)) { - return value; - } - return JSON.stringify(value, null, 2); - }); - - if (formattedValues.length === 0) { - continue; - } - - values.push({ - range: `${sheetName}!A${rowId}:ZZZ${rowId}`, - majorDimension: Dimension.ROWS, - values: [formattedValues], - }); - } - - const response = await sheets.spreadsheets.values.batchUpdate({ - spreadsheetId: spreadsheetId, - - requestBody: { - valueInputOption: valueInputOption, - data: values, - }, - }); - - return response.data; - }, + auth: googleSheetsAuth, + name: 'update-multiple-rows', + displayName: 'Update Multiple Rows', + description: 'Updates multiple rows in a specific spreadsheet.', + props: { + ...commonProps, + values: Property.DynamicProperties({ + auth: googleSheetsAuth, + displayName: 'Values', + description: 'The values to update.', + required: true, + refreshers: ['sheetId', 'spreadsheetId', 'headerRow'], + props: async ({ auth, spreadsheetId, sheetId, headerRow }) => { + const sheet_Id = Number(sheetId); + const spreadsheet_Id = spreadsheetId as unknown as string; + const authentication = auth; + + if ( + !auth || + (spreadsheet_Id ?? '').toString().length === 0 || + (sheet_Id ?? '').toString().length === 0 + ) { + return {}; + } + + const fields: DynamicPropsValue = {}; + + const headers = await googleSheetsCommon.getGoogleSheetRows({ + spreadsheetId: spreadsheet_Id, + auth: auth as GoogleSheetsAuthValue, + sheetId: sheet_Id, + rowIndex_s: 1, + rowIndex_e: 1, + headerRow: (headerRow as unknown as number) || 1, + }); + const firstRow = headers[0].values ?? {}; + + //check for empty headers + if (Object.keys(firstRow).length === 0) { + fields['markdown'] = Property.MarkDown({ + 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.`, + variant: MarkdownVariant.INFO, + }); + } else { + const columns: { + [key: string]: any; + } = { + rowId: Property.Number({ + displayName: 'Row Id', + description: 'The row id to update', + required: true, + }), + }; + + for (const key in firstRow) { + columns[key] = Property.ShortText({ + displayName: firstRow[key].toString(), + description: firstRow[key].toString(), + required: false, + defaultValue: '', + }); + } + fields['values'] = Property.Array({ + displayName: 'Values', + required: false, + properties: columns, + }); + } + + return fields; + }, + }), + as_string: Property.Checkbox({ + displayName: 'As String', + description: + 'Inserted values that are dates and formulas will be entered as strings and have no effect', + required: false, + }), + headerRow: Property.Number({ + displayName: 'Header Row', + description: 'Which row contains the headers?', + required: true, + defaultValue: 1, + }), + }, + async run(context) { + const { + spreadsheetId: inputSpreadsheetId, + sheetId: inputSheetId, + values: { values: rowValuesInput }, + as_string: asString, + headerRow, + } = context.propsValue; + + if (!areSheetIdsValid(inputSpreadsheetId, inputSheetId)) { + throw new Error('Please select a spreadsheet and sheet first.'); + } + + const sheetId = Number(inputSheetId); + const spreadsheetId = inputSpreadsheetId as string; + + const sheetName = await getWorkSheetName( + context.auth, + spreadsheetId, + sheetId + ); + const valueInputOption = asString + ? ValueInputOption.RAW + : ValueInputOption.USER_ENTERED; + + const authClient = await createGoogleClient(context.auth); + const sheets = google.sheets({ version: 'v4', auth: authClient }); + + const values: sheets_v4.Schema$ValueRange[] = []; + + for (const row of rowValuesInput) { + const { rowId, ...rowValues } = row; + if (rowId === undefined || rowId === null) { + continue; + } + + const formattedValues = objectToArray(rowValues).map( + (value: string | null | undefined) => { + if (value === '' || value === null || value === undefined) { + return null; + } + if (isString(value)) { + return value; + } + return JSON.stringify(value, null, 2); + } + ); + + if (formattedValues.length === 0) { + continue; + } + + values.push({ + range: `${sheetName}!A${rowId}:ZZZ${rowId}`, + majorDimension: Dimension.ROWS, + values: [formattedValues], + }); + } + + const response = await sheets.spreadsheets.values.batchUpdate({ + spreadsheetId: spreadsheetId, + + requestBody: { + valueInputOption: valueInputOption, + data: values, + }, + }); + + return response.data; + }, }); diff --git a/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts b/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts index 9dc7160a2a7..518339c5d3d 100644 --- a/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts +++ b/packages/pieces/community/google-sheets/src/lib/actions/update-row.ts @@ -4,12 +4,12 @@ import { googleSheetsAuth } from '../common/common'; import { getWorkSheetName } from '../triggers/helpers'; import { google } from 'googleapis'; import { isString } from '@activepieces/shared'; -import { commonProps, rowValuesProp } from '../common/props'; +import { commonProps, isFirstRowHeaderProp, rowValuesProp } from '../common/props'; export const updateRowAction = createAction({ auth: googleSheetsAuth, name: 'update_row', - description: 'Overwrite values in an existing row', + description: 'Update the data in an existing row.', displayName: 'Update Row', props: { ...commonProps, @@ -18,12 +18,7 @@ export const updateRowAction = createAction({ description: 'The row number to update', required: true, }), - first_row_headers: Property.Checkbox({ - displayName: 'Does the first row contain headers?', - description: 'If the first row is headers', - required: true, - defaultValue: false, - }), + first_row_headers: isFirstRowHeaderProp(), values: rowValuesProp(), }, async run(context) { diff --git a/packages/pieces/community/google-sheets/src/lib/common/props.ts b/packages/pieces/community/google-sheets/src/lib/common/props.ts index 73063dbeb49..00a528f68cb 100644 --- a/packages/pieces/community/google-sheets/src/lib/common/props.ts +++ b/packages/pieces/community/google-sheets/src/lib/common/props.ts @@ -1,12 +1,27 @@ import { DropdownOption, Property } from '@activepieces/pieces-framework'; import { google, drive_v3 } from 'googleapis'; -import { columnToLabel, createGoogleClient, getHeaderRow, googleSheetsAuth, GoogleSheetsAuthValue, googleSheetsCommon } from './common'; +import { + columnToLabel, + createGoogleClient, + getHeaderRow, + googleSheetsAuth, + GoogleSheetsAuthValue, + googleSheetsCommon, +} from './common'; import { isNil } from '@activepieces/shared'; +const createEmptyOptionList = (message: string) => { + return { + disabled: true, + placeholder: message, + options: [], + }; +}; + export const includeTeamDrivesProp = () => Property.Checkbox({ - displayName: 'Include Team Drive Sheets ?', - description: 'Determines if sheets from Team Drives should be included in the results.', + displayName: 'Include Shared Drive Sheets ?', + description: 'Turn this on to also see spreadsheets from Shared Drives.', defaultValue: false, required: false, }); @@ -20,12 +35,9 @@ export const spreadsheetIdProp = (displayName: string, description: string, requ refreshers: ['includeTeamDrives'], options: async ({ auth, includeTeamDrives }, { searchValue }) => { if (!auth) { - return { - disabled: true, - options: [], - placeholder: 'Please authenticate first', - }; + return createEmptyOptionList('please connect your account first.'); } + const authValue = auth; const authClient = await createGoogleClient(authValue); @@ -77,12 +89,12 @@ export const sheetIdProp = (displayName: string, description: string, required = required, refreshers: ['spreadsheetId'], options: async ({ auth, spreadsheetId }) => { - if (!auth || (spreadsheetId ?? '').toString().length === 0) { - return { - disabled: true, - options: [], - placeholder: 'Please select a spreadsheet first.', - }; + if (!auth) { + return createEmptyOptionList('please connect your account first.'); + } + + if ((spreadsheetId ?? '').toString().length === 0) { + return createEmptyOptionList('please select a spreadsheet first.'); } const authValue = auth as GoogleSheetsAuthValue; @@ -102,7 +114,7 @@ export const sheetIdProp = (displayName: string, description: string, required = for (const sheet of sheetsData) { const title = sheet.properties?.title; const sheetId = sheet.properties?.sheetId; - if(isNil(title) || isNil(sheetId)){ + if (isNil(title) || isNil(sheetId)) { continue; } options.push({ @@ -121,13 +133,13 @@ export const sheetIdProp = (displayName: string, description: string, required = export const commonProps = { includeTeamDrives: includeTeamDrivesProp(), spreadsheetId: spreadsheetIdProp('Spreadsheet', 'The ID of the spreadsheet to use.'), - sheetId: sheetIdProp('Sheet', 'The ID of the sheet to use.'), + sheetId: sheetIdProp('Worksheet', 'The ID of the worksheet to use.'), }; export const rowValuesProp = () => Property.DynamicProperties({ displayName: 'Values', - description: 'The values to insert', + description: 'The values to add', required: true, auth: googleSheetsAuth, refreshers: ['sheetId', 'spreadsheetId', 'first_row_headers'], @@ -151,7 +163,7 @@ export const rowValuesProp = () => if (!first_row_headers) { return { values: Property.Array({ - displayName: 'Values', + displayName: 'Row Values', required: true, }), }; @@ -165,7 +177,7 @@ export const rowValuesProp = () => const label = columnToLabel(i); properties[label] = Property.ShortText({ displayName: firstRow[i].toString(), - description: firstRow[i].toString(), + // description: firstRow[i].toString(), required: false, defaultValue: '', }); @@ -175,9 +187,9 @@ export const rowValuesProp = () => }); export const columnNameProp = () => - Property.Dropdown({ - description: 'Column Name', - displayName: 'The name of the column to search in', + Property.Dropdown({ + displayName: 'Column Name', + description: 'The name of the column to search in', required: true, auth: googleSheetsAuth, refreshers: ['sheetId', 'spreadsheetId'], @@ -196,11 +208,7 @@ export const columnNameProp = () => }; } - const sheetName = await googleSheetsCommon.findSheetName( - auth, - spreadsheet_id, - sheet_id, - ); + const sheetName = await googleSheetsCommon.findSheetName(auth, spreadsheet_id, sheet_id); if (!sheetName) { throw Error('Sheet not found in spreadsheet'); @@ -257,3 +265,10 @@ export const columnNameProp = () => }; }, }); + +export const isFirstRowHeaderProp = () => + Property.Checkbox({ + displayName: 'First Row Contains Headers ?', + required: true, + defaultValue: false, + }); diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts index c68a61b00a2..889b50d34a2 100644 --- a/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-spreadsheet.ts @@ -1,4 +1,8 @@ -import { AppConnectionValueForAuthProperty, createTrigger,PiecePropValueSchema,TriggerStrategy } from '@activepieces/pieces-framework'; +import { + AppConnectionValueForAuthProperty, + createTrigger, + TriggerStrategy, +} from '@activepieces/pieces-framework'; import { googleSheetsAuth } from '../common/common'; import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common'; @@ -6,78 +10,79 @@ import dayjs from 'dayjs'; import { google, drive_v3 } from 'googleapis'; import { includeTeamDrivesProp } from '../common/props'; import { createGoogleClient, GoogleSheetsAuthValue } from '../common/common'; + type Props = { - includeTeamDrives?: boolean; + includeTeamDrives?: boolean; }; + const polling: Polling, Props> = { - strategy: DedupeStrategy.TIMEBASED, - async items({ auth, propsValue, lastFetchEpochMS }) { - const authValue = auth as GoogleSheetsAuthValue; - const q = ["mimeType='application/vnd.google-apps.spreadsheet'",'trashed = false']; - if (lastFetchEpochMS) { - q.push(`createdTime > '${dayjs(lastFetchEpochMS).toISOString()}'`); - } - const authClient = await createGoogleClient(authValue); - const drive = google.drive({ version: 'v3', auth: authClient }); - let nextPageToken; - const items = []; - do { - const response: any = await drive.files.list({ - q: q.join(' and '), - fields: '*', - orderBy: 'createdTime desc', - supportsAllDrives: true, - includeItemsFromAllDrives: propsValue.includeTeamDrives, - pageToken: nextPageToken, - }); - const fileList: drive_v3.Schema$FileList = response.data; - if (fileList.files) { - items.push(...fileList.files); - } - if (lastFetchEpochMS === 0) break; - nextPageToken = response.data.nextPageToken; - } while (nextPageToken); - return items.map((item) => ({ - epochMilliSeconds: dayjs(item.createdTime).valueOf(), - data: item, - })); - }, + strategy: DedupeStrategy.TIMEBASED, + async items({ auth, propsValue, lastFetchEpochMS }) { + const authValue = auth as GoogleSheetsAuthValue; + const q = ["mimeType='application/vnd.google-apps.spreadsheet'", 'trashed = false']; + if (lastFetchEpochMS) { + q.push(`createdTime > '${dayjs(lastFetchEpochMS).toISOString()}'`); + } + const authClient = await createGoogleClient(authValue); + const drive = google.drive({ version: 'v3', auth: authClient }); + let nextPageToken; + const items = []; + do { + const response: any = await drive.files.list({ + q: q.join(' and '), + fields: '*', + orderBy: 'createdTime desc', + supportsAllDrives: true, + includeItemsFromAllDrives: propsValue.includeTeamDrives, + pageToken: nextPageToken, + }); + const fileList: drive_v3.Schema$FileList = response.data; + if (fileList.files) { + items.push(...fileList.files); + } + if (lastFetchEpochMS === 0) break; + nextPageToken = response.data.nextPageToken; + } while (nextPageToken); + return items.map((item) => ({ + epochMilliSeconds: dayjs(item.createdTime).valueOf(), + data: item, + })); + }, }; export const newSpreadsheetTrigger = createTrigger({ - auth: googleSheetsAuth, - name: 'new-spreadsheet', - displayName: 'New Spreadsheet', - description: 'Triggers when a new spreadsheet is created.', - type: TriggerStrategy.POLLING, - props: { - includeTeamDrives: includeTeamDrivesProp() - }, - async onEnable(context) { - await pollingHelper.onEnable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - async onDisable(context) { - await pollingHelper.onDisable(polling, { - auth: context.auth, - store: context.store, - propsValue: context.propsValue, - }); - }, - async test(context) { - return await pollingHelper.test(polling, context); - }, - async run(context) { - return await pollingHelper.poll(polling, context); - }, - sampleData:{ - kind: 'drive#file', - mimeType: 'application/vnd.google-apps.spreadsheet', - webViewLink: - 'https://docs.google.com/document/d/1_9xjsrYFgHVvgqYwAJ8KcsDcNU/edit?usp=drivesdk', - id: '1_9xjsrYFgHVvgqYwAJ8KcsDcN3AzPelsux', - name: 'Test Document', - }, -}); \ No newline at end of file + auth: googleSheetsAuth, + name: 'new-spreadsheet', + displayName: 'New Spreadsheet', + description: 'Triggers when a new spreadsheet is created.', + type: TriggerStrategy.POLLING, + props: { + includeTeamDrives: includeTeamDrivesProp(), + }, + async onEnable(context) { + await pollingHelper.onEnable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async onDisable(context) { + await pollingHelper.onDisable(polling, { + auth: context.auth, + store: context.store, + propsValue: context.propsValue, + }); + }, + async test(context) { + return await pollingHelper.test(polling, context); + }, + async run(context) { + return await pollingHelper.poll(polling, context); + }, + sampleData: { + kind: 'drive#file', + mimeType: 'application/vnd.google-apps.spreadsheet', + webViewLink: 'https://docs.google.com/document/d/1_9xjsrYFgHVvgqYwAJ8KcsDcNU/edit?usp=drivesdk', + id: '1_9xjsrYFgHVvgqYwAJ8KcsDcN3AzPelsux', + name: 'Test Document', + }, +}); diff --git a/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts b/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts index 90e2b5785b8..0e5fd331e97 100644 --- a/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts +++ b/packages/pieces/community/google-sheets/src/lib/triggers/new-worksheet.ts @@ -1,7 +1,6 @@ import { googleSheetsAuth } from '../common/common'; import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { google } from 'googleapis'; -import { OAuth2Client } from 'googleapis-common'; import { isNil } from '@activepieces/shared'; import { includeTeamDrivesProp, spreadsheetIdProp } from '../common/props'; import { createGoogleClient } from '../common/common'; diff --git a/packages/pieces/community/pinch-payments/package.json b/packages/pieces/community/pinch-payments/package.json index 0a51e60e41d..0be6dce3bed 100644 --- a/packages/pieces/community/pinch-payments/package.json +++ b/packages/pieces/community/pinch-payments/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-pinch-payments", - "version": "0.0.2", + "version": "0.0.3", "type": "commonjs", "main": "./src/index.js", "types": "./src/index.d.ts", diff --git a/packages/pieces/community/pinch-payments/src/index.ts b/packages/pieces/community/pinch-payments/src/index.ts index c68f9b8d5a0..179e5b55958 100644 --- a/packages/pieces/community/pinch-payments/src/index.ts +++ b/packages/pieces/community/pinch-payments/src/index.ts @@ -30,7 +30,7 @@ export const pinchPayments = createPiece({ auth: pinchPaymentsAuth, minimumSupportedRelease: '0.36.1', categories: [PieceCategory.PAYMENT_PROCESSING], - logoUrl: "https://cdn.activepieces.com/pieces/pinch-payments.png", + logoUrl: "https://cdn.activepieces.com/pieces/pinch-payments.jpg", authors: ['onyedikachi-david', 'dkarzon'], actions: [ createOrUpdatePayerAction, diff --git a/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx b/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx index 4859ea870e2..fad5072f7fc 100644 --- a/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx +++ b/packages/react-ui/src/app/components/sidebar/ap-sidebar-item.tsx @@ -29,6 +29,7 @@ export type SidebarItemType = { isSubItem?: boolean; show?: boolean; hasPermission?: boolean; + onClick?: () => void; }; export const ApSidebarItem = (item: SidebarItemType) => { @@ -38,24 +39,21 @@ export const ApSidebarItem = (item: SidebarItemType) => { location.pathname.startsWith(item.to) || item.isActive?.(location.pathname); const isCollapsed = state === 'collapsed'; - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation(); + const linkProps = { + to: item.to, + target: item.newWindow ? '_blank' : '', + rel: item.newWindow ? 'noopener noreferrer' : undefined, + ...(item.onClick && { onClick: item.onClick }), }; return ( - + {isCollapsed ? ( { ) : ( - +
diff --git a/packages/react-ui/src/app/components/sidebar/dashboard/index.tsx b/packages/react-ui/src/app/components/sidebar/dashboard/index.tsx index 05336adb8b1..3c817428c9a 100644 --- a/packages/react-ui/src/app/components/sidebar/dashboard/index.tsx +++ b/packages/react-ui/src/app/components/sidebar/dashboard/index.tsx @@ -30,6 +30,7 @@ import { TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; +import { templatesTelemetryApi } from '@/features/templates/lib/templates-telemetry-api'; import { platformHooks } from '@/hooks/platform-hooks'; import { projectCollectionUtils } from '@/hooks/project-collection'; import { userHooks } from '@/hooks/user-hooks'; @@ -40,6 +41,7 @@ import { ProjectType, ProjectWithLimits, TeamProjectsLimit, + TemplateTelemetryEventType, } from '@activepieces/shared'; import { SidebarGeneralItemType } from '../ap-sidebar-group'; @@ -152,6 +154,13 @@ export function ProjectDashboardSidebar() { return true; }; + const handleExploreClick = useCallback(() => { + templatesTelemetryApi.sendEvent({ + eventType: TemplateTelemetryEventType.EXPLORE_VIEW, + userId: currentUser?.id, + }); + }, []); + const exploreLink: SidebarItemType = { type: 'link', to: '/templates', @@ -160,6 +169,7 @@ export function ProjectDashboardSidebar() { icon: Compass, hasPermission: true, isSubItem: false, + onClick: handleExploreClick, }; const impactLink: SidebarItemType = { diff --git a/packages/react-ui/src/app/routes/templates/id/use-template-dialog.tsx b/packages/react-ui/src/app/routes/templates/id/use-template-dialog.tsx index 33afca3339c..1d1aaecb99d 100644 --- a/packages/react-ui/src/app/routes/templates/id/use-template-dialog.tsx +++ b/packages/react-ui/src/app/routes/templates/id/use-template-dialog.tsx @@ -25,11 +25,14 @@ import { import { flowHooks } from '@/features/flows/lib/flow-hooks'; import { foldersApi } from '@/features/folders/lib/folders-api'; import { foldersHooks } from '@/features/folders/lib/folders-hooks'; +import { templatesTelemetryApi } from '@/features/templates/lib/templates-telemetry-api'; import { projectCollectionUtils } from '@/hooks/project-collection'; import { authenticationSession } from '@/lib/authentication-session'; import { PopulatedFlow, Template, + TemplateTelemetryEventType, + TemplateType, UncategorizedFolderId, isNil, } from '@activepieces/shared'; @@ -118,6 +121,16 @@ export const UseTemplateDialog = ({ return; } createFlow({ projectId: selectedProjectId, folderId: selectedFolderId }); + + const userId = authenticationSession.getCurrentUserId(); + + if (template.type === TemplateType.OFFICIAL && userId) { + templatesTelemetryApi.sendEvent({ + eventType: TemplateTelemetryEventType.INSTALL, + templateId: template.id, + userId, + }); + } }; const flowCount = template.flows?.length || 0; diff --git a/packages/react-ui/src/app/routes/templates/index.tsx b/packages/react-ui/src/app/routes/templates/index.tsx index 2c2d237c557..892e2905ae3 100644 --- a/packages/react-ui/src/app/routes/templates/index.tsx +++ b/packages/react-ui/src/app/routes/templates/index.tsx @@ -24,7 +24,7 @@ const TemplatesPage = () => { const navigate = useNavigate(); const { data: templateCategories } = templatesHooks.useTemplateCategories(); const { platform } = platformHooks.useCurrentPlatform(); - const isShowingOfficialTemplates = !platform.plan.manageTemplatesEnabled; + const isShowingOfficialTemplates = platform.plan.manageTemplatesEnabled; const { templates, isLoading, search, setSearch, category, setCategory } = templatesHooks.useTemplates( isShowingOfficialTemplates ? TemplateType.OFFICIAL : TemplateType.CUSTOM, diff --git a/packages/react-ui/src/features/templates/lib/templates-telemetry-api.ts b/packages/react-ui/src/features/templates/lib/templates-telemetry-api.ts new file mode 100644 index 00000000000..e4e564b790f --- /dev/null +++ b/packages/react-ui/src/features/templates/lib/templates-telemetry-api.ts @@ -0,0 +1,8 @@ +import { api } from '@/lib/api'; +import { TemplateTelemetryEvent } from '@activepieces/shared'; + +export const templatesTelemetryApi = { + sendEvent(event: TemplateTelemetryEvent) { + return api.post(`/v1/templates-telemetry/event`, event); + }, +}; diff --git a/packages/server/api/src/app/flows/flow/flow-service-side-effects.ts b/packages/server/api/src/app/flows/flow/flow-service-side-effects.ts index 20ff28e2012..82ea02b8dc8 100644 --- a/packages/server/api/src/app/flows/flow/flow-service-side-effects.ts +++ b/packages/server/api/src/app/flows/flow/flow-service-side-effects.ts @@ -14,6 +14,7 @@ export const flowSideEffects = (log: FastifyBaseLogger) => ({ newStatus, flowToUpdate, publishedFlowVersion, + templateId, }: PreUpdateStatusParams): Promise { switch (newStatus) { case FlowStatus.ENABLED: { @@ -21,6 +22,7 @@ export const flowSideEffects = (log: FastifyBaseLogger) => ({ flowVersion: publishedFlowVersion, projectId: flowToUpdate.projectId, simulate: false, + templateId, }) break } @@ -30,6 +32,7 @@ export const flowSideEffects = (log: FastifyBaseLogger) => ({ projectId: flowToUpdate.projectId, simulate: false, ignoreError: false, + templateId, }) break } @@ -68,6 +71,7 @@ type PreUpdateStatusParams = { flowToUpdate: Flow publishedFlowVersion: FlowVersion newStatus: FlowStatus + templateId?: string } diff --git a/packages/server/api/src/app/flows/flow/flow.jobs.ts b/packages/server/api/src/app/flows/flow/flow.jobs.ts index f74b9558ebe..5080de9e7b2 100644 --- a/packages/server/api/src/app/flows/flow/flow.jobs.ts +++ b/packages/server/api/src/app/flows/flow/flow.jobs.ts @@ -54,11 +54,14 @@ export const flowBackgroundJobs = (log: FastifyBaseLogger) => ({ versionId: publishedFlowVersionId, }) + const templateId = flowToUpdate.templateId ?? undefined + if (!preUpdateDone) { await flowSideEffects(log).preUpdateStatus({ flowToUpdate, publishedFlowVersion, newStatus, + templateId, }) await job.updateData({ ...data, diff --git a/packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts b/packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts index 4b555bf9225..d2dcc008c1b 100644 --- a/packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts +++ b/packages/server/api/src/app/trigger/trigger-source/trigger-source-service.ts @@ -1,7 +1,8 @@ -import { ActivepiecesError, apId, ErrorCode, FlowVersion, isNil, PopulatedTriggerSource, TriggerSource } from '@activepieces/shared' +import { ActivepiecesError, apId, ErrorCode, FlowVersion, isNil, PopulatedTriggerSource, TemplateTelemetryEventType, TriggerSource } from '@activepieces/shared' import { FastifyBaseLogger } from 'fastify' import { repoFactory } from '../../core/db/repo-factory' import { flowVersionService } from '../../flows/flow-version/flow-version.service' +import { templateTelemetryService } from '../../template/template-telemetry/template-telemetry.service' import { flowTriggerSideEffect } from './flow-trigger-side-effect' import { TriggerSourceEntity } from './trigger-source-entity' import { triggerUtils } from './trigger-utils' @@ -11,7 +12,7 @@ export const triggerSourceRepo = repoFactory(TriggerSourceEntity) export const triggerSourceService = (log: FastifyBaseLogger) => { return { async enable(params: EnableTriggerParams): Promise { - const { flowVersion, projectId, simulate } = params + const { flowVersion, projectId, simulate, templateId } = params log.info({ flowVersion, projectId, @@ -44,6 +45,15 @@ export const triggerSourceService = (log: FastifyBaseLogger) => { pieceTrigger, simulate, }) + + if (templateId) { + templateTelemetryService(log).sendEvent({ + eventType: TemplateTelemetryEventType.ACTIVATE, + templateId, + flowId: flowVersion.flowId, + }) + } + log.info('[triggerSourceService#enable] Enabled flow trigger side effect') return triggerSourceRepo().save({ ...triggerSource, @@ -107,7 +117,7 @@ export const triggerSourceService = (log: FastifyBaseLogger) => { }) }, async disable(params: DisableTriggerParams): Promise { - const { projectId, flowId, simulate } = params + const { projectId, flowId, simulate, templateId } = params log.info({ flowId, projectId, @@ -140,6 +150,13 @@ export const triggerSourceService = (log: FastifyBaseLogger) => { projectId, }) log.info('[triggerSourceService#disable] Soft deleted trigger source') + if (templateId) { + templateTelemetryService(log).sendEvent({ + eventType: TemplateTelemetryEventType.DEACTIVATE, + templateId, + flowId, + }) + } }, } } @@ -171,10 +188,12 @@ type DisableTriggerParams = { flowId: string simulate: boolean ignoreError: boolean + templateId?: string } type EnableTriggerParams = { flowVersion: FlowVersion projectId: string simulate: boolean + templateId?: string } \ No newline at end of file diff --git a/packages/shared/src/lib/template/template-telemetry.ts b/packages/shared/src/lib/template/template-telemetry.ts index e99410bebd4..e11177f672c 100644 --- a/packages/shared/src/lib/template/template-telemetry.ts +++ b/packages/shared/src/lib/template/template-telemetry.ts @@ -1,4 +1,5 @@ import { Static, Type } from '@sinclair/typebox' +import { DiscriminatedUnion } from '../common' export enum TemplateTelemetryEventType { VIEW = 'VIEW', @@ -36,11 +37,11 @@ const ExploreViewEvent = Type.Object({ userId: Type.Optional(Type.String()), }) -export const TemplateTelemetryEvent = Type.Union([ - ViewEvent, +export const TemplateTelemetryEvent = DiscriminatedUnion('eventType', [ InstallEvent, ActivateEvent, DeactivateEvent, + ViewEvent, ExploreViewEvent, ]) export type TemplateTelemetryEvent = Static