diff --git a/components/microsoft_excel/actions/add-a-worksheet-tablerow/add-a-worksheet-tablerow.mjs b/components/microsoft_excel/actions/add-a-worksheet-tablerow/add-a-worksheet-tablerow.mjs index 102486632e8ba..cb3f570004505 100644 --- a/components/microsoft_excel/actions/add-a-worksheet-tablerow/add-a-worksheet-tablerow.mjs +++ b/components/microsoft_excel/actions/add-a-worksheet-tablerow/add-a-worksheet-tablerow.mjs @@ -3,8 +3,8 @@ import microsoftExcel from "../../microsoft_excel.app.mjs"; export default { key: "microsoft_excel-add-a-worksheet-tablerow", - name: "Add A Worksheet Tablerow", - version: "0.0.4", + name: "Add a Worksheet Tablerow", + version: "0.0.5", description: "Adds rows to the end of specific table. [See the documentation](https://learn.microsoft.com/en-us/graph/api/tablerowcollection-add?view=graph-rest-1.0&tabs=http)", type: "action", props: { @@ -15,10 +15,10 @@ export default { "folderId", ], }, - itemId: { + sheetId: { propDefinition: [ microsoftExcel, - "itemId", + "sheetId", ({ folderId }) => ({ folderId, }), @@ -29,8 +29,8 @@ export default { propDefinition: [ microsoftExcel, "tableId", - ({ itemId }) => ({ - itemId, + ({ sheetId }) => ({ + sheetId, }), ], hidden: true, @@ -49,10 +49,10 @@ export default { }, }, async additionalProps(props) { - if (this.itemId) { + if (this.sheetId) { try { await this.microsoftExcel.listTables({ - itemId: this.itemId, + sheetId: this.sheetId, }); } catch { props.tableName.hidden = false; @@ -65,15 +65,15 @@ export default { async run({ $ }) { const { microsoftExcel, - itemId, + sheetId, tableId, tableName, values, } = this; - const response = await microsoftExcel.addRow({ + const response = await microsoftExcel.addTableRow({ $, - itemId, + sheetId, tableId, tableName, data: { diff --git a/components/microsoft_excel/actions/add-row/add-row.mjs b/components/microsoft_excel/actions/add-row/add-row.mjs new file mode 100644 index 0000000000000..a70a29c7b59cf --- /dev/null +++ b/components/microsoft_excel/actions/add-row/add-row.mjs @@ -0,0 +1,113 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; +import { getColumnLetter } from "../../common/utils.mjs"; + +export default { + key: "microsoft_excel-add-row", + name: "Add Row", + description: "Insert a new row into a specified Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/range-insert?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + values: { + type: "string[]", + label: "Values", + description: "An array of values for the new row. Each item in the array represents one cell. E.g. `[1, 2, 3]`", + }, + }, + methods: { + isArrayString(str) { + return typeof str === "string" && ((str.startsWith("[") && str.endsWith("]")) || ((str.startsWith("[[") || str.startsWith("[ [")) && (str.endsWith("]]") || str.endsWith("] ]")))); + }, + convertStringToArray(str) { + const arrayString = str.match(/\[\[?(.*?)\]?\]/)[1]; + return arrayString.split(","); + }, + parseValues(columnCount) { + let values = this.values; + if (Array.isArray(this.values)) { + if (Array.isArray(this.values[0])) { + values = this.values[0]; + } else if (this.isArrayString(this.values[0])) { + values = this.convertStringToArray(this.values[0]); + } + } else { + if (this.isArrayString(this.values)) { + values = this.convertStringToArray(this.values); + } + } + + if (values.length < columnCount) { + values.length = columnCount; + } + return values; + }, + }, + async run({ $ }) { + const { + address, columnCount, + } = await this.microsoftExcel.getUsedRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + }); + + // get next row range + const match = address.match(/^(.+!)?([A-Z]+)(\d+):([A-Z]+)(\d+)$/); + const nextRow = parseInt(match[5], 10) + 1; + const values = this.parseValues(columnCount); + const colEnd = getColumnLetter(values.length); + const range = `A${nextRow}:${colEnd}${nextRow}`; + + // insert range + await this.microsoftExcel.insertRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range, + data: { + shift: "Down", + }, + }); + + // update range + const response = await this.microsoftExcel.updateRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range, + data: { + values: [ + values, + ], + }, + }); + + $.export("$summary", "Successfully added new row"); + return response; + }, +}; diff --git a/components/microsoft_excel/actions/find-row/find-row.mjs b/components/microsoft_excel/actions/find-row/find-row.mjs new file mode 100644 index 0000000000000..538d2921df176 --- /dev/null +++ b/components/microsoft_excel/actions/find-row/find-row.mjs @@ -0,0 +1,84 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; + +export default { + key: "microsoft_excel-find-row", + name: "Find Row", + description: "Find a row by column and value in an Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/range-get?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + column: { + type: "string", + label: "Column", + description: "The column to search. E.g. `A`", + }, + value: { + type: "string", + label: "Value", + description: "The value to search for", + }, + }, + async run({ $ }) { + const { + rowCount, address, + } = await this.microsoftExcel.getUsedRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + }); + const lastColumn = address.match(/:([A-Z]+)\d+$/)[1]; + + const { values: rangeValues } = await this.microsoftExcel.getRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `${this.column}1:${this.column}${rowCount}`, + }); + const values = rangeValues.map((v) => v[0]); + let index = values.indexOf(this.value); + if (index === -1 && !isNaN(this.value)) { + index = values.indexOf(+this.value); + } + + if (index === -1) { + $.export("$summary", "No matching rows found"); + return values; + } + + const row = index + 1; + const response = await this.microsoftExcel.getRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `A${row}:${lastColumn}${row}`, + }); + + $.export("$summary", `Found value in row ${row}`); + return response; + }, +}; diff --git a/components/microsoft_excel/actions/get-columns/get-columns.mjs b/components/microsoft_excel/actions/get-columns/get-columns.mjs new file mode 100644 index 0000000000000..61b8f3c5dcd2f --- /dev/null +++ b/components/microsoft_excel/actions/get-columns/get-columns.mjs @@ -0,0 +1,65 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "microsoft_excel-get-columns", + name: "Get Columns", + description: "Get the values of the specified columns in an Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/range-get?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + columns: { + type: "string[]", + label: "Columns", + description: "An array of column labels to retrieve. E.g. [\"A\", \"C\"]", + }, + }, + async run({ $ }) { + const { rowCount } = await this.microsoftExcel.getUsedRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + }); + + const values = {}; + const columns = parseObject(this.columns); + for (const column of columns) { + const response = await this.microsoftExcel.getRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `${column}1:${column}${rowCount}`, + }); + values[column] = response.values.map((v) => v[0]); + } + + $.export("$summary", "Successfully retrieved column values"); + + return values; + }, +}; diff --git a/components/microsoft_excel/actions/get-spreadsheet/get-spreadsheet.mjs b/components/microsoft_excel/actions/get-spreadsheet/get-spreadsheet.mjs new file mode 100644 index 0000000000000..9929ef5eb9088 --- /dev/null +++ b/components/microsoft_excel/actions/get-spreadsheet/get-spreadsheet.mjs @@ -0,0 +1,63 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; +import { json2csv } from "json-2-csv"; + +export default { + key: "microsoft_excel-get-spreadsheet", + name: "Get Spreadsheet", + description: "Get the values of a specified Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/range-get?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + range: { + type: "string", + label: "Range", + description: "The range within the worksheet to retrieve. E.g. `A1:C4`. If not specified, entire \"usedRange\" will be returned", + optional: true, + }, + }, + async run({ $ }) { + const response = this.range + ? await this.microsoftExcel.getRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `${this.range}`, + }) + : await this.microsoftExcel.getUsedRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + }); + + const csv = await json2csv(response.values); + response.csv = csv; + + $.export("$summary", "Successfully retrieved spreadsheet values"); + return response; + }, +}; diff --git a/components/microsoft_excel/actions/get-table-rows/get-table-rows.mjs b/components/microsoft_excel/actions/get-table-rows/get-table-rows.mjs new file mode 100644 index 0000000000000..00682dbbe86c4 --- /dev/null +++ b/components/microsoft_excel/actions/get-table-rows/get-table-rows.mjs @@ -0,0 +1,61 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; + +export default { + key: "microsoft_excel-get-table-rows", + name: "Get Table Rows", + description: "Retrieve rows from a specified table in an Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/tablerow-list?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + tableId: { + propDefinition: [ + microsoftExcel, + "tableId", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + }, + async run({ $ }) { + const rows = []; + const params = { + $top: 100, + $skip: 0, + }; + let total; + + do { + const { value } = await this.microsoftExcel.listTableRows({ + $, + sheetId: this.sheetId, + tableId: this.tableId, + params, + }); + rows.push(...value); + total = value.length; + params["$skip"] += params["$top"]; + } while (total); + + $.export("$summary", `Successfully retrieved ${rows.length} row${rows.length === 1 + ? "" + : "s"}`); + return rows; + }, +}; diff --git a/components/microsoft_excel/actions/update-cell/update-cell.mjs b/components/microsoft_excel/actions/update-cell/update-cell.mjs new file mode 100644 index 0000000000000..8721d6274badb --- /dev/null +++ b/components/microsoft_excel/actions/update-cell/update-cell.mjs @@ -0,0 +1,63 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; + +export default { + key: "microsoft_excel-update-cell", + name: "Update Cell", + description: "Update the value of a specific cell in an Excel worksheet. [See the documentation](https://learn.microsoft.com/en-us/graph/api/range-update?view=graph-rest-1.0&tabs=http)", + version: "0.0.1", + type: "action", + props: { + microsoftExcel, + folderId: { + propDefinition: [ + microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + cell: { + type: "string", + label: "Cell", + description: "The address of the cell to update. E.g. `A1`", + }, + value: { + type: "string", + label: "Value", + description: "The value to enter in the cell", + }, + }, + async run({ $ }) { + const response = await this.microsoftExcel.updateRange({ + $, + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `${this.cell}:${this.cell}`, + data: { + values: [ + [ + this.value, + ], + ], + }, + }); + $.export("$summary", `Successfully updated cell \`${this.cell}\``); + return response; + }, +}; diff --git a/components/microsoft_excel/actions/update-worksheet-tablerow/update-worksheet-tablerow.mjs b/components/microsoft_excel/actions/update-worksheet-tablerow/update-worksheet-tablerow.mjs index 91c321da43468..bb198ff8b23c8 100644 --- a/components/microsoft_excel/actions/update-worksheet-tablerow/update-worksheet-tablerow.mjs +++ b/components/microsoft_excel/actions/update-worksheet-tablerow/update-worksheet-tablerow.mjs @@ -1,9 +1,10 @@ import microsoftExcel from "../../microsoft_excel.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; export default { key: "microsoft_excel-update-worksheet-tablerow", name: "Update Worksheet Tablerow", - version: "0.0.4", + version: "0.0.5", description: "Update the properties of tablerow object. `(Only for work or school account)` [See the documentation](https://learn.microsoft.com/en-us/graph/api/tablerow-update?view=graph-rest-1.0&tabs=http)", type: "action", props: { @@ -14,10 +15,10 @@ export default { "folderId", ], }, - itemId: { + sheetId: { propDefinition: [ microsoftExcel, - "itemId", + "sheetId", ({ folderId }) => ({ folderId, }), @@ -27,19 +28,19 @@ export default { propDefinition: [ microsoftExcel, "tableId", - ({ itemId }) => ({ - itemId, + ({ sheetId }) => ({ + sheetId, }), ], }, rowId: { propDefinition: [ microsoftExcel, - "rowId", + "tableRowId", ({ - itemId, tableId, + sheetId, tableId, }) => ({ - itemId, + sheetId, tableId, }), ], @@ -49,25 +50,27 @@ export default { microsoftExcel, "values", ], - description: "Represents the raw values of the specified range. The data returned could be of type string, number, or a boolean. Cells that contain errors return the error string.", + description: "An array of values for the updated row. Each item in the array represents one cell. E.g. `[1, 2, 3]`", }, }, async run({ $ }) { const { microsoftExcel, - itemId, + sheetId, tableId, rowId, values, } = this; - const response = await microsoftExcel.updateRow({ + const response = await microsoftExcel.updateTableRow({ $, - itemId, + sheetId, tableId, rowId, data: { - values: JSON.parse(values), + values: [ + parseObject(values), + ], }, }); diff --git a/components/microsoft_excel/common/utils.mjs b/components/microsoft_excel/common/utils.mjs index 154701004e72a..a7472a1b24fef 100644 --- a/components/microsoft_excel/common/utils.mjs +++ b/components/microsoft_excel/common/utils.mjs @@ -16,3 +16,13 @@ export const parseObject = (obj) => { } return obj; }; + +export function getColumnLetter(index) { + let letter = ""; + while (index > 0) { + const mod = (index - 1) % 26; + letter = String.fromCharCode(65 + mod) + letter; + index = Math.floor((index - 1) / 26); + } + return letter; +} diff --git a/components/microsoft_excel/microsoft_excel.app.mjs b/components/microsoft_excel/microsoft_excel.app.mjs index 4b10d83fdcfcc..3728dc41a8a8a 100644 --- a/components/microsoft_excel/microsoft_excel.app.mjs +++ b/components/microsoft_excel/microsoft_excel.app.mjs @@ -1,4 +1,5 @@ import { axios } from "@pipedream/platform"; +const DEFAULT_LIMIT = 50; export default { type: "app", @@ -6,8 +7,8 @@ export default { propDefinitions: { folderId: { type: "string", - label: "Folder Id", - description: "The ID of the folder where the item is located.", + label: "Folder ID", + description: "The ID of the folder where the item is located", async options() { const folders = await this.listFolderOptions(); return [ @@ -16,15 +17,14 @@ export default { ]; }, }, - itemId: { + sheetId: { type: "string", - label: "Item Id", - description: "The Id of the item you want to use.", + label: "Sheet ID", + description: "The ID of the spreadsheet you want to use", async options({ folderId }) { const { value } = await this.listItems({ folderId, }); - return value.filter( (item) => item.file?.mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ).map(({ @@ -35,18 +35,35 @@ export default { })); }, }, - rowId: { + worksheet: { + type: "string", + label: "Worksheet", + description: "The name of the worksheet to use", + async options({ + sheetId, page, + }) { + const limit = DEFAULT_LIMIT; + const { value } = await this.listWorksheets({ + sheetId, + params: { + $top: limit, + $skip: limit * page, + }, + }); + return value.map(({ name }) => name ); + }, + }, + tableRowId: { type: "string", - label: "Row Id", - description: "The Id of the row you want to use.", + label: "Row ID", + description: "The ID of the row you want to use", async options({ - itemId, tableId, + sheetId, tableId, }) { - const { value } = await this.listRows({ - itemId, + const { value } = await this.listTableRows({ + sheetId, tableId, }); - return value.map(({ index: value, values, }) => ({ @@ -57,13 +74,12 @@ export default { }, tableId: { type: "string", - label: "Table Id", - description: "The Id of the table you want to use.", - async options({ itemId }) { + label: "Table ID", + description: "The ID of the table you want to use", + async options({ sheetId }) { const { value } = await this.listTables({ - itemId, + sheetId, }); - return value.map(({ id: value, name: label, }) => ({ @@ -98,12 +114,12 @@ export default { return axios($, config); }, - addRow({ - itemId, tableId, tableName, ...args + addTableRow({ + sheetId, tableId, tableName, ...args }) { return this._makeRequest({ method: "POST", - path: `me/drive/items/${itemId}/workbook/tables/${tableId || tableName}/rows/add`, + path: `me/drive/items/${sheetId}/workbook/tables/${tableId || tableName}/rows/add`, ...args, }); }, @@ -130,29 +146,37 @@ export default { ...args, }); }, - listRows({ - itemId, tableId, ...args + listTableRows({ + sheetId, tableId, ...args }) { return this._makeRequest({ - path: `me/drive/items/${itemId}/workbook/tables/${tableId}/rows`, + path: `me/drive/items/${sheetId}/workbook/tables/${tableId}/rows`, ...args, }); }, // List tables endpoint is not supported for personal accounts listTables({ - itemId, ...args + sheetId, ...args + }) { + return this._makeRequest({ + path: `me/drive/items/${sheetId}/workbook/tables`, + ...args, + }); + }, + listWorksheets({ + sheetId, ...args }) { return this._makeRequest({ - path: `me/drive/items/${itemId}/workbook/tables`, + path: `/me/drive/items/${sheetId}/workbook/worksheets`, ...args, }); }, - updateRow({ - itemId, tableId, rowId, ...args + updateTableRow({ + sheetId, tableId, rowId, ...args }) { return this._makeRequest({ method: "PATCH", - path: `me/drive/items/${itemId}/workbook/tables/${tableId}/rows/itemAt(index=${rowId})`, + path: `me/drive/items/${sheetId}/workbook/tables/${tableId}/rows/ItemAt(index=${rowId})`, ...args, }); }, @@ -172,6 +196,56 @@ export default { ...args, }); }, + getDriveItem({ + itemId, ...args + }) { + return this._makeRequest({ + path: `me/drive/items/${itemId}`, + ...args, + }); + }, + getDelta({ + path, token, ...args + }) { + return this._makeRequest({ + path: `${path}/delta?token=${token}`, + ...args, + }); + }, + getRange({ + sheetId, worksheet, range, ...args + }) { + return this._makeRequest({ + path: `me/drive/items/${sheetId}/workbook/worksheets/${worksheet}/range(address='${range}')`, + ...args, + }); + }, + getUsedRange({ + sheetId, worksheet, ...args + }) { + return this._makeRequest({ + path: `me/drive/items/${sheetId}/workbook/worksheets/${worksheet}/range/usedRange`, + ...args, + }); + }, + insertRange({ + sheetId, worksheet, range, ...args + }) { + return this._makeRequest({ + method: "POST", + path: `me/drive/items/${sheetId}/workbook/worksheets/${worksheet}/range(address='${range}')/insert`, + ...args, + }); + }, + updateRange({ + sheetId, worksheet, range, ...args + }) { + return this._makeRequest({ + method: "PATCH", + path: `me/drive/items/${sheetId}/workbook/worksheets/${worksheet}/range(address='${range}')`, + ...args, + }); + }, async listFolderOptions({ folderId = null, prefix = "", batchLimit = 20, ...args } = {}) { diff --git a/components/microsoft_excel/package.json b/components/microsoft_excel/package.json index 565385837fc08..89e8486239d42 100644 --- a/components/microsoft_excel/package.json +++ b/components/microsoft_excel/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/microsoft_excel", - "version": "0.1.3", + "version": "0.2.0", "description": "Pipedream Microsoft Excel Components", "main": "microsoft_excel.app.mjs", "keywords": [ @@ -14,6 +14,7 @@ }, "dependencies": { "@pipedream/platform": "^3.0.3", + "json-2-csv": "^5.5.9", "moment": "^2.29.4" } } diff --git a/components/microsoft_excel/sources/common/base-polling.mjs b/components/microsoft_excel/sources/common/base-polling.mjs new file mode 100644 index 0000000000000..9ae12f834d039 --- /dev/null +++ b/components/microsoft_excel/sources/common/base-polling.mjs @@ -0,0 +1,39 @@ +import common from "./base.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + ...common, + props: { + ...common.props, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + folderId: { + propDefinition: [ + common.props.microsoftExcel, + "folderId", + ], + }, + sheetId: { + propDefinition: [ + common.props.microsoftExcel, + "sheetId", + ({ folderId }) => ({ + folderId, + }), + ], + }, + worksheet: { + propDefinition: [ + common.props.microsoftExcel, + "worksheet", + ({ sheetId }) => ({ + sheetId, + }), + ], + }, + }, +}; diff --git a/components/microsoft_excel/sources/common/base-webhook.mjs b/components/microsoft_excel/sources/common/base-webhook.mjs new file mode 100644 index 0000000000000..172c0ed9d25a1 --- /dev/null +++ b/components/microsoft_excel/sources/common/base-webhook.mjs @@ -0,0 +1,126 @@ +import moment from "moment"; +import common from "./base.mjs"; + +export default { + ...common, + props: { + ...common.props, + http: { + type: "$.interface.http", + customResponse: true, + }, + folderId: { + propDefinition: [ + common.props.microsoftExcel, + "folderId", + ], + description: "The ID of the folder to watch for changes", + }, + }, + hooks: { + async activate() { + const response = await this.microsoftExcel.createHook({ + data: { + changeType: "updated", + notificationUrl: this.http.endpoint, + resource: "me/drive/root", + expirationDateTime: moment().add(30, "days"), + }, + }); + + this._setHookId(response.id); + + const { token } = await this.getDeltaValues(this.path(), "latest"); + this._setDeltaToken(token); + }, + async deactivate() { + const id = this._getHookId("hookId"); + await this.microsoftExcel.deleteHook(id); + }, + }, + methods: { + ...common.methods, + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + _getDeltaToken() { + return this.db.get("deltaToken"); + }, + _setDeltaToken(token) { + this.db.set("deltaToken", token); + }, + async updateSubscription() { + try { + await this.microsoftExcel.deleteHook(this._getHookId("hookId")); + } catch { + // couldn't find webhook + } + + const { id } = await this.microsoftExcel.createHook({ + data: { + changeType: "updated", + notificationUrl: this.http.endpoint, + resource: "me/drive/root", + expirationDateTime: moment().add(30, "days"), + }, + }); + this._setHookId(id); + }, + path() { + return this.folderId === "root" + ? "me/drive/root" + : `me/drive/items/${this.folderId}`; + }, + async getDeltaValues(path, token) { + const delta = await this.microsoftExcel.getDelta({ + path, + token, + }); + const deltaLink = delta["@odata.deltaLink"] || delta["@odata.nextLink"]; + return { + value: delta?.value, + token: new URL(deltaLink).searchParams.get("token"), + }; + }, + filterRelevantSpreadsheets(spreadsheets) { + return spreadsheets; + }, + }, + async run({ query }) { + if (query.validationToken) { + this.http.respond({ + status: 200, + body: query.validationToken, + headers: { + "content-type": "text/plain", + }, + }); + return; + } + + let deltaValues; + try { + deltaValues = await this.getDeltaValues(this.path(), this._getDeltaToken()); + } catch { + deltaValues = await this.getDeltaValues(this.path(), "latest"); + } + const { + value, token, + } = deltaValues; + + await this.updateSubscription(); + this._setDeltaToken(token); + + let spreadsheets = value.filter(({ file }) => file?.mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + spreadsheets = this.filterRelevantSpreadsheets(spreadsheets); + + if (!spreadsheets?.length) { + return; + } + + spreadsheets.forEach((item) => this.emitEvent(item)); + }, +}; diff --git a/components/microsoft_excel/sources/common/base.mjs b/components/microsoft_excel/sources/common/base.mjs new file mode 100644 index 0000000000000..f4a3d222efdc3 --- /dev/null +++ b/components/microsoft_excel/sources/common/base.mjs @@ -0,0 +1,14 @@ +import microsoftExcel from "../../microsoft_excel.app.mjs"; + +export default { + props: { + microsoftExcel, + db: "$.service.db", + }, + methods: { + emitEvent(item) { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }, + }, +}; diff --git a/components/microsoft_excel/sources/new-cell-value-changed/new-cell-value-changed.mjs b/components/microsoft_excel/sources/new-cell-value-changed/new-cell-value-changed.mjs new file mode 100644 index 0000000000000..25cd33a500645 --- /dev/null +++ b/components/microsoft_excel/sources/new-cell-value-changed/new-cell-value-changed.mjs @@ -0,0 +1,58 @@ +import common from "../common/base-polling.mjs"; + +export default { + ...common, + key: "microsoft_excel-new-cell-value-changed", + name: "New Cell Value Changed", + description: "Emit new event when when a specific cell's value changes in an Excel worksheet", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + cell: { + type: "string", + label: "Cell", + description: "The address of the cell to watch for changes. E.g. `A1`", + }, + }, + methods: { + ...common.methods, + _getCellValue() { + return this.db.get("cellValue"); + }, + _setCellValue(value) { + this.db.set("cellValue", value); + }, + generateMeta(item) { + const ts = Date.now(); + return { + id: `${item.address}${ts}`, + summary: "Cell value updated", + ts, + }; + }, + }, + async run() { + const previousCellValue = this._getCellValue(); + + const response = await this.microsoftExcel.getRange({ + sheetId: this.sheetId, + worksheet: this.worksheet, + range: `${this.cell}:${this.cell}`, + }); + + const value = response.values[0][0]; + + if (value === previousCellValue) { + return; + } + + this._setCellValue(value); + if (previousCellValue) { + response.previousValue = previousCellValue; + } + + this.emitEvent(response); + }, +}; diff --git a/components/microsoft_excel/sources/new-item-created/new-item-created.mjs b/components/microsoft_excel/sources/new-item-created/new-item-created.mjs new file mode 100644 index 0000000000000..511ed0efa63b7 --- /dev/null +++ b/components/microsoft_excel/sources/new-item-created/new-item-created.mjs @@ -0,0 +1,49 @@ +import common from "../common/base-webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + dedupe: "unique", + key: "microsoft_excel-new-item-created", + name: "New Spreadsheet Created (Instant)", + description: "Emit new event when a new Excel spreadsheet is created.", + version: "0.0.1", + type: "source", + hooks: { + ...common.hooks, + async deploy() { + this._setLastCreatedTs(Date.now()); + }, + }, + methods: { + ...common.methods, + _getLastCreatedTs() { + return this.db.get("lastCreatedTs"); + }, + _setLastCreatedTs(lastCreatedTs) { + this.db.set("lastCreatedTs", lastCreatedTs); + }, + filterRelevantSpreadsheets(spreadsheets) { + const lastCreatedTs = this._getLastCreatedTs(); + let maxTs = lastCreatedTs; + const relevant = []; + for (const spreadsheet of spreadsheets) { + const ts = Date.parse(spreadsheet.createdDateTime); + if (ts > lastCreatedTs) { + relevant.push(spreadsheet); + maxTs = Math.max(ts, maxTs); + } + } + this._setLastCreatedTs(maxTs); + return relevant; + }, + generateMeta(item) { + return { + id: `${item.id}`, + summary: `Item ${item.name} created`, + ts: Date.parse(item.createdDateTime), + }; + }, + }, + sampleEmit, +}; diff --git a/components/microsoft_excel/sources/new-item-created/test-event.mjs b/components/microsoft_excel/sources/new-item-created/test-event.mjs new file mode 100644 index 0000000000000..f9a1165a44a24 --- /dev/null +++ b/components/microsoft_excel/sources/new-item-created/test-event.mjs @@ -0,0 +1,46 @@ +export default { + "@microsoft.graph.downloadUrl": "", + "createdDateTime": "2025-04-17T21:06:34.207Z", + "cTag": "", + "eTag": "", + "id": "", + "lastModifiedDateTime": "2025-04-17T21:06:37.6Z", + "name": "Book.xlsx", + "size": 8176, + "webUrl": "", + "reactions": { + "commentCount": 0 + }, + "createdBy": { + "application": { + "id": "" + }, + "user": { + "displayName": "", + "id": "" + } + }, + "lastModifiedBy": { + "user": { + "displayName": "", + "id": "" + } + }, + "parentReference": { + "driveId": "", + "driveType": "personal", + "id": "", + "name": "Documents", + "path": "/drive/root:/Documents" + }, + "file": { + "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "hashes": { + "quickXorHash": "" + } + }, + "fileSystemInfo": { + "createdDateTime": "2025-04-17T21:06:34.206Z", + "lastModifiedDateTime": "2025-04-17T21:06:37.583Z" + } +} \ No newline at end of file diff --git a/components/microsoft_excel/sources/new-item-updated/new-item-updated.mjs b/components/microsoft_excel/sources/new-item-updated/new-item-updated.mjs index b0560234f0b72..643ae7e823588 100644 --- a/components/microsoft_excel/sources/new-item-updated/new-item-updated.mjs +++ b/components/microsoft_excel/sources/new-item-updated/new-item-updated.mjs @@ -1,89 +1,24 @@ -import moment from "moment"; -import microsoftExcel from "../../microsoft_excel.app.mjs"; +import common from "../common/base-webhook.mjs"; import sampleEmit from "./test-event.mjs"; export default { + ...common, dedupe: "unique", key: "microsoft_excel-new-item-updated", - name: "New Item Updated (Instant)", - description: "Emit new event when an item is updated.", - version: "0.0.4", + name: "New Spreadsheet Updated (Instant)", + description: "Emit new event when an Excel spreadsheet is updated.", + version: "0.0.5", type: "source", - props: { - microsoftExcel, - http: { - type: "$.interface.http", - customResponse: true, - }, - db: "$.service.db", - }, - hooks: { - async activate() { - const response = await this.microsoftExcel.createHook({ - data: { - changeType: "updated", - notificationUrl: this.http.endpoint, - resource: "me/drive/root", - expirationDateTime: moment().add(30, "days"), - }, - }); - - this._setHookId(response.id); - }, - async deactivate() { - const id = this._getHookId("hookId"); - await this.microsoftExcel.deleteHook(id); - }, - }, methods: { - emitEvent(body) { - const meta = this.generateMeta(body); - this.$emit(body, meta); - }, - _getHookId() { - return this.db.get("hookId"); - }, - _setHookId(hookId) { - this.db.set("hookId", hookId); - }, - generateMeta(body) { - const { value } = body; - - const ts = new Date(); + ...common.methods, + generateMeta(item) { + const ts = Date.parse(item.lastModifiedDateTime); return { - id: `${value[0].tenantId}${ts}`, - summary: `The item ${value[0].tenantId - ? `with TenantId: ${value[0].tenantId} ` - : " "}was updated!`, - ts: ts, + id: `${item.id}${ts}`, + summary: `Item ${item.name} was updated`, + ts, }; }, - async updateSubscription() { - const hookId = this._getHookId(); - await this.microsoftExcel.updateSubscription({ - hookId, - data: { - expirationDateTime: moment().add(30, "days"), - }, - }); - }, - }, - async run({ - body, query, - }) { - if (query.validationToken) { - this.http.respond({ - status: 200, - body: query.validationToken, - headers: { - "content-type": "text/plan", - }, - }); - return; - } - - this.emitEvent(body); - await this.updateSubscription(); }, sampleEmit, }; diff --git a/components/microsoft_excel/sources/new-item-updated/test-event.mjs b/components/microsoft_excel/sources/new-item-updated/test-event.mjs index e00e7ea6c9307..9d8788646145b 100644 --- a/components/microsoft_excel/sources/new-item-updated/test-event.mjs +++ b/components/microsoft_excel/sources/new-item-updated/test-event.mjs @@ -1,11 +1,49 @@ export default { - "value": [{ - "subscriptionId": "109f1232-d794-1122-aabc-e12345afa726", - "clientState": null, - "resource": "me/drive/root", - "tenantId": "109f1232-d794-1122-aabc-e12345afa726", - "resourceData": null, - "subscriptionExpirationDateTime": "2024-10-22T12:02:12.637+00:00", - "changeType": "updated" - }] + "@microsoft.graph.downloadUrl": "", + "createdDateTime": "2024-08-02T18:04:17.28Z", + "cTag": "", + "eTag": "", + "id": "", + "lastModifiedDateTime": "2025-04-17T19:19:55.427Z", + "name": "Book.xlsx", + "size": 10367, + "webUrl": "", + "reactions": { + "commentCount": 0 + }, + "createdBy": { + "application": { + "id": "" + }, + "user": { + "displayName": "", + "id": "" + } + }, + "lastModifiedBy": { + "application": { + "id": "" + }, + "user": { + "displayName": "", + "id": "" + } + }, + "parentReference": { + "driveId": "", + "driveType": "personal", + "id": "", + "name": "root:", + "path": "/drive/root:" + }, + "file": { + "mimeType": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "hashes": { + "quickXorHash": "" + } + }, + "fileSystemInfo": { + "createdDateTime": "2024-08-02T18:04:17.28Z", + "lastModifiedDateTime": "2025-04-17T19:19:55.103Z" + } } \ No newline at end of file diff --git a/components/microsoft_excel/sources/new-row-added/new-row-added.mjs b/components/microsoft_excel/sources/new-row-added/new-row-added.mjs new file mode 100644 index 0000000000000..b0140cb0f80f7 --- /dev/null +++ b/components/microsoft_excel/sources/new-row-added/new-row-added.mjs @@ -0,0 +1,47 @@ +import common from "../common/base-polling.mjs"; + +export default { + ...common, + key: "microsoft_excel-new-row-added", + name: "New Row Added", + description: "Emit new event when when a new row is added to an Excel worksheet", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + _getRowCount() { + return this.db.get("rowCount"); + }, + _setRowCount(value) { + this.db.set("rowCount", value); + }, + generateMeta(item) { + const ts = Date.now(); + return { + id: `${item.address}${ts}`, + summary: `${item.numRowsAdded} row(s) added`, + ts, + }; + }, + }, + async run() { + const previousRowCount = this._getRowCount(); + + const response = await this.microsoftExcel.getUsedRange({ + sheetId: this.sheetId, + worksheet: this.worksheet, + }); + + const { rowCount } = response; + + this._setRowCount(rowCount); + + if (!previousRowCount || rowCount <= previousRowCount) { + return; + } + + response.numRowsAdded = rowCount - previousRowCount; + this.emitEvent(response); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29c6699ad1c74..1572a9b7a30d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7986,6 +7986,9 @@ importers: '@pipedream/platform': specifier: ^3.0.3 version: 3.0.3 + json-2-csv: + specifier: ^5.5.9 + version: 5.5.9 moment: specifier: ^2.29.4 version: 2.30.1 @@ -21837,6 +21840,10 @@ packages: resolution: {integrity: sha512-PZrpz5xLo2JPZa3L+kqMMMdZU5pRwMysTM1xd6pLhNtgQw4Iq3wbF2QWaQTVh+HRq9Yg4rcjDIJ+scfGLxmsjQ==} engines: {node: '>= 12'} + deeks@3.1.0: + resolution: {integrity: sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==} + engines: {node: '>= 16'} + deep-assign@3.0.0: resolution: {integrity: sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==} engines: {node: '>=0.10.0'} @@ -22017,6 +22024,10 @@ packages: resolution: {integrity: sha512-Pv2hLQbUM8du5681lTWIYk0OtVBmNhMAeZNGeFhMMJBIR89Nw4XesBwee1Xtlfk83n71tn0Y6VsJOn4d3qIiTw==} engines: {node: '>=12'} + doc-path@4.1.1: + resolution: {integrity: sha512-h1ErTglQAVv2gCnOpD3sFS6uolDbOKHDU1BZq+Kl3npPqroU3dYL42lUgMfd5UimlwtRgp7C9dLGwqQ5D2HYgQ==} + engines: {node: '>=16'} + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -24456,6 +24467,10 @@ packages: resolution: {integrity: sha512-IbqUB+yaycVNB/q2fiY5kyRjy5kRiEXqvNvGlxM5L0Bfi0RdvklVHc4t9MfeYF1GsZVpZWDBs9LdWmSjsQ8jvg==} engines: {node: '>= 12'} + json-2-csv@5.5.9: + resolution: {integrity: sha512-l4g6GZVHrsN+5SKkpOmGNSvho+saDZwXzj/xmcO0lJAgklzwsiqy70HS5tA9djcRvBEybZ9IF6R1MDFTEsaOGQ==} + engines: {node: '>= 16'} + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -38754,6 +38769,8 @@ snapshots: deeks@2.6.1: {} + deeks@3.1.0: {} + deep-assign@3.0.0: dependencies: is-obj: 1.0.1 @@ -38930,6 +38947,8 @@ snapshots: doc-path@3.1.0: {} + doc-path@4.1.1: {} + doctrine@2.1.0: dependencies: esutils: 2.0.3 @@ -42359,6 +42378,11 @@ snapshots: deeks: 2.6.1 doc-path: 3.1.0 + json-2-csv@5.5.9: + dependencies: + deeks: 3.1.0 + doc-path: 4.1.1 + json-bigint@1.0.0: dependencies: bignumber.js: 9.1.2