diff --git a/packages/pieces/community/baserow/package.json b/packages/pieces/community/baserow/package.json index bab3016eeedb..32b4e05b8e74 100644 --- a/packages/pieces/community/baserow/package.json +++ b/packages/pieces/community/baserow/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-baserow", - "version": "0.10.0", + "version": "0.11.0", "main": "./dist/src/index.js", "types": "./dist/src/index.d.ts", "scripts": { diff --git a/packages/pieces/community/baserow/src/index.ts b/packages/pieces/community/baserow/src/index.ts index 8613e1ba4514..7987f6cedd7a 100644 --- a/packages/pieces/community/baserow/src/index.ts +++ b/packages/pieces/community/baserow/src/index.ts @@ -16,6 +16,38 @@ import { batchUpdateRowsAction } from './lib/actions/batch-update-rows'; import { batchDeleteRowsAction } from './lib/actions/batch-delete-rows'; import { upsertRowAction } from './lib/actions/upsert-row'; import { uploadFileAction } from './lib/actions/upload-file'; +import { createRowAiAction } from './lib/actions/create-row-ai'; +import { getRowAiAction } from './lib/actions/get-row-ai'; +import { listRowsAiAction } from './lib/actions/list-rows-ai'; +import { findRowAiAction } from './lib/actions/find-row-ai'; +import { updateRowAiAction } from './lib/actions/update-row-ai'; +import { upsertRowAiAction } from './lib/actions/upsert-row-ai'; +import { deleteRowAiAction } from './lib/actions/delete-row-ai'; +import { batchCreateRowsAiAction } from './lib/actions/batch-create-rows-ai'; +import { batchUpdateRowsAiAction } from './lib/actions/batch-update-rows-ai'; +import { batchDeleteRowsAiAction } from './lib/actions/batch-delete-rows-ai'; +import { aggregateFieldAiAction } from './lib/actions/aggregate-field-ai'; +import { listWorkspacesAction } from './lib/actions/list-workspaces'; +import { listDatabasesAction } from './lib/actions/list-databases'; +import { listTablesAction } from './lib/actions/list-tables'; +import { getTableAction } from './lib/actions/get-table'; +import { getTableFieldsAction } from './lib/actions/get-table-fields'; +import { listViewsAction } from './lib/actions/list-views'; +import { listRowNamesAction } from './lib/actions/list-row-names'; +import { getFieldUniqueValuesAction } from './lib/actions/get-field-unique-values'; +import { listWorkspaceUsersAction } from './lib/actions/list-workspace-users'; +import { searchWorkspaceAction } from './lib/actions/search-workspace'; +import { createDatabaseAction } from './lib/actions/create-database'; +import { createTableAction } from './lib/actions/create-table'; +import { updateTableAction } from './lib/actions/update-table'; +import { deleteTableAction } from './lib/actions/delete-table'; +import { createFieldAction } from './lib/actions/create-field'; +import { updateFieldAction } from './lib/actions/update-field'; +import { deleteFieldAction } from './lib/actions/delete-field'; +import { createViewAction } from './lib/actions/create-view'; +import { getRowHistoryAction } from './lib/actions/get-row-history'; +import { exportTableAction } from './lib/actions/export-table'; +import { getExportJobAction } from './lib/actions/get-export-job'; import { rowCreatedTrigger } from './lib/triggers/row-created'; import { rowUpdatedTrigger } from './lib/triggers/row-updated'; import { rowDeletedTrigger } from './lib/triggers/row-deleted'; @@ -45,7 +77,7 @@ export const baserow = createPiece({ displayName: 'Baserow', description: 'Open-source online database tool, alternative to Airtable', auth: baserowAuth, - minimumSupportedRelease: '0.30.0', + minimumSupportedRelease: '0.88.2', logoUrl: 'https://cdn.activepieces.com/pieces/baserow.png', categories: [PieceCategory.PRODUCTIVITY], authors: ["kishanprmr", "MoShizzle", "abuaboud", 'bst1n', 'sanket-a11y', 'onyedikachi-david'], @@ -63,6 +95,38 @@ export const baserow = createPiece({ batchDeleteRowsAction, aggregateFieldAction, uploadFileAction, + createRowAiAction, + getRowAiAction, + listRowsAiAction, + findRowAiAction, + updateRowAiAction, + upsertRowAiAction, + deleteRowAiAction, + batchCreateRowsAiAction, + batchUpdateRowsAiAction, + batchDeleteRowsAiAction, + aggregateFieldAiAction, + listWorkspacesAction, + listDatabasesAction, + listTablesAction, + getTableAction, + getTableFieldsAction, + listViewsAction, + listRowNamesAction, + getFieldUniqueValuesAction, + listWorkspaceUsersAction, + searchWorkspaceAction, + createDatabaseAction, + createTableAction, + updateTableAction, + deleteTableAction, + createFieldAction, + updateFieldAction, + deleteFieldAction, + createViewAction, + getRowHistoryAction, + exportTableAction, + getExportJobAction, createCustomApiCallAction({ baseUrl: (auth) => { if (!auth) { diff --git a/packages/pieces/community/baserow/src/lib/actions/aggregate-field-ai.ts b/packages/pieces/community/baserow/src/lib/actions/aggregate-field-ai.ts new file mode 100644 index 000000000000..4edbaeee5fbb --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/aggregate-field-ai.ts @@ -0,0 +1,61 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { aggregateFieldAiOutputSchema } from '../output-schemas'; + +export const aggregateFieldAiAction = createAction({ + name: 'baserow_aggregate_field_ai', + classification: 'READ', + outputSchema: aggregateFieldAiOutputSchema, + displayName: 'Aggregate Field', + description: 'Computes an aggregate over one field in a grid view.', + audience: 'ai', + aiMetadata: { + description: + 'Computes one aggregate (sum, average, min, max, median, std dev, variance, decile, distribution, or empty/non-empty/unique counts) over a field across all rows visible in a Baserow grid view. Use for totals and metrics instead of paging through rows. Numeric aggregations need a number field. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + view_id: Property.Number({ + displayName: 'Grid View ID', + description: 'ID of a grid view on the table. Resolve with List Views.', + required: true, + }), + field_id: baserowAiProps.fieldIdProp(), + aggregation_type: Property.StaticDropdown({ + displayName: 'Aggregation Type', + description: 'The calculation to run.', + required: true, + options: { + disabled: false, + options: [ + { label: 'Sum', value: 'sum' }, + { label: 'Average', value: 'average' }, + { label: 'Min', value: 'min' }, + { label: 'Max', value: 'max' }, + { label: 'Median', value: 'median' }, + { label: 'Standard deviation', value: 'std_dev' }, + { label: 'Variance', value: 'variance' }, + { label: 'Decile', value: 'decile' }, + { label: 'Distribution', value: 'distribution' }, + { label: 'Count', value: 'count' }, + { label: 'Count (empty)', value: 'empty_count' }, + { label: 'Count (non-empty)', value: 'not_empty_count' }, + { label: 'Count (unique values)', value: 'unique_count' }, + ], + }, + }), + }, + async run(context) { + const { view_id, field_id, aggregation_type } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Aggregate Field' }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => + client.aggregateField(view_id, field_id, aggregation_type) + ); + return { aggregation_type, value: response.value }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/aggregate-field.ts b/packages/pieces/community/baserow/src/lib/actions/aggregate-field.ts index 2d2bbaede431..d2525a888262 100644 --- a/packages/pieces/community/baserow/src/lib/actions/aggregate-field.ts +++ b/packages/pieces/community/baserow/src/lib/actions/aggregate-field.ts @@ -5,14 +5,16 @@ import { } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { aggregateFieldOutputSchema } from '../output-schemas'; export const aggregateFieldAction = createAction({ name: 'baserow_aggregate_field', classification: 'READ', + outputSchema: aggregateFieldOutputSchema, displayName: 'Aggregate Field', description: 'Calculates an aggregation (sum, average, min, max, count, etc.) over all values of a field in a grid view.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Computes a single aggregate value (sum, average, min, max, median, std dev, variance, or empty/non-empty/unique counts) over one field across all rows of a Baserow grid view. Use for roll-up metrics over a whole view rather than fetching rows yourself; numeric aggregations require a number field. Requires a grid view ID and a field ID. Read-only and idempotent.', diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-create-rows-ai.ts b/packages/pieces/community/baserow/src/lib/actions/batch-create-rows-ai.ts new file mode 100644 index 000000000000..5a8698e0100c --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/batch-create-rows-ai.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { batchRowsOutputSchema } from '../output-schemas'; + +export const batchCreateRowsAiAction = createAction({ + name: 'baserow_batch_create_rows_ai', + classification: 'WRITE', + outputSchema: batchRowsOutputSchema, + displayName: 'Batch Create Rows', + description: 'Creates up to 200 rows in one request.', + audience: 'ai', + aiMetadata: { + description: + 'Creates up to 200 new rows in a Baserow table in a single request from a JSON array of field-name-to-value objects. Use for bulk inserts instead of repeated Create Row calls; split larger sets into chunks of 200. Not idempotent — re-running appends the rows again.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + rows: Property.Json({ + displayName: 'Rows', + description: + 'JSON array of row objects keyed by field name, e.g. [{"Name": "A"}, {"Name": "B"}]. Maximum 200. Value formats match Create Row.', + required: true, + }), + }, + async run(context) { + const { table_id, rows } = context.propsValue; + const items = baserowAiHelpers.toRecordArray({ + value: rows, + propName: 'Rows', + max: baserowAiHelpers.BATCH_LIMIT, + }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => client.batchCreateRows(table_id, items)); + return { count: response.items.length, rows: response.items }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-create-rows.ts b/packages/pieces/community/baserow/src/lib/actions/batch-create-rows.ts index fd9dbdd44a4c..ae450fa2f5db 100644 --- a/packages/pieces/community/baserow/src/lib/actions/batch-create-rows.ts +++ b/packages/pieces/community/baserow/src/lib/actions/batch-create-rows.ts @@ -1,14 +1,16 @@ import { Property, createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { batchRowsOutputSchema } from '../output-schemas'; export const batchCreateRowsAction = createAction({ name: 'baserow_batch_create_rows', classification: 'WRITE', + outputSchema: batchRowsOutputSchema, displayName: 'Batch Create Rows', description: 'Creates multiple rows in a single request. Accepts up to 200 rows.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Creates many new rows in a Baserow table in one request from a JSON array of row objects (field names as keys), up to 200 rows per call. Use for bulk inserts instead of repeated Create Row calls. Not idempotent — each call appends another set of rows, so it can produce duplicates if re-run.', diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows-ai.ts b/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows-ai.ts new file mode 100644 index 000000000000..a081b52ca917 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows-ai.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { batchDeleteRowsAiOutputSchema } from '../output-schemas'; + +export const batchDeleteRowsAiAction = createAction({ + name: 'baserow_batch_delete_rows_ai', + classification: 'DESTRUCTIVE', + outputSchema: batchDeleteRowsAiOutputSchema, + displayName: 'Batch Delete Rows', + description: 'Deletes up to 200 rows in one request.', + audience: 'ai', + aiMetadata: { + description: + 'Deletes up to 200 rows from a Baserow table by row ID in one request; the rows move to the Baserow trash and can be restored there. Use instead of repeated Delete Row calls. Does not fire row-deleted webhooks. Not idempotent — a repeat call fails for IDs that are already gone.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_ids: Property.Array({ + displayName: 'Row IDs', + description: 'Numeric IDs of the rows to delete. Maximum 200.', + required: true, + }), + }, + async run(context) { + const { table_id, row_ids } = context.propsValue; + const ids = baserowAiHelpers.toIdArray({ + value: row_ids, + propName: 'Row IDs', + max: baserowAiHelpers.BATCH_LIMIT, + }); + const client = await makeClient(context.auth); + await baserowAiHelpers.execute(() => client.batchDeleteRows(table_id, ids)); + return { deleted: true, count: ids.length, row_ids: ids }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows.ts b/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows.ts index 5b8792ff154d..286159c523f4 100644 --- a/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows.ts +++ b/packages/pieces/community/baserow/src/lib/actions/batch-delete-rows.ts @@ -1,14 +1,16 @@ import { Property, createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { batchDeleteRowsOutputSchema } from '../output-schemas'; export const batchDeleteRowsAction = createAction({ name: 'baserow_batch_delete_rows', classification: 'DESTRUCTIVE', + outputSchema: batchDeleteRowsOutputSchema, displayName: 'Batch Delete Rows', description: 'Deletes multiple rows in a single request. Accepts up to 200 row IDs.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Permanently deletes many Baserow rows in one request from a list of numeric row IDs, up to 200 per call. Use for bulk removal of known rows; for a single row use Delete Row. Destructive — verify the IDs first (List Rows or Find Row can supply them). Not idempotent against changing data once the rows are removed.', diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-update-rows-ai.ts b/packages/pieces/community/baserow/src/lib/actions/batch-update-rows-ai.ts new file mode 100644 index 000000000000..f22ec8010d5e --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/batch-update-rows-ai.ts @@ -0,0 +1,45 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { batchRowsOutputSchema } from '../output-schemas'; + +export const batchUpdateRowsAiAction = createAction({ + name: 'baserow_batch_update_rows_ai', + classification: 'WRITE', + outputSchema: batchRowsOutputSchema, + displayName: 'Batch Update Rows', + description: 'Updates up to 200 rows in one request.', + audience: 'ai', + aiMetadata: { + description: + 'Updates up to 200 existing Baserow rows in one request; each object needs the row "id" plus only the fields to change, and omitted fields are left as they are. Use for bulk edits instead of repeated Update Row calls. Idempotent — repeating the same values converges on the same state.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + rows: Property.Json({ + displayName: 'Rows', + description: + 'JSON array of objects, each with the numeric row "id" and the field names to change, e.g. [{"id": 1, "Status": "Done"}]. Maximum 200.', + required: true, + }), + }, + async run(context) { + const { table_id, rows } = context.propsValue; + const items = baserowAiHelpers.toRecordArray({ + value: rows, + propName: 'Rows', + max: baserowAiHelpers.BATCH_LIMIT, + }); + const missingId = items.findIndex((item) => !Number.isInteger(Number(item['id']))); + if (missingId !== -1) { + throw new Error(`Rows[${missingId}] is missing a numeric "id".`); + } + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => client.batchUpdateRows(table_id, items)); + return { count: response.items.length, rows: response.items }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/batch-update-rows.ts b/packages/pieces/community/baserow/src/lib/actions/batch-update-rows.ts index c18b8c4e3da9..9d77b0fb1964 100644 --- a/packages/pieces/community/baserow/src/lib/actions/batch-update-rows.ts +++ b/packages/pieces/community/baserow/src/lib/actions/batch-update-rows.ts @@ -1,14 +1,16 @@ import { Property, createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { batchRowsOutputSchema } from '../output-schemas'; export const batchUpdateRowsAction = createAction({ name: 'baserow_batch_update_rows', classification: 'WRITE', + outputSchema: batchRowsOutputSchema, displayName: 'Batch Update Rows', description: 'Updates multiple rows in a single request. Each row must include an "id" field. Accepts up to 200 rows.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Updates many existing Baserow rows in one request from a JSON array, up to 200 rows per call. Every object must carry an "id" identifying the row plus the fields to change. Use for bulk edits to known rows instead of repeated Update Row calls. Idempotent: re-sending the same id/field values converges those rows to the same state.', diff --git a/packages/pieces/community/baserow/src/lib/actions/clean-row.ts b/packages/pieces/community/baserow/src/lib/actions/clean-row.ts index e0b92455a66d..fc8bd2520fcc 100644 --- a/packages/pieces/community/baserow/src/lib/actions/clean-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/clean-row.ts @@ -1,14 +1,16 @@ import { createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, formatFieldValues, makeClient } from '../common'; +import { rowOutputSchema } from '../output-schemas'; export const cleanRowAction = createAction({ name: 'baserow_clean_row', classification: 'DESTRUCTIVE', + outputSchema: rowOutputSchema, displayName: 'Clean Row', description: 'Sets all fields in a row to empty/null. To update only specific fields, use Update Row instead.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Clears the supplied fields on one existing Baserow row (identified by row ID) to empty/null, including blank values that Update Row would skip. Use specifically to wipe field contents rather than set new values; to change only some fields to new values use Update Row. Idempotent: re-clearing the same row leaves it in the same empty state.', diff --git a/packages/pieces/community/baserow/src/lib/actions/create-database.ts b/packages/pieces/community/baserow/src/lib/actions/create-database.ts new file mode 100644 index 000000000000..014a854f3b45 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-database.ts @@ -0,0 +1,38 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { createDatabaseOutputSchema } from '../output-schemas'; + +export const createDatabaseAction = createAction({ + name: 'baserow_create_database', + classification: 'WRITE', + outputSchema: createDatabaseOutputSchema, + displayName: 'Create Database', + description: 'Creates an empty database in a workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Creates a new, empty Baserow database in a workspace and returns its ID. Follow with Create Table to add tables. Requires an Email & Password connection. Not idempotent — each call creates another database.', + idempotent: false, + }, + auth: baserowAuth, + props: { + workspace_id: baserowAiProps.workspaceIdProp(), + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the new database (max 160 characters).', + required: true, + }), + }, + async run(context) { + const { workspace_id, name } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Create Database' }); + const client = await makeClient(context.auth); + const database = await baserowAiHelpers.execute(() => + client.createDatabase({ workspaceId: workspace_id, name }) + ); + return { id: database['id'], name: database['name'], workspace: database['workspace'] }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/create-field.ts b/packages/pieces/community/baserow/src/lib/actions/create-field.ts new file mode 100644 index 000000000000..c560f33e77e7 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-field.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { fieldOutputSchema } from '../output-schemas'; + +export const createFieldAction = createAction({ + name: 'baserow_create_field', + classification: 'WRITE', + outputSchema: fieldOutputSchema, + displayName: 'Create Field', + description: 'Adds a field (column) to a table.', + audience: 'ai', + aiMetadata: { + description: + 'Adds a new field to a Baserow table with a given name and type, plus optional type-specific settings (select options, number decimals, linked table, formula). Use Get Table Fields first to avoid duplicate names. Requires an Email & Password connection — Database Tokens cannot create fields. Not idempotent — a repeat call fails on the duplicate name.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the new field. Must be unique in the table.', + required: true, + }), + type: Property.ShortText({ + displayName: 'Type', + description: + 'Baserow field type, e.g. text, long_text, number, boolean, date, email, url, phone_number, rating, single_select, multiple_select, link_row, file, formula, duration, autonumber.', + required: true, + }), + options: Property.Json({ + displayName: 'Type Options', + description: + 'Optional JSON object of type-specific settings, e.g. {"number_decimal_places": 2}, {"select_options": [{"value": "Open", "color": "blue"}]}, {"link_row_table_id": 12}, {"formula": "field(\'Price\') * 2"}, {"date_include_time": true}.', + required: false, + }), + }, + async run(context) { + const { table_id, name, type, options } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Create Field' }); + const extra = options ? baserowAiHelpers.toRecord({ value: options, propName: 'Type Options' }) : {}; + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => + client.createField({ tableId: table_id, body: { ...extra, name, type } }) + ); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/create-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/create-row-ai.ts new file mode 100644 index 000000000000..524c9c6e09d6 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-row-ai.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { ensureSelectOptionsExist, makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { rowOutputSchema } from '../output-schemas'; + +export const createRowAiAction = createAction({ + name: 'baserow_create_row_ai', + classification: 'WRITE', + outputSchema: rowOutputSchema, + displayName: 'Create Row', + description: 'Creates a new row in a table from a JSON map of field values.', + audience: 'ai', + aiMetadata: { + description: + 'Creates one new row in a Baserow table from a JSON object of field name to value. Call Get Table Fields first to learn field names and types; for many rows use Batch Create Rows, and to avoid duplicates by a key field use Upsert Row. Not idempotent — each call inserts another row.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + fields: Property.Json({ + displayName: 'Fields', + description: + 'JSON object mapping field names to values, e.g. {"Name": "Acme", "Status": "Active"}. Single select takes the option name or ID; multiple select, link-to-table and collaborator fields take arrays (link rows accept row IDs or primary field values; collaborators take [{"id": 1}]). File fields take [{"name": ""}].', + required: true, + }), + create_missing_select_options: Property.Checkbox({ + displayName: 'Create Missing Select Options', + description: + 'Adds single/multiple select values that do not exist yet as new options before writing. Requires an Email & Password connection.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { table_id, fields, create_missing_select_options } = context.propsValue; + baserowAiHelpers.assertSelectOptionsAllowed({ + auth: context.auth, + createMissingSelectOptions: create_missing_select_options, + }); + const payload = baserowAiHelpers.toRecord({ value: fields, propName: 'Fields' }); + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(async () => { + if (create_missing_select_options) { + await ensureSelectOptionsExist({ + fields: await client.listTableFields(table_id), + payload, + client, + }); + } + return await client.createRow(table_id, payload); + }); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/create-row.ts b/packages/pieces/community/baserow/src/lib/actions/create-row.ts index d7c8b8bf175d..214f6052b56c 100644 --- a/packages/pieces/community/baserow/src/lib/actions/create-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/create-row.ts @@ -6,13 +6,15 @@ import { formatFieldValues, makeClient, } from '../common'; +import { rowOutputSchema } from '../output-schemas'; export const createRowAction = createAction({ name: 'baserow_create_row', classification: 'WRITE', + outputSchema: rowOutputSchema, displayName: 'Create Row', description: 'Creates a new row in a table.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Creates one new row in a chosen Baserow table, setting field values by field name. Use to add a single record; for many rows at once use Batch Create Rows, and to avoid duplicates by a key field use Upsert Row. Not idempotent — each call inserts another row. Optionally auto-creates missing single/multi-select options referenced in the payload.', diff --git a/packages/pieces/community/baserow/src/lib/actions/create-table.ts b/packages/pieces/community/baserow/src/lib/actions/create-table.ts new file mode 100644 index 000000000000..d05d2daeb389 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-table.ts @@ -0,0 +1,67 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { createTableOutputSchema } from '../output-schemas'; + +export const createTableAction = createAction({ + name: 'baserow_create_table', + classification: 'WRITE', + outputSchema: createTableOutputSchema, + displayName: 'Create Table', + description: 'Creates a table in a database, optionally seeded with rows.', + audience: 'ai', + aiMetadata: { + description: + 'Creates a new table in a Baserow database. Pass Data as rows of cell values; with First Row Is Header the first row becomes the field names and all fields are created as text. Without Data the table gets a single "Name" field and no rows. Add typed fields afterwards with Create Field. Requires an Email & Password connection. Not idempotent — each call creates another table.', + idempotent: false, + }, + auth: baserowAuth, + props: { + database_id: Property.Number({ + displayName: 'Database ID', + description: 'The database to create the table in. Resolve with List Databases.', + required: true, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the new table.', + required: true, + }), + data: Property.Json({ + displayName: 'Data', + description: + 'Optional JSON array of rows, each an array of cell values, e.g. [["Name", "Email"], ["Ada", "ada@example.com"]].', + required: false, + }), + first_row_header: Property.Checkbox({ + displayName: 'First Row Is Header', + description: 'Use the first row of Data as the field names.', + required: false, + defaultValue: true, + }), + }, + async run(context) { + const { database_id, name, data, first_row_header } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Create Table' }); + const rows = data === undefined || data === null ? [['Name']] : toRows({ value: data }); + const firstRowHeader = data === undefined || data === null ? true : first_row_header ?? true; + const client = await makeClient(context.auth); + const table = await baserowAiHelpers.execute(() => + client.createTable({ databaseId: database_id, name, data: rows, firstRowHeader }) + ); + return { id: table['id'], name: table['name'], database_id: table['database_id'] }; + }, +}); + +function toRows({ value }: { value: unknown }): unknown[][] { + if (!Array.isArray(value) || value.length === 0) { + throw new Error('Data must be a non-empty JSON array of rows.'); + } + return value.map((row, index) => { + if (!Array.isArray(row)) { + throw new Error(`Data[${index}] must be an array of cell values.`); + } + return row; + }); +} diff --git a/packages/pieces/community/baserow/src/lib/actions/create-view.ts b/packages/pieces/community/baserow/src/lib/actions/create-view.ts new file mode 100644 index 000000000000..b27254e85be3 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/create-view.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { createViewOutputSchema } from '../output-schemas'; + +export const createViewAction = createAction({ + name: 'baserow_create_view', + classification: 'WRITE', + outputSchema: createViewOutputSchema, + displayName: 'Create View', + description: 'Creates a grid, gallery or form view on a table.', + audience: 'ai', + aiMetadata: { + description: + 'Creates a new grid, gallery or form view on a Baserow table and returns its ID. Use when a separate saved view is needed, for example a grid view to aggregate over. Requires an Email & Password connection. Not idempotent — each call creates another view.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + name: Property.ShortText({ + displayName: 'Name', + description: 'Name of the new view.', + required: true, + }), + type: Property.StaticDropdown({ + displayName: 'Type', + description: 'The kind of view to create.', + required: true, + defaultValue: 'grid', + options: { + disabled: false, + options: [ + { label: 'Grid', value: 'grid' }, + { label: 'Gallery', value: 'gallery' }, + { label: 'Form', value: 'form' }, + ], + }, + }), + }, + async run(context) { + const { table_id, name, type } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Create View' }); + const client = await makeClient(context.auth); + const view = await baserowAiHelpers.execute(() => + client.createView({ tableId: table_id, name, type }) + ); + return { id: view['id'], name: view['name'], type: view['type'], table_id }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/delete-field.ts b/packages/pieces/community/baserow/src/lib/actions/delete-field.ts new file mode 100644 index 000000000000..003127771692 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/delete-field.ts @@ -0,0 +1,31 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { deleteFieldOutputSchema } from '../output-schemas'; + +export const deleteFieldAction = createAction({ + name: 'baserow_delete_field', + classification: 'DESTRUCTIVE', + outputSchema: deleteFieldOutputSchema, + displayName: 'Delete Field', + description: 'Deletes a field and its data from a table.', + audience: 'ai', + aiMetadata: { + description: + 'Deletes a Baserow field and every value stored in it; the field moves to the Baserow trash and can be restored there. The primary field cannot be deleted. Requires an Email & Password connection. Not idempotent — a repeat call fails because the field is gone.', + idempotent: false, + }, + auth: baserowAuth, + props: { + field_id: baserowAiProps.fieldIdProp(), + }, + async run(context) { + const { field_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Delete Field' }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => client.deleteField({ fieldId: field_id })); + return { deleted: true, field_id, related_fields: response['related_fields'] ?? [] }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/delete-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/delete-row-ai.ts new file mode 100644 index 000000000000..aea0d185d329 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/delete-row-ai.ts @@ -0,0 +1,31 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { deleteRowAiOutputSchema } from '../output-schemas'; + +export const deleteRowAiAction = createAction({ + name: 'baserow_delete_row_ai', + classification: 'DESTRUCTIVE', + outputSchema: deleteRowAiOutputSchema, + displayName: 'Delete Row', + description: 'Deletes a row by its ID.', + audience: 'ai', + aiMetadata: { + description: + 'Deletes one row from a Baserow table by numeric row ID; the row moves to the Baserow trash and can be restored there. For many rows use Batch Delete Rows. Confirm the ID first with Find Row or Get Row. Not idempotent — a repeat call fails because the row is gone.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_id: baserowAiProps.rowIdProp(), + }, + async run(context) { + const { table_id, row_id } = context.propsValue; + const client = await makeClient(context.auth); + await baserowAiHelpers.execute(() => client.deleteRow(table_id, row_id)); + return { deleted: true, table_id, row_id }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/delete-row.ts b/packages/pieces/community/baserow/src/lib/actions/delete-row.ts index 8b72fc5f6928..5e7a0784cc6e 100644 --- a/packages/pieces/community/baserow/src/lib/actions/delete-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/delete-row.ts @@ -1,13 +1,15 @@ import { createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { deleteRowOutputSchema } from '../output-schemas'; export const deleteRowAction = createAction({ name: 'baserow_delete_row', classification: 'DESTRUCTIVE', + outputSchema: deleteRowOutputSchema, displayName: 'Delete Row', description: 'Deletes an existing row.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Permanently deletes one row from a Baserow table by its numeric row ID. Use to remove a single known record; for multiple rows use Batch Delete Rows. Destructive — confirm the row ID first (resolve via Find Row or List Rows if unknown). Not idempotent against changing data: once the row is gone a repeat call targets a no-longer-existing ID.', diff --git a/packages/pieces/community/baserow/src/lib/actions/delete-table.ts b/packages/pieces/community/baserow/src/lib/actions/delete-table.ts new file mode 100644 index 000000000000..8a433e202867 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/delete-table.ts @@ -0,0 +1,31 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { deleteTableOutputSchema } from '../output-schemas'; + +export const deleteTableAction = createAction({ + name: 'baserow_delete_table', + classification: 'DESTRUCTIVE', + outputSchema: deleteTableOutputSchema, + displayName: 'Delete Table', + description: 'Deletes a table and all its rows.', + audience: 'ai', + aiMetadata: { + description: + 'Deletes a Baserow table together with all its fields, views and rows; it moves to the Baserow trash and can be restored there until the trash is emptied. Confirm the table with Get Table first. Requires an Email & Password connection. Not idempotent — a repeat call fails because the table is gone.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + }, + async run(context) { + const { table_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Delete Table' }); + const client = await makeClient(context.auth); + await baserowAiHelpers.execute(() => client.deleteTable({ tableId: table_id })); + return { deleted: true, table_id }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/export-table.ts b/packages/pieces/community/baserow/src/lib/actions/export-table.ts new file mode 100644 index 000000000000..b08ca2526c8e --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/export-table.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { exportTableOutputSchema } from '../output-schemas'; + +export const exportTableAction = createAction({ + name: 'baserow_export_table', + classification: 'WRITE', + outputSchema: exportTableOutputSchema, + displayName: 'Export Table', + description: 'Starts a CSV export of a table or view.', + audience: 'ai', + aiMetadata: { + description: + 'Starts an asynchronous export of a Baserow table (or one of its views) to CSV and returns the export job ID. The file is not ready yet — call Get Export Job with the job ID until its state is "finished" to get the download URL. Requires an Email & Password connection. Not idempotent — each call starts a new export job.', + idempotent: false, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + view_id: Property.Number({ + displayName: 'View ID', + description: 'Export only the rows and fields visible in this view. Leave empty for the whole table.', + required: false, + }), + }, + async run(context) { + const { table_id, view_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Export Table' }); + const client = await makeClient(context.auth); + const job = await baserowAiHelpers.execute(() => + client.exportTable({ + tableId: table_id, + body: { exporter_type: 'csv', ...(view_id ? { view_id } : {}) }, + }) + ); + return { job_id: job['id'], state: job['state'] }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/find-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/find-row-ai.ts new file mode 100644 index 000000000000..f1494a5fb1ab --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/find-row-ai.ts @@ -0,0 +1,51 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { findRowAiOutputSchema } from '../output-schemas'; + +export const findRowAiAction = createAction({ + name: 'baserow_find_row_ai', + classification: 'SEARCH', + outputSchema: findRowAiOutputSchema, + displayName: 'Find Row', + description: 'Finds the first row whose field equals a value.', + audience: 'ai', + aiMetadata: { + description: + 'Returns the first row in a Baserow table whose named field exactly equals a value, plus the total match count. Use to resolve a row ID from a known key such as an email or external ID; for partial-text or multi-condition queries use List Rows. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + field_name: Property.ShortText({ + displayName: 'Field Name', + description: 'Exact name of the field to match on. Resolve with Get Table Fields.', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + description: 'The value the field must equal.', + required: true, + }), + }, + async run(context) { + const { table_id, field_name, value } = context.propsValue; + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(async () => { + const field = await baserowAiHelpers.resolveField({ + client, + tableId: table_id, + fieldName: field_name, + }); + return await client.queryRows({ + tableId: table_id, + query: { size: '1', [`filter__field_${field.id}__equal`]: value }, + }); + }); + const row = response.results[0] ?? null; + return { found: row !== null, count: response.count, row }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/find-row.ts b/packages/pieces/community/baserow/src/lib/actions/find-row.ts index 233a88408cf3..790a11334240 100644 --- a/packages/pieces/community/baserow/src/lib/actions/find-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/find-row.ts @@ -6,14 +6,16 @@ import { import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { BaserowFieldType } from '../common/constants'; +import { findRowOutputSchema } from '../output-schemas'; export const findRowAction = createAction({ name: 'baserow_find_row', classification: 'SEARCH', + outputSchema: findRowOutputSchema, displayName: 'Find Row', description: 'Finds a row by matching a field value. Returns the first match.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Looks up the first row in a Baserow table whose chosen field exactly equals a given value. Use to resolve a row from a known field value (e.g. an email or external key) when you do not have its row ID; for ID lookups use Get Row, and for multi-condition or partial-text queries use List Rows. The match field must be a filterable type (link, multi-select, file, formula/lookup, and date-type fields are excluded). Read-only and idempotent.', diff --git a/packages/pieces/community/baserow/src/lib/actions/get-export-job.ts b/packages/pieces/community/baserow/src/lib/actions/get-export-job.ts new file mode 100644 index 000000000000..4c300f4387a5 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-export-job.ts @@ -0,0 +1,40 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { getExportJobOutputSchema } from '../output-schemas'; + +export const getExportJobAction = createAction({ + name: 'baserow_get_export_job', + classification: 'READ', + outputSchema: getExportJobOutputSchema, + displayName: 'Get Export Job', + description: 'Gets the state and download URL of an export job.', + audience: 'ai', + aiMetadata: { + description: + 'Returns the state (pending, exporting, finished, failed, cancelled), progress and — once finished — the download URL of a Baserow export job started by Export Table. Call again later while the state is pending or exporting; it does not wait. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + job_id: Property.Number({ + displayName: 'Job ID', + description: 'The job ID returned by Export Table.', + required: true, + }), + }, + async run(context) { + const { job_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Get Export Job' }); + const client = await makeClient(context.auth); + const job = await baserowAiHelpers.execute(() => client.getExportJob({ jobId: job_id })); + return { + job_id: job['id'], + state: job['state'], + progress_percentage: job['progress_percentage'], + file_name: job['exported_file_name'], + url: job['state'] === 'finished' ? job['url'] : null, + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-field-unique-values.ts b/packages/pieces/community/baserow/src/lib/actions/get-field-unique-values.ts new file mode 100644 index 000000000000..36f1654d73cf --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-field-unique-values.ts @@ -0,0 +1,48 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { getFieldUniqueValuesOutputSchema } from '../output-schemas'; + +export const getFieldUniqueValuesAction = createAction({ + name: 'baserow_get_field_unique_values', + classification: 'READ', + outputSchema: getFieldUniqueValuesOutputSchema, + displayName: 'Get Field Unique Values', + description: 'Lists the distinct values stored in a field.', + audience: 'ai', + aiMetadata: { + description: + 'Returns the distinct values stored in a Baserow field, most frequent first. Use to discover valid values before filtering rows or to profile a column without paging through rows. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + field_id: baserowAiProps.fieldIdProp(), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum number of values to return. Defaults to 10.', + required: false, + }), + split_comma_separated: Property.Checkbox({ + displayName: 'Split Comma-Separated Values', + description: 'Treat "a,b" as two values a and b.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { field_id, limit, split_comma_separated } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Get Field Unique Values' }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => + client.getFieldUniqueValues({ + fieldId: field_id, + limit: limit ?? undefined, + splitCommaSeparated: split_comma_separated, + }) + ); + return { count: response.values.length, values: response.values }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/get-row-ai.ts new file mode 100644 index 000000000000..8d0bc06ab0bd --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-row-ai.ts @@ -0,0 +1,30 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { rowOutputSchema } from '../output-schemas'; + +export const getRowAiAction = createAction({ + name: 'baserow_get_row_ai', + classification: 'READ', + outputSchema: rowOutputSchema, + displayName: 'Get Row', + description: 'Gets a single row by its ID.', + audience: 'ai', + aiMetadata: { + description: + 'Fetches one row and all its field values (keyed by field name) from a Baserow table by numeric row ID. Use when the row ID is known; to locate a row by a field value use Find Row. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_id: baserowAiProps.rowIdProp(), + }, + async run(context) { + const { table_id, row_id } = context.propsValue; + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => client.getRow(table_id, row_id)); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-row-history.ts b/packages/pieces/community/baserow/src/lib/actions/get-row-history.ts new file mode 100644 index 000000000000..2398c1c8f78b --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-row-history.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { getRowHistoryOutputSchema } from '../output-schemas'; + +export const getRowHistoryAction = createAction({ + name: 'baserow_get_row_history', + classification: 'READ', + outputSchema: getRowHistoryOutputSchema, + displayName: 'Get Row History', + description: 'Lists the change history of a row.', + audience: 'ai', + aiMetadata: { + description: + 'Returns the change log of a Baserow row, newest first: who changed which fields, when, and the before/after values. Use to audit edits or recover a previous value. History is kept for a limited period (180 days by default). Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_id: baserowAiProps.rowIdProp(), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum entries to return.', + required: false, + }), + offset: Property.Number({ + displayName: 'Offset', + description: 'Number of entries to skip, for paging.', + required: false, + }), + }, + async run(context) { + const { table_id, row_id, limit, offset } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Get Row History' }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => + client.getRowHistory({ + tableId: table_id, + rowId: row_id, + limit: limit ?? undefined, + offset: offset ?? undefined, + }) + ); + return { count: response['count'], has_more: Boolean(response['next']), entries: response['results'] }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-row.ts b/packages/pieces/community/baserow/src/lib/actions/get-row.ts index fdf2c3c983b9..263eaba6cd31 100644 --- a/packages/pieces/community/baserow/src/lib/actions/get-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/get-row.ts @@ -1,13 +1,15 @@ import { createAction } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { rowOutputSchema } from '../output-schemas'; export const getRowAction = createAction({ name: 'baserow_get_row', classification: 'READ', + outputSchema: rowOutputSchema, displayName: 'Get Row', description: 'Gets a single row by its ID from a table.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Fetches one row from a Baserow table by its numeric row ID. Use when you already know the exact row ID and want its full current field values; to locate a row by a field value instead, use Find Row. Read-only and idempotent.', diff --git a/packages/pieces/community/baserow/src/lib/actions/get-table-fields.ts b/packages/pieces/community/baserow/src/lib/actions/get-table-fields.ts new file mode 100644 index 000000000000..f9322b9d9d16 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-table-fields.ts @@ -0,0 +1,44 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { getTableFieldsOutputSchema } from '../output-schemas'; + +export const getTableFieldsAction = createAction({ + name: 'baserow_get_table_fields', + classification: 'READ', + outputSchema: getTableFieldsOutputSchema, + displayName: 'Get Table Fields', + description: 'Lists the fields (columns) of a table.', + audience: 'ai', + aiMetadata: { + description: + 'Returns every field of a Baserow table with its ID, name, type, primary/read-only flags and select options. Call before creating, updating or filtering rows to learn exact field names, types and valid select values. Works with Database Token and Email & Password connections. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + }, + async run(context) { + const { table_id } = context.propsValue; + const client = await makeClient(context.auth); + const fields = await baserowAiHelpers.execute(() => client.listTableFields(table_id)); + return { + count: fields.length, + fields: fields.map((field) => ({ + id: field.id, + name: field.name, + type: field.type, + primary: field.primary, + read_only: field.read_only, + select_options: + 'select_options' in field + ? field.select_options.map((option) => ({ id: option.id, value: option.value })) + : undefined, + link_row_table_id: 'link_row_table_id' in field ? field.link_row_table_id : undefined, + })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/get-table.ts b/packages/pieces/community/baserow/src/lib/actions/get-table.ts new file mode 100644 index 000000000000..67b1c39547d8 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/get-table.ts @@ -0,0 +1,30 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { tableOutputSchema } from '../output-schemas'; + +export const getTableAction = createAction({ + name: 'baserow_get_table', + classification: 'READ', + outputSchema: tableOutputSchema, + displayName: 'Get Table', + description: 'Gets a table by its ID.', + audience: 'ai', + aiMetadata: { + description: + 'Returns a Baserow table\'s ID, name, order and database ID. Use to confirm a table ID or its parent database; to discover tables use List Tables and for its columns use Get Table Fields. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + }, + async run(context) { + const { table_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Get Table' }); + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => client.getTable({ tableId: table_id })); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-databases.ts b/packages/pieces/community/baserow/src/lib/actions/list-databases.ts new file mode 100644 index 000000000000..1096b8d39f58 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-databases.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listDatabasesOutputSchema } from '../output-schemas'; + +export const listDatabasesAction = createAction({ + name: 'baserow_list_databases', + classification: 'SEARCH', + outputSchema: listDatabasesOutputSchema, + displayName: 'List Databases', + description: 'Lists the databases the user can access.', + audience: 'ai', + aiMetadata: { + description: + 'Lists every Baserow database the connected user can access across workspaces, with its ID, name, workspace and tables. Use to get the Database ID for Create Table; to just find a table use List Tables. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: {}, + async run(context) { + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'List Databases' }); + const client = await makeClient(context.auth); + const applications = await baserowAiHelpers.execute(() => client.listApplications()); + const databases = applications.filter((application) => application.type === 'database'); + return { + count: databases.length, + databases: databases.map((database) => ({ + id: database.id, + name: database.name, + workspace_id: database.workspace?.id, + workspace_name: database.workspace?.name, + tables: (database.tables ?? []).map((table) => ({ id: table.id, name: table.name })), + })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-row-names.ts b/packages/pieces/community/baserow/src/lib/actions/list-row-names.ts new file mode 100644 index 000000000000..19276800f6b2 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-row-names.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listRowNamesOutputSchema } from '../output-schemas'; + +export const listRowNamesAction = createAction({ + name: 'baserow_list_row_names', + classification: 'READ', + outputSchema: listRowNamesOutputSchema, + displayName: 'List Row Names', + description: 'Gets the primary field value of the given rows.', + audience: 'ai', + aiMetadata: { + description: + 'Returns the display name (primary field value) of each given row ID in a Baserow table. Use to turn linked-row IDs into readable names without fetching whole rows. Works with Database Token and Email & Password connections. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_ids: Property.Array({ + displayName: 'Row IDs', + description: 'Numeric row IDs to look up.', + required: true, + }), + }, + async run(context) { + const { table_id, row_ids } = context.propsValue; + const ids = baserowAiHelpers.toIdArray({ value: row_ids, propName: 'Row IDs', max: 200 }); + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => + client.listRowNames({ tableId: table_id, rowIds: ids }) + ); + const names = response[String(table_id)] ?? {}; + return { + rows: Object.entries(names).map(([id, name]) => ({ id: Number(id), name })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-rows-ai.ts b/packages/pieces/community/baserow/src/lib/actions/list-rows-ai.ts new file mode 100644 index 000000000000..05ec27001e00 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-rows-ai.ts @@ -0,0 +1,99 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listRowsAiOutputSchema } from '../output-schemas'; + +export const listRowsAiAction = createAction({ + name: 'baserow_list_rows_ai', + classification: 'SEARCH', + outputSchema: listRowsAiOutputSchema, + displayName: 'List Rows', + description: 'Lists rows from a table with optional search, sorting, filters and view.', + audience: 'ai', + aiMetadata: { + description: + 'Returns one page of rows from a Baserow table, optionally narrowed by free-text search, structured filters (AND/OR over field names), a view, and a field selection. Use to browse or query many rows; for one row by ID use Get Row, or for an exact single-field match use Find Row. Page size is at most 200 — follow `next` by incrementing Page. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + search: Property.ShortText({ + displayName: 'Search', + description: 'Only return rows with cell data matching this text.', + required: false, + }), + filters: Property.Json({ + displayName: 'Filters', + description: + 'Optional filter tree: {"filter_type": "AND" | "OR", "filters": [{"field": "", "type": "", "value": ""}], "groups": []}. Operators include equal, not_equal, contains, contains_not, higher_than, lower_than, date_is, empty, not_empty, boolean, link_row_has, single_select_equal.', + required: false, + }), + order_by: Property.ShortText({ + displayName: 'Order By', + description: 'Comma-separated field names; prefix a name with "-" for descending, e.g. "-Created,Name".', + required: false, + }), + include: Property.Array({ + displayName: 'Include Fields', + description: 'Only return these field names. Leave empty for all fields.', + required: false, + }), + exclude: Property.Array({ + displayName: 'Exclude Fields', + description: 'Field names to leave out of the response.', + required: false, + }), + view_id: Property.Number({ + displayName: 'View ID', + description: 'Apply the filters and sorts saved on this view. Resolve with List Views.', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + defaultValue: 1, + }), + size: Property.Number({ + displayName: 'Page Size', + description: 'Rows per page, 1–200. Defaults to 100.', + required: false, + defaultValue: 100, + }), + }, + async run(context) { + const { table_id, search, filters, order_by, include, exclude, view_id, page, size } = + context.propsValue; + if (size !== undefined && size !== null && (size < 1 || size > 200)) { + throw new Error('Page Size must be between 1 and 200.'); + } + const query: Record = { + page: String(page ?? 1), + size: String(size ?? 100), + ...(search ? { search } : {}), + ...(order_by ? { order_by } : {}), + ...(include && include.length > 0 ? { include: include.map(String).join(',') } : {}), + ...(exclude && exclude.length > 0 ? { exclude: exclude.map(String).join(',') } : {}), + ...(view_id ? { view_id: String(view_id) } : {}), + ...(filters + ? { + filters: JSON.stringify( + baserowAiHelpers.toRecord({ value: filters, propName: 'Filters' }) + ), + } + : {}), + }; + const client = await makeClient(context.auth); + const response = await baserowAiHelpers.execute(() => + client.queryRows({ tableId: table_id, query }) + ); + return { + count: response.count, + has_more: response.next !== null, + rows: response.results, + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-rows.ts b/packages/pieces/community/baserow/src/lib/actions/list-rows.ts index d7b262794416..17c42bf621e0 100644 --- a/packages/pieces/community/baserow/src/lib/actions/list-rows.ts +++ b/packages/pieces/community/baserow/src/lib/actions/list-rows.ts @@ -4,13 +4,15 @@ import { } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; +import { listRowsOutputSchema } from '../output-schemas'; export const listRowsAction = createAction({ name: 'baserow_list_rows', classification: 'SEARCH', + outputSchema: listRowsOutputSchema, displayName: 'List Rows', description: 'Lists rows from a table with optional search, sorting, and filtering.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Retrieves a page of rows from a Baserow table. With no search/filters it returns all rows (paginated); supply a free-text search, an order-by field, or structured field filters (each a JSON object with field ID, operator, and value, combined with AND/OR) to narrow results. Use to browse or query many rows; for a single row by ID use Get Row, or by one field value use Find Row. Read-only and idempotent.', diff --git a/packages/pieces/community/baserow/src/lib/actions/list-tables.ts b/packages/pieces/community/baserow/src/lib/actions/list-tables.ts new file mode 100644 index 000000000000..d2f419b3df9e --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-tables.ts @@ -0,0 +1,33 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listTablesOutputSchema } from '../output-schemas'; + +export const listTablesAction = createAction({ + name: 'baserow_list_tables', + classification: 'SEARCH', + outputSchema: listTablesOutputSchema, + displayName: 'List Tables', + description: 'Lists the tables the connection can access.', + audience: 'ai', + aiMetadata: { + description: + 'Lists every Baserow table the connection can reach, with its ID, name and database ID. Call this first to get the Table ID every row action needs. Works with Database Token and Email & Password connections. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: {}, + async run(context) { + const client = await makeClient(context.auth); + const tables = await baserowAiHelpers.execute(() => client.listTables()); + return { + count: tables.length, + tables: tables.map((table) => ({ + id: table.id, + name: table.name, + database_id: table.database_id, + })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-views.ts b/packages/pieces/community/baserow/src/lib/actions/list-views.ts new file mode 100644 index 000000000000..5d727187af23 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-views.ts @@ -0,0 +1,34 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listViewsOutputSchema } from '../output-schemas'; + +export const listViewsAction = createAction({ + name: 'baserow_list_views', + classification: 'SEARCH', + outputSchema: listViewsOutputSchema, + displayName: 'List Views', + description: 'Lists the views of a table.', + audience: 'ai', + aiMetadata: { + description: + 'Lists all views (grid, gallery, form, kanban, calendar, timeline) of a Baserow table with their ID, name and type. Use to get a View ID for List Rows or a grid View ID for Aggregate Field. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + }, + async run(context) { + const { table_id } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'List Views' }); + const client = await makeClient(context.auth); + const views = await baserowAiHelpers.execute(() => client.listAllViews({ tableId: table_id })); + return { + count: views.length, + views: views.map((view) => ({ id: view['id'], name: view['name'], type: view['type'] })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-workspace-users.ts b/packages/pieces/community/baserow/src/lib/actions/list-workspace-users.ts new file mode 100644 index 000000000000..36e8c23450de --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-workspace-users.ts @@ -0,0 +1,46 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listWorkspaceUsersOutputSchema } from '../output-schemas'; + +export const listWorkspaceUsersAction = createAction({ + name: 'baserow_list_workspace_users', + classification: 'SEARCH', + outputSchema: listWorkspaceUsersOutputSchema, + displayName: 'List Workspace Users', + description: 'Lists the members of a workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Lists the members of a Baserow workspace with user ID, name, email and role. Use to find the user ID needed for collaborator fields ([{"id": }]). Only workspace admins can call it, with an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + workspace_id: baserowAiProps.workspaceIdProp(), + search: Property.ShortText({ + displayName: 'Search', + description: 'Only return users whose name or email contains this text.', + required: false, + }), + }, + async run(context) { + const { workspace_id, search } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'List Workspace Users' }); + const client = await makeClient(context.auth); + const users = await baserowAiHelpers.execute(() => + client.listWorkspaceUsers({ workspaceId: workspace_id, search: search ?? undefined }) + ); + return { + count: users.length, + users: users.map((user) => ({ + user_id: user['user_id'], + name: user['name'], + email: user['email'], + permissions: user['permissions'], + })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/list-workspaces.ts b/packages/pieces/community/baserow/src/lib/actions/list-workspaces.ts new file mode 100644 index 000000000000..8e7a2965a2d8 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/list-workspaces.ts @@ -0,0 +1,34 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { listWorkspacesOutputSchema } from '../output-schemas'; + +export const listWorkspacesAction = createAction({ + name: 'baserow_list_workspaces', + classification: 'SEARCH', + outputSchema: listWorkspacesOutputSchema, + displayName: 'List Workspaces', + description: 'Lists the workspaces the user belongs to.', + audience: 'ai', + aiMetadata: { + description: + 'Lists the Baserow workspaces the connected user belongs to, with ID, name and the user\'s permission level. Use to get the Workspace ID for Create Database, List Workspace Users or Search Workspace. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: {}, + async run(context) { + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'List Workspaces' }); + const client = await makeClient(context.auth); + const workspaces = await baserowAiHelpers.execute(() => client.listWorkspaces()); + return { + count: workspaces.length, + workspaces: workspaces.map((workspace) => ({ + id: workspace['id'], + name: workspace['name'], + permissions: workspace['permissions'], + })), + }; + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/search-workspace.ts b/packages/pieces/community/baserow/src/lib/actions/search-workspace.ts new file mode 100644 index 000000000000..549bf92f2a53 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/search-workspace.ts @@ -0,0 +1,52 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { searchWorkspaceOutputSchema } from '../output-schemas'; + +export const searchWorkspaceAction = createAction({ + name: 'baserow_search_workspace', + classification: 'SEARCH', + outputSchema: searchWorkspaceOutputSchema, + displayName: 'Search Workspace', + description: 'Searches databases, tables, fields and rows in a workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Runs Baserow\'s workspace-wide search across databases, tables, fields and row content, returning matches with their type and IDs. Use when you do not know which table holds a record; within a known table use List Rows with Search. Requires an Email & Password connection. Read-only and idempotent.', + idempotent: true, + }, + auth: baserowAuth, + props: { + workspace_id: baserowAiProps.workspaceIdProp(), + query: Property.ShortText({ + displayName: 'Query', + description: 'Text to search for.', + required: true, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum results to return.', + required: false, + }), + offset: Property.Number({ + displayName: 'Offset', + description: 'Number of results to skip, for paging.', + required: false, + }), + }, + async run(context) { + const { workspace_id, query, limit, offset } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Search Workspace' }); + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => + client.searchWorkspace({ + workspaceId: workspace_id, + query, + limit: limit ?? undefined, + offset: offset ?? undefined, + }) + ); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/update-field.ts b/packages/pieces/community/baserow/src/lib/actions/update-field.ts new file mode 100644 index 000000000000..7231cd0081f0 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/update-field.ts @@ -0,0 +1,54 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { fieldOutputSchema } from '../output-schemas'; + +export const updateFieldAction = createAction({ + name: 'baserow_update_field', + classification: 'WRITE', + outputSchema: fieldOutputSchema, + displayName: 'Update Field', + description: 'Renames a field or changes its settings or type.', + audience: 'ai', + aiMetadata: { + description: + 'Updates a Baserow field: rename it, change type-specific settings such as select options, or convert its type. Only the given properties change. Converting the type can lose cell data that does not fit the new type. Requires an Email & Password connection. Idempotent — the same update converges on the same field.', + idempotent: true, + }, + auth: baserowAuth, + props: { + field_id: baserowAiProps.fieldIdProp(), + name: Property.ShortText({ + displayName: 'New Name', + description: 'Leave empty to keep the current name.', + required: false, + }), + type: Property.ShortText({ + displayName: 'New Type', + description: 'Leave empty to keep the current type. Changing type can drop incompatible values.', + required: false, + }), + options: Property.Json({ + displayName: 'Type Options', + description: + 'Optional JSON object of type-specific settings to change. For select fields, "select_options" replaces the whole list — include existing options (with their "id") to keep them.', + required: false, + }), + }, + async run(context) { + const { field_id, name, type, options } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Update Field' }); + const body = { + ...(options ? baserowAiHelpers.toRecord({ value: options, propName: 'Type Options' }) : {}), + ...(name ? { name } : {}), + ...(type ? { type } : {}), + }; + if (Object.keys(body).length === 0) { + throw new Error('Provide at least one of New Name, New Type or Type Options.'); + } + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => client.updateField({ fieldId: field_id, body })); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/update-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/update-row-ai.ts new file mode 100644 index 000000000000..a58c53952e3a --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/update-row-ai.ts @@ -0,0 +1,60 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { ensureSelectOptionsExist, makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { rowOutputSchema } from '../output-schemas'; + +export const updateRowAiAction = createAction({ + name: 'baserow_update_row_ai', + classification: 'WRITE', + outputSchema: rowOutputSchema, + displayName: 'Update Row', + description: 'Updates only the given fields of a row.', + audience: 'ai', + aiMetadata: { + description: + 'Updates an existing Baserow row by ID, writing only the fields present in the JSON object and leaving all others unchanged. Pass null (or [] for select, link and file fields) to clear a cell. To update many rows use Batch Update Rows; to create-or-update by a key use Upsert Row. Idempotent — repeating the same values converges on the same state.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + row_id: baserowAiProps.rowIdProp(), + fields: Property.Json({ + displayName: 'Fields', + description: + 'JSON object of field name to new value, e.g. {"Status": "Done"}. Only these fields are written. Use null to clear a value. Formats match Create Row.', + required: true, + }), + create_missing_select_options: Property.Checkbox({ + displayName: 'Create Missing Select Options', + description: + 'Adds single/multiple select values that do not exist yet as new options before writing. Requires an Email & Password connection.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { table_id, row_id, fields, create_missing_select_options } = context.propsValue; + baserowAiHelpers.assertSelectOptionsAllowed({ + auth: context.auth, + createMissingSelectOptions: create_missing_select_options, + }); + const payload = baserowAiHelpers.toRecord({ value: fields, propName: 'Fields' }); + if (Object.keys(payload).length === 0) { + throw new Error('Fields must contain at least one field to update.'); + } + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(async () => { + if (create_missing_select_options) { + await ensureSelectOptionsExist({ + fields: await client.listTableFields(table_id), + payload, + client, + }); + } + return await client.updateRow(table_id, row_id, payload); + }); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/update-row.ts b/packages/pieces/community/baserow/src/lib/actions/update-row.ts index 6392dd54821a..e59026bc00ec 100644 --- a/packages/pieces/community/baserow/src/lib/actions/update-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/update-row.ts @@ -6,14 +6,16 @@ import { formatFieldValues, makeClient, } from '../common'; +import { rowOutputSchema } from '../output-schemas'; export const updateRowAction = createAction({ name: 'baserow_update_row', classification: 'WRITE', + outputSchema: rowOutputSchema, displayName: 'Update Row', description: 'Updates fields in an existing row. Empty values are skipped. To clear a field, use Clean Row.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Updates field values on one existing Baserow row identified by its numeric row ID. Only the fields you supply are changed and empty values are skipped, so it cannot clear a field — use Clean Row for that. Requires a known row ID (resolve one first with Find Row or List Rows if needed). Optionally auto-creates missing single/multi-select options. Idempotent: applying the same values to the same row leaves it in the same state.', diff --git a/packages/pieces/community/baserow/src/lib/actions/update-table.ts b/packages/pieces/community/baserow/src/lib/actions/update-table.ts new file mode 100644 index 000000000000..b5218e572885 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/update-table.ts @@ -0,0 +1,35 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { tableOutputSchema } from '../output-schemas'; + +export const updateTableAction = createAction({ + name: 'baserow_update_table', + classification: 'WRITE', + outputSchema: tableOutputSchema, + displayName: 'Rename Table', + description: 'Renames a table.', + audience: 'ai', + aiMetadata: { + description: + 'Renames an existing Baserow table. Requires an Email & Password connection. Idempotent — applying the same name again changes nothing.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + name: Property.ShortText({ + displayName: 'Name', + description: 'The new table name.', + required: true, + }), + }, + async run(context) { + const { table_id, name } = context.propsValue; + baserowAiHelpers.assertJwt({ auth: context.auth, actionName: 'Rename Table' }); + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(() => client.updateTable({ tableId: table_id, name })); + }, +}); diff --git a/packages/pieces/community/baserow/src/lib/actions/upload-file.ts b/packages/pieces/community/baserow/src/lib/actions/upload-file.ts index 90d80ebb281b..b7604913aefb 100644 --- a/packages/pieces/community/baserow/src/lib/actions/upload-file.ts +++ b/packages/pieces/community/baserow/src/lib/actions/upload-file.ts @@ -1,17 +1,19 @@ import { Property, createAction } from '@activepieces/pieces-framework'; -import { baserowAuth, baserowAuthHelpers } from '../auth'; +import { baserowAuth } from '../auth'; import { makeClient } from '../common'; +import { uploadFileOutputSchema } from '../output-schemas'; export const uploadFileAction = createAction({ name: 'baserow_upload_file', classification: 'WRITE', + outputSchema: uploadFileOutputSchema, displayName: 'Upload File', description: - 'Uploads a file to Baserow from a URL. Returns the uploaded file object that can be used in file fields. Requires Email & Password (JWT) authentication — Database Tokens do not have access to the user-files endpoint.', + 'Uploads a file to Baserow from a URL. Returns the uploaded file object that can be used in file fields.', audience: 'both', aiMetadata: { description: - 'Downloads a file from a public URL and uploads it into Baserow user files, producing a file reference you can then assign to a file field via Create/Update Row. Use as the first step before attaching files to rows. Requires Email & Password (JWT) authentication — Database Token connections cannot reach the user-files endpoint. Not idempotent — each call uploads a new file copy.', + 'Downloads a file from a public URL and uploads it into Baserow user files, producing a file reference you can then assign to a file field via Create/Update Row. Use as the first step before attaching files to rows. Not idempotent — each call uploads a new file copy.', idempotent: false, }, auth: baserowAuth, @@ -23,11 +25,6 @@ export const uploadFileAction = createAction({ }), }, async run(context) { - if (!baserowAuthHelpers.isJwtAuth(context.auth)) { - throw new Error( - 'Upload File requires Email & Password (JWT) authentication. Database Tokens are limited to row CRUD operations and cannot access the user-files endpoint. Please create a new Baserow connection using Email & Password.' - ); - } const { url } = context.propsValue; const client = await makeClient(context.auth); return await client.uploadFileFromUrl({ url }); diff --git a/packages/pieces/community/baserow/src/lib/actions/upsert-row-ai.ts b/packages/pieces/community/baserow/src/lib/actions/upsert-row-ai.ts new file mode 100644 index 000000000000..3b1968109a53 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/actions/upsert-row-ai.ts @@ -0,0 +1,135 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { baserowAuth } from '../auth'; +import { BaserowFieldType } from '../common/constants'; +import { BaserowField } from '../common/types'; +import { ensureSelectOptionsExist, makeClient } from '../common'; +import { baserowAiProps } from '../common/ai-props'; +import { baserowAiHelpers } from '../common/ai-helpers'; +import { upsertRowOutputSchema } from '../output-schemas'; + +export const upsertRowAiAction = createAction({ + name: 'baserow_upsert_row_ai', + classification: 'WRITE', + outputSchema: upsertRowOutputSchema, + displayName: 'Upsert Row', + description: 'Updates the row matching a key field value, or creates it.', + audience: 'ai', + aiMetadata: { + description: + 'Looks up the first row whose match field equals the match value; updates it with the given fields if found, otherwise creates a new row with those fields plus the match value (the match value always wins over the same field in Fields). Use to sync records by a unique key (email, external ID) without duplicates. Idempotent only when the match value is unique in the table — the lookup and write are separate calls, so concurrent calls can still create two rows.', + idempotent: true, + }, + auth: baserowAuth, + props: { + table_id: baserowAiProps.tableIdProp(), + match_field: Property.ShortText({ + displayName: 'Match Field Name', + description: 'Exact name of the key field to match on. Must be a writable text, long text, number, rating, boolean, email, URL, phone number or single select field.', + required: true, + }), + match_value: Property.ShortText({ + displayName: 'Match Value', + description: 'The key value to look for. For a single select field, pass the option name, or its option ID when no option has that name.', + required: true, + }), + fields: Property.Json({ + displayName: 'Fields', + description: + 'JSON object of field name to value to write on the matched or new row. Formats match Create Row. The match field is not changed on an existing row.', + required: true, + }), + create_missing_select_options: Property.Checkbox({ + displayName: 'Create Missing Select Options', + description: + 'Adds single/multiple select values that do not exist yet as new options before writing. Requires an Email & Password connection.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { table_id, match_field, match_value, fields, create_missing_select_options } = + context.propsValue; + baserowAiHelpers.assertSelectOptionsAllowed({ + auth: context.auth, + createMissingSelectOptions: create_missing_select_options, + }); + const payload = baserowAiHelpers.toRecord({ value: fields, propName: 'Fields' }); + const client = await makeClient(context.auth); + return await baserowAiHelpers.execute(async () => { + const tableFields = await client.listTableFields(table_id); + const matchField = tableFields.find((f) => f.name === match_field); + if (!matchField) { + throw new Error( + `Field "${match_field}" was not found in table ${table_id}. Available fields: ${tableFields.map((f) => f.name).join(', ')}.` + ); + } + if (!isMatchableField({ field: matchField })) { + throw new Error( + `Field "${match_field}" (${matchField.type}${matchField.read_only ? ', read-only' : ''}) cannot be used as a match field. Use a writable text, long text, number, rating, boolean, email, URL, phone number or single select field. Matchable fields: ${tableFields.filter((f) => isMatchableField({ field: f })).map((f) => f.name).join(', ')}.` + ); + } + const matchFilter = buildMatchFilter({ field: matchField, value: match_value }); + const existingRow = matchFilter + ? (await client.queryRows({ tableId: table_id, query: { size: '1', ...matchFilter } })).results[0] + : undefined; + const rowPayload = existingRow + ? Object.fromEntries(Object.entries(payload).filter(([key]) => key !== matchField.name)) + : { ...payload, [matchField.name]: toCreateValue({ field: matchField, value: match_value }) }; + if (create_missing_select_options) { + await ensureSelectOptionsExist({ fields: tableFields, payload: rowPayload, client }); + } + if (existingRow) { + const row = await client.updateRow(table_id, existingRow.id, rowPayload); + return { action: 'updated', row }; + } + const row = await client.createRow(table_id, rowPayload); + return { action: 'created', row }; + }); + }, +}); + +function isMatchableField({ field }: { field: BaserowField }): boolean { + const unsupportedTypes: string[] = [ + BaserowFieldType.LINK_TO_TABLE, + BaserowFieldType.MULTI_SELECT, + BaserowFieldType.MULTIPLE_COLLABORATORS, + BaserowFieldType.FILE, + BaserowFieldType.ROLLUP, + BaserowFieldType.LOOKUP, + BaserowFieldType.COUNT, + BaserowFieldType.LAST_MODIFIED_BY, + BaserowFieldType.CREATED_BY, + BaserowFieldType.DATE, + BaserowFieldType.LAST_MODIFIED, + BaserowFieldType.CREATED_ON, + BaserowFieldType.DURATION, + BaserowFieldType.UUID, + BaserowFieldType.AUTO_NUMBER, + ]; + return !field.read_only && !unsupportedTypes.includes(field.type); +} + +function buildMatchFilter({ field, value }: { field: BaserowField; value: string }): Record | null { + if (field.type !== BaserowFieldType.SINGLE_SELECT) { + return { [`filter__field_${field.id}__equal`]: value }; + } + const option = findSelectOption({ field, value }); + return option ? { [`filter__field_${field.id}__single_select_equal`]: String(option.id) } : null; +} + +function toCreateValue({ field, value }: { field: BaserowField; value: string }): string | number { + if (field.type !== BaserowFieldType.SINGLE_SELECT) { + return value; + } + return findSelectOption({ field, value })?.id ?? value; +} + +function findSelectOption({ field, value }: { field: BaserowField; value: string }): { id: number; value: string } | undefined { + if (field.type !== BaserowFieldType.SINGLE_SELECT) { + return undefined; + } + return ( + field.select_options.find((o) => o.value === value) ?? + field.select_options.find((o) => String(o.id) === value.trim()) + ); +} diff --git a/packages/pieces/community/baserow/src/lib/actions/upsert-row.ts b/packages/pieces/community/baserow/src/lib/actions/upsert-row.ts index 8c74638e9e13..9d445fbb1199 100644 --- a/packages/pieces/community/baserow/src/lib/actions/upsert-row.ts +++ b/packages/pieces/community/baserow/src/lib/actions/upsert-row.ts @@ -11,14 +11,16 @@ import { makeClient, } from '../common'; import { BaserowFieldType } from '../common/constants'; +import { upsertRowOutputSchema } from '../output-schemas'; export const upsertRowAction = createAction({ name: 'baserow_upsert_row', classification: 'WRITE', + outputSchema: upsertRowOutputSchema, displayName: 'Upsert Row', description: 'Creates a new row or updates an existing one by matching a field value.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Inserts or updates a single Baserow row keyed on a match field value: if a row whose match field exactly equals the given value exists it is updated, otherwise a new row is created with that value. Use to sync records idempotently without creating duplicates. The match field must be a writable, filterable type (read-only, link, select, file, date, and auto-number fields are excluded). Idempotent on the match key — repeating the same call converges the row to the same state rather than adding rows.', diff --git a/packages/pieces/community/baserow/src/lib/common/ai-helpers.ts b/packages/pieces/community/baserow/src/lib/common/ai-helpers.ts new file mode 100644 index 000000000000..c696f7ea69d8 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/ai-helpers.ts @@ -0,0 +1,163 @@ +import { HttpError } from '@activepieces/pieces-common'; +import { tryCatch } from '@activepieces/pieces-framework'; +import { baserowAuthHelpers, BaserowAuthValue } from '../auth'; +import { BaserowClient } from './client'; +import { BaserowField } from './types'; + +export const baserowAiHelpers = { + assertJwt, + assertSelectOptionsAllowed, + execute, + toRecord, + toRecordArray, + toIdArray, + resolveField, + BATCH_LIMIT: 200, +}; + +function assertJwt({ + auth, + actionName, +}: { + auth: BaserowAuthValue; + actionName: string; +}): void { + if (!baserowAuthHelpers.isJwtAuth(auth)) { + throw new Error( + `${actionName} requires a Baserow connection that uses Email & Password. Database Tokens can only read and write rows. Reconnect Baserow with Email & Password to use this action.` + ); + } +} + +function assertSelectOptionsAllowed({ + auth, + createMissingSelectOptions, +}: { + auth: BaserowAuthValue; + createMissingSelectOptions: boolean | undefined; +}): void { + if (createMissingSelectOptions && !baserowAuthHelpers.isJwtAuth(auth)) { + throw new Error( + 'Creating missing select options edits the field and requires a Baserow connection that uses Email & Password. Turn the option off, or reconnect Baserow with Email & Password.' + ); + } +} + +async function execute(request: () => Promise): Promise { + const { data, error } = await tryCatch(request); + if (error) { + throw new Error(describeError(error)); + } + return data; +} + +function toRecord({ value, propName }: { value: unknown; propName: string }): Record { + if (isRecord(value)) { + return value; + } + throw new Error(`${propName} must be a JSON object.`); +} + +function toRecordArray({ + value, + propName, + max, +}: { + value: unknown; + propName: string; + max: number; +}): Record[] { + if (!Array.isArray(value) || value.length === 0) { + throw new Error(`${propName} must be a non-empty JSON array of objects.`); + } + if (value.length > max) { + throw new Error(`${propName} accepts at most ${max} items per call; received ${value.length}.`); + } + return value.map((item, index) => { + if (!isRecord(item)) { + throw new Error(`${propName}[${index}] must be a JSON object.`); + } + return item; + }); +} + +function toIdArray({ + value, + propName, + max, +}: { + value: unknown[] | undefined; + propName: string; + max: number; +}): number[] { + if (!value || value.length === 0) { + throw new Error(`${propName} must contain at least one ID.`); + } + if (value.length > max) { + throw new Error(`${propName} accepts at most ${max} IDs per call; received ${value.length}.`); + } + return value.map((item) => { + const id = Number(item); + if (!Number.isInteger(id) || id <= 0) { + throw new Error(`${propName} contains an invalid ID: ${String(item)}.`); + } + return id; + }); +} + +async function resolveField({ + client, + tableId, + fieldName, +}: { + client: BaserowClient; + tableId: number; + fieldName: string; +}): Promise { + const fields = await client.listTableFields(tableId); + const field = fields.find((f) => f.name === fieldName); + if (!field) { + throw new Error( + `Field "${fieldName}" was not found in table ${tableId}. Available fields: ${fields.map((f) => f.name).join(', ')}.` + ); + } + return field; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function describeError(error: unknown): string { + if (!(error instanceof HttpError)) { + return error instanceof Error ? error.message : String(error); + } + const { status, body } = error.response; + const detail = extractDetail(body); + switch (status) { + case 400: + return `Baserow rejected the request: ${detail}`; + case 401: + return `Baserow authentication failed. Check the connection credentials. ${detail}`; + case 402: + return `This Baserow feature requires a Premium or higher plan. ${detail}`; + case 403: + return `The Baserow connection does not have permission for this operation. ${detail}`; + case 404: + return `The Baserow resource was not found. Check the IDs. ${detail}`; + case 429: + return `Baserow rate limit reached. Retry after a short wait. ${detail}`; + default: + return `Baserow request failed with status ${status}: ${detail}`; + } +} + +function extractDetail(body: unknown): string { + if (isRecord(body)) { + const code = typeof body['error'] === 'string' ? body['error'] : ''; + const detail = body['detail']; + const detailText = typeof detail === 'string' ? detail : detail === undefined ? '' : JSON.stringify(detail); + return [code, detailText].filter((part) => part.length > 0).join(' — '); + } + return typeof body === 'string' ? body : JSON.stringify(body); +} diff --git a/packages/pieces/community/baserow/src/lib/common/ai-props.ts b/packages/pieces/community/baserow/src/lib/common/ai-props.ts new file mode 100644 index 000000000000..636b42eaeef0 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/common/ai-props.ts @@ -0,0 +1,40 @@ +import { Property } from '@activepieces/pieces-framework'; + +export const baserowAiProps = { + tableIdProp, + rowIdProp, + fieldIdProp, + workspaceIdProp, +}; + +function tableIdProp() { + return Property.Number({ + displayName: 'Table ID', + description: 'The numeric Baserow table ID. Resolve it with List Tables.', + required: true, + }); +} + +function rowIdProp() { + return Property.Number({ + displayName: 'Row ID', + description: 'The numeric row ID. Resolve it with Find Row or List Rows.', + required: true, + }); +} + +function fieldIdProp() { + return Property.Number({ + displayName: 'Field ID', + description: 'The numeric field ID. Resolve it with Get Table Fields.', + required: true, + }); +} + +function workspaceIdProp() { + return Property.Number({ + displayName: 'Workspace ID', + description: 'The numeric workspace ID. Resolve it with List Workspaces.', + required: true, + }); +} diff --git a/packages/pieces/community/baserow/src/lib/common/client.ts b/packages/pieces/community/baserow/src/lib/common/client.ts index a25422fd0faa..2d717db7a86b 100644 --- a/packages/pieces/community/baserow/src/lib/common/client.ts +++ b/packages/pieces/community/baserow/src/lib/common/client.ts @@ -255,4 +255,235 @@ export class BaserowClient { { url } ); } + async queryRows({ + tableId, + query, + }: { + tableId: number; + query: QueryParams; + }): Promise { + return await this.makeRequest( + HttpMethod.GET, + `/database/rows/table/${tableId}/`, + { ...query, user_field_names: 'true' } + ); + } + async listAllViews({ tableId }: { tableId: number }): Promise[]> { + return await this.makeRequest[]>( + HttpMethod.GET, + `/database/views/table/${tableId}/` + ); + } + async listApplications(): Promise { + return await this.makeRequest(HttpMethod.GET, `/applications/`); + } + async listWorkspaces(): Promise[]> { + return await this.makeRequest[]>(HttpMethod.GET, `/workspaces/`); + } + async createDatabase({ + workspaceId, + name, + }: { + workspaceId: number; + name: string; + }): Promise> { + return await this.makeRequest( + HttpMethod.POST, + `/applications/workspace/${workspaceId}/`, + undefined, + { type: 'database', name, init_with_data: false } + ); + } + async getTable({ tableId }: { tableId: number }): Promise> { + return await this.makeRequest(HttpMethod.GET, `/database/tables/${tableId}/`); + } + async createTable({ + databaseId, + name, + data, + firstRowHeader, + }: { + databaseId: number; + name: string; + data: unknown[][]; + firstRowHeader: boolean; + }): Promise> { + return await this.makeRequest( + HttpMethod.POST, + `/database/tables/database/${databaseId}/`, + undefined, + { name, data, first_row_header: firstRowHeader } + ); + } + async updateTable({ + tableId, + name, + }: { + tableId: number; + name: string; + }): Promise> { + return await this.makeRequest( + HttpMethod.PATCH, + `/database/tables/${tableId}/`, + undefined, + { name } + ); + } + async deleteTable({ tableId }: { tableId: number }): Promise { + await this.makeRequest(HttpMethod.DELETE, `/database/tables/${tableId}/`); + } + async createField({ + tableId, + body, + }: { + tableId: number; + body: Record; + }): Promise> { + return await this.makeRequest( + HttpMethod.POST, + `/database/fields/table/${tableId}/`, + undefined, + body + ); + } + async updateField({ + fieldId, + body, + }: { + fieldId: number; + body: Record; + }): Promise> { + return await this.makeRequest( + HttpMethod.PATCH, + `/database/fields/${fieldId}/`, + undefined, + body + ); + } + async deleteField({ fieldId }: { fieldId: number }): Promise> { + return await this.makeRequest(HttpMethod.DELETE, `/database/fields/${fieldId}/`); + } + async getFieldUniqueValues({ + fieldId, + limit, + splitCommaSeparated, + }: { + fieldId: number; + limit?: number; + splitCommaSeparated?: boolean; + }): Promise<{ values: string[] }> { + return await this.makeRequest<{ values: string[] }>( + HttpMethod.GET, + `/database/fields/${fieldId}/unique_row_values/`, + prepareQuery({ limit, split_comma_separated: splitCommaSeparated }) + ); + } + async listRowNames({ + tableId, + rowIds, + }: { + tableId: number; + rowIds: number[]; + }): Promise>> { + return await this.makeRequest>>( + HttpMethod.GET, + `/database/rows/names/`, + { [`table__${tableId}`]: rowIds.join(',') } + ); + } + async getRowHistory({ + tableId, + rowId, + limit, + offset, + }: { + tableId: number; + rowId: number; + limit?: number; + offset?: number; + }): Promise> { + return await this.makeRequest( + HttpMethod.GET, + `/database/rows/table/${tableId}/${rowId}/history/`, + prepareQuery({ limit, offset }) + ); + } + async listWorkspaceUsers({ + workspaceId, + search, + }: { + workspaceId: number; + search?: string; + }): Promise[]> { + return await this.makeRequest[]>( + HttpMethod.GET, + `/workspaces/users/workspace/${workspaceId}/`, + prepareQuery({ search }) + ); + } + async searchWorkspace({ + workspaceId, + query, + limit, + offset, + }: { + workspaceId: number; + query: string; + limit?: number; + offset?: number; + }): Promise> { + return await this.makeRequest( + HttpMethod.GET, + `/search/workspace/${workspaceId}/`, + prepareQuery({ query, limit, offset }) + ); + } + async exportTable({ + tableId, + body, + }: { + tableId: number; + body: Record; + }): Promise> { + return await this.makeRequest( + HttpMethod.POST, + `/database/export/table/${tableId}/`, + undefined, + body + ); + } + async getExportJob({ jobId }: { jobId: number }): Promise> { + return await this.makeRequest(HttpMethod.GET, `/database/export/${jobId}/`); + } + async createView({ + tableId, + name, + type, + }: { + tableId: number; + name: string; + type: string; + }): Promise> { + return await this.makeRequest( + HttpMethod.POST, + `/database/views/table/${tableId}/`, + undefined, + { name, type } + ); + } } + +type BaserowRowPage = { + count: number; + next: string | null; + previous: string | null; + results: Array<{ id: number } & Record>; +}; + +type BaserowApplication = { + id: number; + name: string; + type: string; + workspace?: { id: number; name: string }; + tables?: BaserowTable[]; +}; diff --git a/packages/pieces/community/baserow/src/lib/output-schemas.ts b/packages/pieces/community/baserow/src/lib/output-schemas.ts new file mode 100644 index 000000000000..ce7bf4bbd542 --- /dev/null +++ b/packages/pieces/community/baserow/src/lib/output-schemas.ts @@ -0,0 +1,425 @@ +import { OutputSchema } from '@activepieces/pieces-framework'; + +const rowFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Row ID', format: 'number' }, + { key: 'order', label: 'Order' }, + { key: 'fields', label: 'Row Fields', value: '', dynamicKey: true }, +]; + +const rowChangeFields: OutputSchema['fields'] = [ + { key: 'row', label: 'Row', children: rowFields }, + { key: 'previous', label: 'Previous Row', children: rowFields }, +]; + +const rowListFields: OutputSchema['fields'] = [ + { key: 'count', label: 'Count', format: 'number' }, + { key: 'rows', label: 'Rows', labelKey: 'id', listItems: rowFields }, +]; + +const tableFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Table ID', format: 'number' }, + { key: 'name', label: 'Table Name' }, + { key: 'order', label: 'Order', format: 'number' }, + { key: 'database_id', label: 'Database ID', format: 'number' }, +]; + +const selectOptionFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Option ID', format: 'number' }, + { key: 'value', label: 'Value' }, +]; + +const fieldDefinitionFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Field ID', format: 'number' }, + { key: 'table_id', label: 'Table ID', format: 'number' }, + { key: 'name', label: 'Field Name' }, + { key: 'type', label: 'Field Type' }, + { key: 'primary', label: 'Primary', format: 'boolean' }, + { key: 'read_only', label: 'Read Only', format: 'boolean' }, + { key: 'description', label: 'Description' }, + { + key: 'select_options', + label: 'Select Options', + labelKey: 'value', + listItems: [...selectOptionFields, { key: 'color', label: 'Color' }], + }, +]; + +export const rowOutputSchema: OutputSchema = { fields: rowFields }; + +export const listRowsOutputSchema: OutputSchema = { fields: rowListFields }; + +export const listRowsAiOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Total Matching Rows', format: 'number' }, + { key: 'has_more', label: 'Has More Pages', format: 'boolean' }, + { key: 'rows', label: 'Rows', labelKey: 'id', listItems: rowFields }, + ], +}; + +export const findRowOutputSchema: OutputSchema = { + fields: [ + { key: 'found', label: 'Found', format: 'boolean' }, + { key: 'count', label: 'Match Count', format: 'number' }, + ...rowFields, + ], +}; + +export const findRowAiOutputSchema: OutputSchema = { + fields: [ + { key: 'found', label: 'Found', format: 'boolean' }, + { key: 'count', label: 'Match Count', format: 'number' }, + { key: 'row', label: 'Row', children: rowFields }, + ], +}; + +export const upsertRowOutputSchema: OutputSchema = { + fields: [ + { key: 'action', label: 'Action Taken' }, + { key: 'row', label: 'Row', children: rowFields }, + ], +}; + +export const batchRowsOutputSchema: OutputSchema = { fields: rowListFields }; + +export const deleteRowOutputSchema: OutputSchema = { + fields: [{ key: 'success', label: 'Success', format: 'boolean' }], +}; + +export const deleteRowAiOutputSchema: OutputSchema = { + fields: [ + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + { key: 'table_id', label: 'Table ID', format: 'number' }, + { key: 'row_id', label: 'Row ID', format: 'number' }, + ], +}; + +export const batchDeleteRowsOutputSchema: OutputSchema = { + fields: [{ key: 'deleted_count', label: 'Deleted Count', format: 'number' }], +}; + +export const batchDeleteRowsAiOutputSchema: OutputSchema = { + fields: [ + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + { key: 'count', label: 'Deleted Count', format: 'number' }, + { key: 'row_ids', label: 'Deleted Row IDs' }, + ], +}; + +export const aggregateFieldOutputSchema: OutputSchema = { + fields: [{ key: 'result', label: 'Result' }], +}; + +export const aggregateFieldAiOutputSchema: OutputSchema = { + fields: [ + { key: 'aggregation_type', label: 'Aggregation Type' }, + { key: 'value', label: 'Value' }, + ], +}; + +export const uploadFileOutputSchema: OutputSchema = { + fields: [ + { key: 'name', label: 'File Name', description: 'Pass as [{"name": ""}] to a file field.' }, + { key: 'original_name', label: 'Original Name' }, + { key: 'url', label: 'URL', format: 'url' }, + { key: 'size', label: 'Size', format: 'filesize' }, + { key: 'mime_type', label: 'MIME Type' }, + { key: 'is_image', label: 'Is Image', format: 'boolean' }, + { key: 'image_width', label: 'Image Width', format: 'number' }, + { key: 'image_height', label: 'Image Height', format: 'number' }, + { key: 'uploaded_at', label: 'Uploaded At', format: 'datetime' }, + { key: 'thumbnails', label: 'Thumbnails', dynamicKey: true }, + ], +}; + +export const listTablesOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'tables', + label: 'Tables', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Table ID', format: 'number' }, + { key: 'name', label: 'Table Name' }, + { key: 'database_id', label: 'Database ID', format: 'number' }, + ], + }, + ], +}; + +export const getTableFieldsOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'fields', + label: 'Fields', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Field ID', format: 'number' }, + { key: 'name', label: 'Field Name' }, + { key: 'type', label: 'Field Type' }, + { key: 'primary', label: 'Primary', format: 'boolean' }, + { key: 'read_only', label: 'Read Only', format: 'boolean' }, + { key: 'select_options', label: 'Select Options', labelKey: 'value', listItems: selectOptionFields }, + { key: 'link_row_table_id', label: 'Linked Table ID', format: 'number' }, + ], + }, + ], +}; + +export const listViewsOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'views', + label: 'Views', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'View ID', format: 'number' }, + { key: 'name', label: 'View Name' }, + { key: 'type', label: 'View Type' }, + ], + }, + ], +}; + +export const listWorkspacesOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'workspaces', + label: 'Workspaces', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Workspace ID', format: 'number' }, + { key: 'name', label: 'Workspace Name' }, + { key: 'permissions', label: 'Permission Level' }, + ], + }, + ], +}; + +export const listDatabasesOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'databases', + label: 'Databases', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Database ID', format: 'number' }, + { key: 'name', label: 'Database Name' }, + { key: 'workspace_id', label: 'Workspace ID', format: 'number' }, + { key: 'workspace_name', label: 'Workspace Name' }, + { + key: 'tables', + label: 'Tables', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Table ID', format: 'number' }, + { key: 'name', label: 'Table Name' }, + ], + }, + ], + }, + ], +}; + +export const createDatabaseOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Database ID', format: 'number' }, + { key: 'name', label: 'Database Name' }, + { + key: 'workspace', + label: 'Workspace', + children: [ + { key: 'id', label: 'Workspace ID', format: 'number' }, + { key: 'name', label: 'Workspace Name' }, + ], + }, + ], +}; + +export const tableOutputSchema: OutputSchema = { fields: tableFields }; + +export const createTableOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Table ID', format: 'number' }, + { key: 'name', label: 'Table Name' }, + { key: 'database_id', label: 'Database ID', format: 'number' }, + ], +}; + +export const deleteTableOutputSchema: OutputSchema = { + fields: [ + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + { key: 'table_id', label: 'Table ID', format: 'number' }, + ], +}; + +export const fieldOutputSchema: OutputSchema = { fields: fieldDefinitionFields }; + +export const deleteFieldOutputSchema: OutputSchema = { + fields: [ + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + { key: 'field_id', label: 'Field ID', format: 'number' }, + { key: 'related_fields', label: 'Related Fields Changed', labelKey: 'name', listItems: fieldDefinitionFields }, + ], +}; + +export const getFieldUniqueValuesOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { key: 'values', label: 'Values' }, + ], +}; + +export const listRowNamesOutputSchema: OutputSchema = { + fields: [ + { + key: 'rows', + label: 'Rows', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Row ID', format: 'number' }, + { key: 'name', label: 'Name' }, + ], + }, + ], +}; + +export const getRowHistoryOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Total Entries', format: 'number' }, + { key: 'has_more', label: 'Has More', format: 'boolean' }, + { + key: 'entries', + label: 'Entries', + labelKey: 'timestamp', + listItems: [ + { key: 'id', label: 'Entry ID', format: 'number' }, + { key: 'action_type', label: 'Action Type' }, + { key: 'timestamp', label: 'Changed At', format: 'datetime' }, + { + key: 'user', + label: 'Changed By', + children: [ + { key: 'id', label: 'User ID', format: 'number' }, + { key: 'name', label: 'Name' }, + ], + }, + { key: 'before', label: 'Before', dynamicKey: true }, + { key: 'after', label: 'After', dynamicKey: true }, + ], + }, + ], +}; + +export const searchWorkspaceOutputSchema: OutputSchema = { + fields: [ + { key: 'has_more', label: 'Has More', format: 'boolean' }, + { + key: 'results', + label: 'Results', + labelKey: 'title', + listItems: [ + { key: 'type', label: 'Result Type' }, + { key: 'id', label: 'Result ID', format: 'number' }, + { key: 'title', label: 'Title' }, + { key: 'subtitle', label: 'Subtitle' }, + { key: 'description', label: 'Description' }, + { + key: 'metadata', + label: 'Location', + children: [ + { key: 'workspace_id', label: 'Workspace ID', format: 'number' }, + { key: 'database_id', label: 'Database ID', format: 'number' }, + { key: 'database_name', label: 'Database Name' }, + { key: 'table_id', label: 'Table ID', format: 'number' }, + { key: 'table_name', label: 'Table Name' }, + { key: 'row_id', label: 'Row ID', format: 'number' }, + { key: 'field_id', label: 'Field ID', format: 'number' }, + { key: 'field_name', label: 'Field Name' }, + { key: 'primary_field_value', label: 'Primary Field Value' }, + ], + }, + ], + }, + ], +}; + +export const listWorkspaceUsersOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'users', + label: 'Users', + labelKey: 'name', + listItems: [ + { key: 'user_id', label: 'User ID', format: 'number' }, + { key: 'name', label: 'Name' }, + { key: 'email', label: 'Email', format: 'email' }, + { key: 'permissions', label: 'Role' }, + ], + }, + ], +}; + +export const exportTableOutputSchema: OutputSchema = { + fields: [ + { key: 'job_id', label: 'Export Job ID', format: 'number' }, + { key: 'state', label: 'State' }, + ], +}; + +export const getExportJobOutputSchema: OutputSchema = { + fields: [ + { key: 'job_id', label: 'Export Job ID', format: 'number' }, + { key: 'state', label: 'State' }, + { key: 'progress_percentage', label: 'Progress (%)', format: 'number' }, + { key: 'file_name', label: 'File Name' }, + { key: 'url', label: 'Download URL', format: 'url' }, + ], +}; + +export const createViewOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'View ID', format: 'number' }, + { key: 'name', label: 'View Name' }, + { key: 'type', label: 'View Type' }, + { key: 'table_id', label: 'Table ID', format: 'number' }, + ], +}; + +export const rowUpdatedTriggerOutputSchema: OutputSchema = { fields: rowChangeFields }; + +export const rowDeletedTriggerOutputSchema: OutputSchema = { + fields: [{ key: 'id', label: 'Deleted Row ID', format: 'number' }], +}; + +export const rowEventTriggerOutputSchema: OutputSchema = { + fields: [ + { key: 'event_type', label: 'Event Type' }, + { key: 'row', label: 'Row', children: rowFields }, + { key: 'previous_row', label: 'Previous Row', children: rowFields }, + ], +}; + +export const rowsUpdatedTriggerOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { key: 'rows', label: 'Rows', listItems: rowChangeFields }, + ], +}; + +export const rowsDeletedTriggerOutputSchema: OutputSchema = { + fields: [ + { key: 'count', label: 'Count', format: 'number' }, + { + key: 'rows', + label: 'Deleted Rows', + labelKey: 'id', + listItems: [{ key: 'id', label: 'Row ID', format: 'number' }], + }, + ], +}; diff --git a/packages/pieces/community/baserow/src/lib/triggers/row-created.ts b/packages/pieces/community/baserow/src/lib/triggers/row-created.ts index 4cfd51167101..3a20acdf4222 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/row-created.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/row-created.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.created'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowCreatedTrigger = createTrigger({ name: 'baserow_row_created', classification: 'READ', + outputSchema: rowOutputSchema, auth: baserowAuth, displayName: 'New Row', description: 'Triggers when a new row is created in a Baserow table.', diff --git a/packages/pieces/community/baserow/src/lib/triggers/row-deleted.ts b/packages/pieces/community/baserow/src/lib/triggers/row-deleted.ts index 5d32d930c2f1..3da156a92651 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/row-deleted.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/row-deleted.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowDeletedTriggerOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.deleted'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowDeletedTrigger = createTrigger({ name: 'baserow_row_deleted', classification: 'READ', + outputSchema: rowDeletedTriggerOutputSchema, auth: baserowAuth, displayName: 'Deleted Row', description: 'Triggers when a row is deleted from a Baserow table.', diff --git a/packages/pieces/community/baserow/src/lib/triggers/row-event.ts b/packages/pieces/community/baserow/src/lib/triggers/row-event.ts index c07f80466f78..34f71352cac0 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/row-event.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/row-event.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowEventTriggerOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.created', 'rows.updated', 'rows.deleted'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowEventTrigger = createTrigger({ name: 'baserow_row_event', classification: 'READ', + outputSchema: rowEventTriggerOutputSchema, auth: baserowAuth, displayName: 'Any Row Change', description: diff --git a/packages/pieces/community/baserow/src/lib/triggers/row-updated.ts b/packages/pieces/community/baserow/src/lib/triggers/row-updated.ts index 06b3ad343431..ed9cbca258a6 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/row-updated.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/row-updated.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowUpdatedTriggerOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.updated'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowUpdatedTrigger = createTrigger({ name: 'baserow_row_updated', classification: 'READ', + outputSchema: rowUpdatedTriggerOutputSchema, auth: baserowAuth, displayName: 'Updated Row', description: 'Triggers when an existing row is updated in a Baserow table.', diff --git a/packages/pieces/community/baserow/src/lib/triggers/rows-created.ts b/packages/pieces/community/baserow/src/lib/triggers/rows-created.ts index 42fbd8b9e3c4..1c34c27ff1a2 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/rows-created.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/rows-created.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { listRowsOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.created'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowsCreatedTrigger = createTrigger({ name: 'baserow_rows_created', classification: 'READ', + outputSchema: listRowsOutputSchema, auth: baserowAuth, displayName: 'New Rows (Batch)', description: diff --git a/packages/pieces/community/baserow/src/lib/triggers/rows-deleted.ts b/packages/pieces/community/baserow/src/lib/triggers/rows-deleted.ts index 8b59d42962b1..db41325b9202 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/rows-deleted.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/rows-deleted.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowsDeletedTriggerOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.deleted'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowsDeletedTrigger = createTrigger({ name: 'baserow_rows_deleted', classification: 'READ', + outputSchema: rowsDeletedTriggerOutputSchema, auth: baserowAuth, displayName: 'Deleted Rows (Batch)', description: diff --git a/packages/pieces/community/baserow/src/lib/triggers/rows-updated.ts b/packages/pieces/community/baserow/src/lib/triggers/rows-updated.ts index 5ebced3a5d36..140d7429c502 100644 --- a/packages/pieces/community/baserow/src/lib/triggers/rows-updated.ts +++ b/packages/pieces/community/baserow/src/lib/triggers/rows-updated.ts @@ -2,6 +2,7 @@ import { createTrigger, TriggerStrategy } from '@activepieces/pieces-framework'; import { baserowAuth } from '../auth'; import { baserowCommon, makeClient } from '../common'; import { createWebhookTriggerHooks, dynamicWebhookInstructions } from '../common/webhook-trigger'; +import { rowsUpdatedTriggerOutputSchema } from '../output-schemas'; const triggerHooks = createWebhookTriggerHooks({ events: ['rows.updated'], @@ -11,6 +12,7 @@ const triggerHooks = createWebhookTriggerHooks({ export const rowsUpdatedTrigger = createTrigger({ name: 'baserow_rows_updated', classification: 'READ', + outputSchema: rowsUpdatedTriggerOutputSchema, auth: baserowAuth, displayName: 'Updated Rows (Batch)', description: diff --git a/packages/pieces/community/monday/package.json b/packages/pieces/community/monday/package.json index 0f54f1c2da37..76c9d2cf1881 100644 --- a/packages/pieces/community/monday/package.json +++ b/packages/pieces/community/monday/package.json @@ -1,6 +1,6 @@ { "name": "@activepieces/piece-monday", - "version": "0.3.8", + "version": "0.4.0", "main": "./dist/src/index.js", "types": "./dist/src/index.d.ts", "dependencies": { diff --git a/packages/pieces/community/monday/src/index.ts b/packages/pieces/community/monday/src/index.ts index acccaee532ac..d2caa689b297 100644 --- a/packages/pieces/community/monday/src/index.ts +++ b/packages/pieces/community/monday/src/index.ts @@ -1,4 +1,4 @@ -import { PieceAuth, createPiece } from '@activepieces/pieces-framework'; +import { createPiece } from '@activepieces/pieces-framework'; import { PieceCategory } from '@activepieces/pieces-framework'; import { createColumnAction } from './lib/actions/create-column'; import { createGroupAction } from './lib/actions/create-group'; @@ -12,19 +12,103 @@ import { newItemInBoardTrigger } from './lib/triggers/new-item-in-board'; import { specificColumnValueUpdatedTrigger } from './lib/triggers/specific-column-updated'; import { uploadFileToColumnAction } from './lib/actions/upload-file-to-column'; import { mondayAuth } from './lib/auth'; - -const markdown = ` -1.Log into your monday.com account.\n -2.Click on your avatar/profile picture in the top right corner.\n -3.Select **Administration** (this requires you to have admin permissions).\n -4.Go to the **API** section.\n -5.Copy your personal token`; +import { addTeamsToBoardAction } from './lib/actions/ai/boards/add-teams-to-board'; +import { addUsersToBoardAction } from './lib/actions/ai/boards/add-users-to-board'; +import { archiveBoardAction } from './lib/actions/ai/boards/archive-board'; +import { createBoardAction } from './lib/actions/ai/boards/create-board'; +import { deleteBoardAction } from './lib/actions/ai/boards/delete-board'; +import { duplicateBoardAction } from './lib/actions/ai/boards/duplicate-board'; +import { getBoardActivityLogsAction } from './lib/actions/ai/boards/get-board-activity-logs'; +import { listBoardViewsAction } from './lib/actions/ai/boards/list-board-views'; +import { listBoardsAction } from './lib/actions/ai/boards/list-boards'; +import { removeBoardSubscribersAction } from './lib/actions/ai/boards/remove-board-subscribers'; +import { removeTeamsFromBoardAction } from './lib/actions/ai/boards/remove-teams-from-board'; +import { updateBoardHierarchyAction } from './lib/actions/ai/boards/update-board-hierarchy'; +import { updateBoardAction } from './lib/actions/ai/boards/update-board'; +import { addBoardColumnAction } from './lib/actions/ai/columns/add-board-column'; +import { deleteColumnAction } from './lib/actions/ai/columns/delete-column'; +import { getColumnTypeSchemaAction } from './lib/actions/ai/columns/get-column-type-schema'; +import { listColumnsAction } from './lib/actions/ai/columns/list-columns'; +import { updateColumnAction } from './lib/actions/ai/columns/update-column'; +import { appendMarkdownToDocAction } from './lib/actions/ai/docs/append-markdown-to-doc'; +import { createDocBlockAction } from './lib/actions/ai/docs/create-doc-block'; +import { createDocAction } from './lib/actions/ai/docs/create-doc'; +import { deleteDocBlockAction } from './lib/actions/ai/docs/delete-doc-block'; +import { deleteDocAction } from './lib/actions/ai/docs/delete-doc'; +import { duplicateDocAction } from './lib/actions/ai/docs/duplicate-doc'; +import { exportDocAsMarkdownAction } from './lib/actions/ai/docs/export-doc-as-markdown'; +import { getDocBlocksAction } from './lib/actions/ai/docs/get-doc-blocks'; +import { importDocFromHtmlAction } from './lib/actions/ai/docs/import-doc-from-html'; +import { listDocsAction } from './lib/actions/ai/docs/list-docs'; +import { renameDocAction } from './lib/actions/ai/docs/rename-doc'; +import { updateDocBlockAction } from './lib/actions/ai/docs/update-doc-block'; +import { getAssetsAction } from './lib/actions/ai/files/get-assets'; +import { uploadFileAction } from './lib/actions/ai/files/upload-file'; +import { createFolderAction } from './lib/actions/ai/folders/create-folder'; +import { deleteFolderAction } from './lib/actions/ai/folders/delete-folder'; +import { listFoldersAction } from './lib/actions/ai/folders/list-folders'; +import { updateFolderAction } from './lib/actions/ai/folders/update-folder'; +import { addBoardGroupAction } from './lib/actions/ai/groups/add-board-group'; +import { archiveGroupAction } from './lib/actions/ai/groups/archive-group'; +import { deleteGroupAction } from './lib/actions/ai/groups/delete-group'; +import { duplicateGroupAction } from './lib/actions/ai/groups/duplicate-group'; +import { listGroupsAction } from './lib/actions/ai/groups/list-groups'; +import { updateGroupAction } from './lib/actions/ai/groups/update-group'; +import { archiveItemAction } from './lib/actions/ai/items/archive-item'; +import { changeColumnValueAction } from './lib/actions/ai/items/change-column-value'; +import { changeItemPositionAction } from './lib/actions/ai/items/change-item-position'; +import { changeSimpleColumnValueAction } from './lib/actions/ai/items/change-simple-column-value'; +import { clearItemUpdatesAction } from './lib/actions/ai/items/clear-item-updates'; +import { createBoardItemAction } from './lib/actions/ai/items/create-board-item'; +import { createSubitemAction } from './lib/actions/ai/items/create-subitem'; +import { deleteItemAction } from './lib/actions/ai/items/delete-item'; +import { duplicateItemAction } from './lib/actions/ai/items/duplicate-item'; +import { getItemsAction } from './lib/actions/ai/items/get-items'; +import { listBoardItemsAction } from './lib/actions/ai/items/list-board-items'; +import { listSubitemsAction } from './lib/actions/ai/items/list-subitems'; +import { moveItemToBoardAction } from './lib/actions/ai/items/move-item-to-board'; +import { moveItemToGroupAction } from './lib/actions/ai/items/move-item-to-group'; +import { renameItemAction } from './lib/actions/ai/items/rename-item'; +import { searchItemsByColumnValuesAction } from './lib/actions/ai/items/search-items-by-column-values'; +import { setItemColumnValuesAction } from './lib/actions/ai/items/set-item-column-values'; +import { updateAssetsOnItemAction } from './lib/actions/ai/items/update-assets-on-item'; +import { aggregateBoardDataAction } from './lib/actions/ai/other/aggregate-board-data'; +import { createOrGetTagAction } from './lib/actions/ai/other/create-or-get-tag'; +import { getBoardMuteSettingsAction } from './lib/actions/ai/other/get-board-mute-settings'; +import { getFavoritesAction } from './lib/actions/ai/other/get-favorites'; +import { getFormAction } from './lib/actions/ai/other/get-form'; +import { listNotificationsAction } from './lib/actions/ai/other/list-notifications'; +import { listTagsAction } from './lib/actions/ai/other/list-tags'; +import { searchAction } from './lib/actions/ai/other/search'; +import { sendNotificationAction } from './lib/actions/ai/other/send-notification'; +import { updateBoardMuteSettingsAction } from './lib/actions/ai/other/update-board-mute-settings'; +import { deleteUpdateAction } from './lib/actions/ai/updates/delete-update'; +import { editUpdateAction } from './lib/actions/ai/updates/edit-update'; +import { likeUpdateAction } from './lib/actions/ai/updates/like-update'; +import { listBoardUpdatesAction } from './lib/actions/ai/updates/list-board-updates'; +import { listItemUpdatesAction } from './lib/actions/ai/updates/list-item-updates'; +import { pinUpdateAction } from './lib/actions/ai/updates/pin-update'; +import { postItemUpdateAction } from './lib/actions/ai/updates/post-item-update'; +import { unlikeUpdateAction } from './lib/actions/ai/updates/unlike-update'; +import { unpinUpdateAction } from './lib/actions/ai/updates/unpin-update'; +import { getAccountAction } from './lib/actions/ai/users/get-account'; +import { getMeAction } from './lib/actions/ai/users/get-me'; +import { listTeamsAction } from './lib/actions/ai/users/list-teams'; +import { listUsersAction } from './lib/actions/ai/users/list-users'; +import { addTeamsToWorkspaceAction } from './lib/actions/ai/workspaces/add-teams-to-workspace'; +import { addUsersToWorkspaceAction } from './lib/actions/ai/workspaces/add-users-to-workspace'; +import { createWorkspaceAction } from './lib/actions/ai/workspaces/create-workspace'; +import { deleteTeamsFromWorkspaceAction } from './lib/actions/ai/workspaces/delete-teams-from-workspace'; +import { deleteUsersFromWorkspaceAction } from './lib/actions/ai/workspaces/delete-users-from-workspace'; +import { deleteWorkspaceAction } from './lib/actions/ai/workspaces/delete-workspace'; +import { listWorkspacesAction } from './lib/actions/ai/workspaces/list-workspaces'; +import { updateWorkspaceAction } from './lib/actions/ai/workspaces/update-workspace'; export const monday = createPiece({ displayName: 'monday.com', description: 'Work operating system for businesses', - minimumSupportedRelease: '0.30.0', + minimumSupportedRelease: '0.88.2', logoUrl: 'https://cdn.activepieces.com/pieces/monday.png', categories: [PieceCategory.PRODUCTIVITY], authors: [ @@ -47,6 +131,97 @@ export const monday = createPiece({ updateColumnValuesOfItemAction, updateItemNameAction, uploadFileToColumnAction, + addTeamsToBoardAction, + addUsersToBoardAction, + archiveBoardAction, + createBoardAction, + deleteBoardAction, + duplicateBoardAction, + getBoardActivityLogsAction, + listBoardViewsAction, + listBoardsAction, + removeBoardSubscribersAction, + removeTeamsFromBoardAction, + updateBoardHierarchyAction, + updateBoardAction, + addBoardColumnAction, + deleteColumnAction, + getColumnTypeSchemaAction, + listColumnsAction, + updateColumnAction, + appendMarkdownToDocAction, + createDocBlockAction, + createDocAction, + deleteDocBlockAction, + deleteDocAction, + duplicateDocAction, + exportDocAsMarkdownAction, + getDocBlocksAction, + importDocFromHtmlAction, + listDocsAction, + renameDocAction, + updateDocBlockAction, + getAssetsAction, + uploadFileAction, + createFolderAction, + deleteFolderAction, + listFoldersAction, + updateFolderAction, + addBoardGroupAction, + archiveGroupAction, + deleteGroupAction, + duplicateGroupAction, + listGroupsAction, + updateGroupAction, + archiveItemAction, + changeColumnValueAction, + changeItemPositionAction, + changeSimpleColumnValueAction, + clearItemUpdatesAction, + createBoardItemAction, + createSubitemAction, + deleteItemAction, + duplicateItemAction, + getItemsAction, + listBoardItemsAction, + listSubitemsAction, + moveItemToBoardAction, + moveItemToGroupAction, + renameItemAction, + searchItemsByColumnValuesAction, + setItemColumnValuesAction, + updateAssetsOnItemAction, + aggregateBoardDataAction, + createOrGetTagAction, + getBoardMuteSettingsAction, + getFavoritesAction, + getFormAction, + listNotificationsAction, + listTagsAction, + searchAction, + sendNotificationAction, + updateBoardMuteSettingsAction, + deleteUpdateAction, + editUpdateAction, + likeUpdateAction, + listBoardUpdatesAction, + listItemUpdatesAction, + pinUpdateAction, + postItemUpdateAction, + unlikeUpdateAction, + unpinUpdateAction, + getAccountAction, + getMeAction, + listTeamsAction, + listUsersAction, + addTeamsToWorkspaceAction, + addUsersToWorkspaceAction, + createWorkspaceAction, + deleteTeamsFromWorkspaceAction, + deleteUsersFromWorkspaceAction, + deleteWorkspaceAction, + listWorkspacesAction, + updateWorkspaceAction, ], triggers: [newItemInBoardTrigger, specificColumnValueUpdatedTrigger], }); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/add-teams-to-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/add-teams-to-board.ts new file mode 100644 index 000000000000..ab9084753816 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/add-teams-to-board.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { addTeamsToBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const addTeamsToBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_add_teams_to_board', + classification: 'WRITE', + displayName: 'Add Teams to Board', + description: 'Adds teams to a board as subscribers or owners.', + audience: 'ai', + aiMetadata: { + description: + 'Give whole teams access to a monday.com board as subscribers or owners. Use to share a board with a team; to share with individual people use Add Users to Board. Requires board-owner rights. Re-adding teams already on the board leaves them unchanged, so it is safe to retry.', + idempotent: true, + }, + outputSchema: addTeamsToBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + team_ids: mondayAiProps.teamIds(), + kind: Property.StaticDropdown({ + displayName: 'Role', + required: true, + defaultValue: 'subscriber', + options: { + options: [ + { label: 'Subscriber', value: 'subscriber' }, + { label: 'Owner', value: 'owner' }, + ], + }, + }), + }, + async run(context) { + const { board_id, kind } = context.propsValue; + const teamIds = mondayApi.toStringArray(context.propsValue.team_ids); + if (teamIds.length === 0) { + throw new Error('Provide at least one team ID.'); + } + + const data = await makeClient(context.auth).query<{ add_teams_to_board: { id: string; name: string }[] | null }>({ + query: `mutation ($board_id: ID!, $team_ids: [ID!]!, $kind: BoardSubscriberKind) { + add_teams_to_board(board_id: $board_id, team_ids: $team_ids, kind: $kind) { id name } + }`, + variables: { board_id, team_ids: teamIds, kind }, + }); + + const teams = (data.add_teams_to_board ?? []).map((team) => ({ + id: team.id, + name: team.name, + })); + + return { board_id, role: kind, teams, count: teams.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/add-users-to-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/add-users-to-board.ts new file mode 100644 index 000000000000..24a1de84b45e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/add-users-to-board.ts @@ -0,0 +1,64 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { addUsersToBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const addUsersToBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_add_users_to_board', + classification: 'WRITE', + displayName: 'Add Users to Board', + description: 'Adds users to a board as subscribers or owners.', + audience: 'ai', + aiMetadata: { + description: + 'Give users access to a monday.com board as subscribers or owners. Use to share a board with specific people; to share with a whole team use Add Teams to Board. Requires board-owner rights. It sets each user role to the chosen kind, so adding an existing owner as a subscriber demotes them; repeating the same call is safe to retry.', + idempotent: true, + }, + outputSchema: addUsersToBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + user_ids: mondayAiProps.userIds(), + kind: Property.StaticDropdown({ + displayName: 'Role', + required: true, + defaultValue: 'subscriber', + options: { + options: [ + { label: 'Subscriber', value: 'subscriber' }, + { label: 'Owner', value: 'owner' }, + ], + }, + }), + }, + async run(context) { + const { board_id, kind } = context.propsValue; + const userIds = mondayApi.toStringArray(context.propsValue.user_ids); + if (userIds.length === 0) { + throw new Error('Provide at least one user ID.'); + } + + const data = await makeClient(context.auth).query<{ add_users_to_board: MondayUser[] | null }>({ + query: `mutation ($board_id: ID!, $user_ids: [ID!]!, $kind: BoardSubscriberKind) { + add_users_to_board(board_id: $board_id, user_ids: $user_ids, kind: $kind) { id name email } + }`, + variables: { board_id, user_ids: userIds, kind }, + }); + + const users = (data.add_users_to_board ?? []).map((user) => ({ + id: user.id, + name: user.name, + email: user.email ?? null, + })); + + return { board_id, role: kind, users, count: users.length }; + }, +}); + +type MondayUser = { + id: string; + name: string; + email: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/archive-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/archive-board.ts new file mode 100644 index 000000000000..ae55800c9bd2 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/archive-board.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { archiveBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const archiveBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_archive_board', + classification: 'DESTRUCTIVE', + displayName: 'Archive Board', + description: 'Archives a board.', + audience: 'ai', + aiMetadata: { + description: + 'Archive a monday.com board so it leaves active views but can be restored from the archive in the monday.com UI. Prefer this over Delete Board when a board is no longer needed. Fails if the board is already archived.', + idempotent: false, + }, + outputSchema: archiveBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ archive_board: { id: string; name: string; state: string } }>({ + query: `mutation ($board_id: ID!) { + archive_board(board_id: $board_id) { id name state } + }`, + variables: { board_id: context.propsValue.board_id }, + }); + + return { + id: data.archive_board.id, + name: data.archive_board.name, + state: data.archive_board.state, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/create-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/create-board.ts new file mode 100644 index 000000000000..aa2799d7a6e3 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/create-board.ts @@ -0,0 +1,121 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { createBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const createBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_create_board', + classification: 'WRITE', + displayName: 'Create Board', + description: 'Creates a new board.', + audience: 'ai', + aiMetadata: { + description: + 'Create a new monday.com board, optionally inside a workspace and folder or from a template. Use to start a new board; to copy an existing board with its structure or items use Duplicate Board instead. Each call creates another board, so retries duplicate.', + idempotent: false, + }, + outputSchema: createBoardActionOutputSchema, + props: { + board_name: Property.ShortText({ + displayName: 'Board Name', + required: true, + }), + board_kind: Property.StaticDropdown({ + displayName: 'Board Kind', + description: 'Who can see the board.', + required: true, + defaultValue: 'public', + options: { + options: [ + { label: 'Public (main)', value: 'public' }, + { label: 'Private', value: 'private' }, + { label: 'Shareable', value: 'share' }, + ], + }, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + workspace_id: Property.ShortText({ + displayName: 'Workspace ID', + description: 'Workspace to create the board in. Resolve it with List Workspaces. Omit for the main workspace.', + required: false, + }), + folder_id: Property.ShortText({ + displayName: 'Folder ID', + description: 'Folder to place the board in; must belong to the workspace. Resolve it with List Folders.', + required: false, + }), + template_id: Property.ShortText({ + displayName: 'Template ID', + description: 'Board or template ID to create the board from.', + required: false, + }), + board_owner_ids: Property.Array({ + displayName: 'Owner User IDs', + description: 'Users to add as board owners. Resolve them with List Users.', + required: false, + }), + board_subscriber_ids: Property.Array({ + displayName: 'Subscriber User IDs', + description: 'Users to add as board subscribers. Resolve them with List Users.', + required: false, + }), + }, + async run(context) { + const { board_name, board_kind, description, workspace_id, folder_id, template_id } = context.propsValue; + const ownerIds = mondayApi.toStringArray(context.propsValue.board_owner_ids); + const subscriberIds = mondayApi.toStringArray(context.propsValue.board_subscriber_ids); + + const data = await makeClient(context.auth).query<{ create_board: MondayBoard }>({ + query: `mutation ($board_name: String!, $board_kind: BoardKind!, $description: String, $workspace_id: ID, $folder_id: ID, $template_id: ID, $board_owner_ids: [ID!], $board_subscriber_ids: [ID!]) { + create_board(board_name: $board_name, board_kind: $board_kind, description: $description, workspace_id: $workspace_id, folder_id: $folder_id, template_id: $template_id, board_owner_ids: $board_owner_ids, board_subscriber_ids: $board_subscriber_ids) { + id + name + description + state + board_kind + url + workspace_id + board_folder_id + } + }`, + variables: { + board_name, + board_kind, + description: description || undefined, + workspace_id: workspace_id || undefined, + folder_id: folder_id || undefined, + template_id: template_id || undefined, + board_owner_ids: ownerIds.length > 0 ? ownerIds : undefined, + board_subscriber_ids: subscriberIds.length > 0 ? subscriberIds : undefined, + }, + }); + + const board = data.create_board; + return { + id: board.id, + name: board.name, + description: board.description ?? null, + state: board.state, + board_kind: board.board_kind, + url: board.url, + workspace_id: board.workspace_id ?? null, + folder_id: board.board_folder_id ?? null, + }; + }, +}); + +type MondayBoard = { + id: string; + name: string; + description: string | null; + state: string; + board_kind: string; + url: string; + workspace_id: string | null; + board_folder_id: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/delete-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/delete-board.ts new file mode 100644 index 000000000000..88dd38da26c8 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/delete-board.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { deleteBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const deleteBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_board', + classification: 'DESTRUCTIVE', + displayName: 'Delete Board', + description: 'Deletes a board and all of its items.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a monday.com board together with all its groups, items and updates. Destructive: only use when the user explicitly asks to delete; prefer Archive Board, which is recoverable. Requires board-owner rights; a retry after success fails because the board is gone.', + idempotent: false, + }, + outputSchema: deleteBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ delete_board: { id: string; name: string; state: string } }>({ + query: `mutation ($board_id: ID!) { + delete_board(board_id: $board_id) { id name state } + }`, + variables: { board_id: context.propsValue.board_id }, + }); + + return { + id: data.delete_board.id, + name: data.delete_board.name, + state: data.delete_board.state, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/duplicate-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/duplicate-board.ts new file mode 100644 index 000000000000..8c0c6d08790b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/duplicate-board.ts @@ -0,0 +1,85 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { duplicateBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const duplicateBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_duplicate_board', + classification: 'WRITE', + displayName: 'Duplicate Board', + description: 'Duplicates a board with its structure and optionally its items and updates.', + audience: 'ai', + aiMetadata: { + description: + 'Copy an existing monday.com board: structure only, structure plus items, or structure plus items and updates. Use to clone a template-like board; to start from scratch use Create Board. Large boards are duplicated asynchronously, so items may appear shortly after the call returns. Each call creates another board.', + idempotent: false, + }, + outputSchema: duplicateBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + duplicate_type: Property.StaticDropdown({ + displayName: 'What to Copy', + required: true, + defaultValue: 'duplicate_board_with_structure', + options: { + options: [ + { label: 'Structure only', value: 'duplicate_board_with_structure' }, + { label: 'Structure and items', value: 'duplicate_board_with_pulses' }, + { label: 'Structure, items and updates', value: 'duplicate_board_with_pulses_and_updates' }, + ], + }, + }), + board_name: Property.ShortText({ + displayName: 'New Board Name', + description: 'Defaults to "Duplicate of ".', + required: false, + }), + workspace_id: Property.ShortText({ + displayName: 'Workspace ID', + description: 'Destination workspace. Defaults to the original board’s workspace.', + required: false, + }), + folder_id: Property.ShortText({ + displayName: 'Folder ID', + description: 'Destination folder. Defaults to the original board’s folder.', + required: false, + }), + keep_subscribers: Property.Checkbox({ + displayName: 'Keep Subscribers', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, duplicate_type, board_name, workspace_id, folder_id, keep_subscribers } = context.propsValue; + + const data = await makeClient(context.auth).query<{ + duplicate_board: { board: { id: string; name: string; url: string; workspace_id: string | null } }; + }>({ + query: `mutation ($board_id: ID!, $duplicate_type: DuplicateBoardType!, $board_name: String, $workspace_id: ID, $folder_id: ID, $keep_subscribers: Boolean) { + duplicate_board(board_id: $board_id, duplicate_type: $duplicate_type, board_name: $board_name, workspace_id: $workspace_id, folder_id: $folder_id, keep_subscribers: $keep_subscribers) { + board { id name url workspace_id } + } + }`, + variables: { + board_id, + duplicate_type, + board_name: board_name || undefined, + workspace_id: workspace_id || undefined, + folder_id: folder_id || undefined, + keep_subscribers: keep_subscribers ?? false, + }, + }); + + const result = data.duplicate_board; + return { + id: result.board.id, + name: result.board.name, + url: result.board.url, + workspace_id: result.board.workspace_id ?? null, + source_board_id: board_id, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/get-board-activity-logs.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/get-board-activity-logs.ts new file mode 100644 index 000000000000..38a743f8b199 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/get-board-activity-logs.ts @@ -0,0 +1,126 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { getBoardActivityLogsActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const getBoardActivityLogsAction = createAction({ + auth: mondayAuth, + name: 'monday_get_board_activity_logs', + classification: 'SEARCH', + displayName: 'Get Board Activity Logs', + description: 'Gets the activity log of a board.', + audience: 'ai', + aiMetadata: { + description: + 'Read the activity log of a monday.com board: who changed what and when (column value changes, item creation, moves, deletes). Narrow by time range, users, items, groups or columns. Use to audit recent changes; to read comments use List Item Updates. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getBoardActivityLogsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + from: Property.DateTime({ + displayName: 'From', + description: 'Only logs at or after this time (ISO 8601).', + required: false, + }), + to: Property.DateTime({ + displayName: 'To', + description: 'Only logs at or before this time (ISO 8601).', + required: false, + }), + item_ids: Property.Array({ + displayName: 'Item IDs', + required: false, + }), + user_ids: Property.Array({ + displayName: 'User IDs', + required: false, + }), + group_ids: Property.Array({ + displayName: 'Group IDs', + required: false, + }), + column_ids: Property.Array({ + displayName: 'Column IDs', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Logs per page (default 25).', + required: false, + defaultValue: 25, + }), + page: Property.Number({ + displayName: 'Page', + required: false, + defaultValue: 1, + }), + }, + async run(context) { + const { board_id, from, to, limit, page } = context.propsValue; + const itemIds = mondayApi.toStringArray(context.propsValue.item_ids); + const userIds = mondayApi.toStringArray(context.propsValue.user_ids); + const groupIds = mondayApi.toStringArray(context.propsValue.group_ids); + const columnIds = mondayApi.toStringArray(context.propsValue.column_ids); + + const data = await makeClient(context.auth).query<{ boards: { activity_logs: MondayActivityLog[] | null }[] }>({ + query: `query ($board_id: [ID!], $from: ISO8601DateTime, $to: ISO8601DateTime, $item_ids: [ID!], $user_ids: [ID!], $group_ids: [String], $column_ids: [String], $limit: Int, $page: Int) { + boards(ids: $board_id) { + activity_logs(from: $from, to: $to, item_ids: $item_ids, user_ids: $user_ids, group_ids: $group_ids, column_ids: $column_ids, limit: $limit, page: $page) { + id + event + entity + user_id + account_id + created_at + data + } + } + }`, + variables: { + board_id: [board_id], + from: from || undefined, + to: to || undefined, + item_ids: itemIds.length > 0 ? itemIds : undefined, + user_ids: userIds.length > 0 ? userIds : undefined, + group_ids: groupIds.length > 0 ? groupIds : undefined, + column_ids: columnIds.length > 0 ? columnIds : undefined, + limit: limit ?? 25, + page: page ?? 1, + }, + }); + + if (data.boards.length === 0) { + throw new Error(`Board ${board_id} was not found or is not accessible.`); + } + + const activityLogs = (data.boards[0].activity_logs ?? []).map((log) => ({ + id: log.id, + event: log.event, + entity: log.entity, + user_id: log.user_id, + account_id: log.account_id, + created_at: toIsoDate(log.created_at), + data: log.data, + })); + + return { activity_logs: activityLogs, count: activityLogs.length }; + }, +}); + +function toIsoDate(value: string): string { + const ticks = Number(value); + return Number.isFinite(ticks) ? new Date(Math.floor(ticks / 10000)).toISOString() : value; +} + +type MondayActivityLog = { + id: string; + event: string; + entity: string; + user_id: string; + account_id: string; + created_at: string; + data: string; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/list-board-views.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/list-board-views.ts new file mode 100644 index 000000000000..5012c345f3b1 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/list-board-views.ts @@ -0,0 +1,58 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { listBoardViewsActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const listBoardViewsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_board_views', + classification: 'SEARCH', + displayName: 'List Board Views', + description: 'Lists the views of a board.', + audience: 'ai', + aiMetadata: { + description: + 'List the views (table, kanban, form, dashboard, etc.) configured on a monday.com board, with their type and access level. Use to discover a board’s views or find a form view. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listBoardViewsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + }, + async run(context) { + const boardId = context.propsValue.board_id; + + const data = await makeClient(context.auth).query<{ boards: { views: MondayView[] | null }[] }>({ + query: `query ($ids: [ID!]) { + boards(ids: $ids) { + views { id name type access_level source_view_id } + } + }`, + variables: { ids: [boardId] }, + }); + + if (data.boards.length === 0) { + throw new Error(`Board ${boardId} was not found or is not accessible.`); + } + + const views = (data.boards[0].views ?? []).map((view) => ({ + id: view.id, + name: view.name, + type: view.type, + access_level: view.access_level, + source_view_id: view.source_view_id ?? null, + board_id: boardId, + })); + + return { views, count: views.length }; + }, +}); + +type MondayView = { + id: string; + name: string; + type: string; + access_level: string; + source_view_id: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/list-boards.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/list-boards.ts new file mode 100644 index 000000000000..210a7437da2a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/list-boards.ts @@ -0,0 +1,130 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { listBoardsActionOutputSchema } from '../../../output-schemas'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const listBoardsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_boards', + classification: 'SEARCH', + displayName: 'List Boards', + description: 'Lists boards, optionally filtered by workspace, IDs or state.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com boards the connected user can access, with their workspace, folder, owners and item count. Use to resolve a board name to the board ID that every item, column and group action needs. Filter by workspace IDs or board IDs; paginate with page and limit. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listBoardsActionOutputSchema, + props: { + workspace_ids: Property.Array({ + displayName: 'Workspace IDs', + description: 'Only return boards in these workspaces. Resolve them with List Workspaces.', + required: false, + }), + board_ids: Property.Array({ + displayName: 'Board IDs', + description: 'Only return these boards.', + required: false, + }), + state: Property.StaticDropdown({ + displayName: 'State', + required: false, + defaultValue: 'active', + options: { + options: [ + { label: 'Active', value: 'active' }, + { label: 'Archived', value: 'archived' }, + { label: 'Deleted', value: 'deleted' }, + { label: 'All', value: 'all' }, + ], + }, + }), + board_kind: Property.StaticDropdown({ + displayName: 'Board Kind', + required: false, + options: { + options: [ + { label: 'Public', value: 'public' }, + { label: 'Private', value: 'private' }, + { label: 'Shareable', value: 'share' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Boards per page (default 50).', + required: false, + defaultValue: 50, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + defaultValue: 1, + }), + }, + async run(context) { + const { state, board_kind, limit, page } = context.propsValue; + const workspaceIds = mondayApi.toStringArray(context.propsValue.workspace_ids); + const boardIds = mondayApi.toStringArray(context.propsValue.board_ids); + + const data = await makeClient(context.auth).query<{ boards: MondayBoard[] }>({ + query: `query ($ids: [ID!], $workspace_ids: [ID], $state: State, $board_kind: BoardKind, $limit: Int, $page: Int) { + boards(ids: $ids, workspace_ids: $workspace_ids, state: $state, board_kind: $board_kind, limit: $limit, page: $page) { + id + name + description + state + board_kind + url + workspace_id + board_folder_id + items_count + updated_at + owners { id name } + } + }`, + variables: { + ids: boardIds.length > 0 ? boardIds : undefined, + workspace_ids: workspaceIds.length > 0 ? workspaceIds : undefined, + state: state ?? 'active', + board_kind: board_kind ?? undefined, + limit: limit ?? 50, + page: page ?? 1, + }, + }); + + const boards = data.boards.map((board) => ({ + id: board.id, + name: board.name, + description: board.description ?? null, + state: board.state, + board_kind: board.board_kind, + url: board.url, + workspace_id: board.workspace_id ?? null, + folder_id: board.board_folder_id ?? null, + items_count: board.items_count ?? null, + updated_at: board.updated_at ?? null, + owner_ids: board.owners.map((owner) => owner.id).join(', '), + owner_names: board.owners.map((owner) => owner.name).join(', '), + })); + + return { boards, count: boards.length }; + }, +}); + +type MondayBoard = { + id: string; + name: string; + description: string | null; + state: string; + board_kind: string; + url: string; + workspace_id: string | null; + board_folder_id: string | null; + items_count: number | null; + updated_at: string | null; + owners: { id: string; name: string }[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-board-subscribers.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-board-subscribers.ts new file mode 100644 index 000000000000..111334138d0f --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-board-subscribers.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { removeBoardSubscribersActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const removeBoardSubscribersAction = createAction({ + auth: mondayAuth, + name: 'monday_remove_board_subscribers', + classification: 'DESTRUCTIVE', + displayName: 'Remove Users from Board', + description: 'Removes users from a board.', + audience: 'ai', + aiMetadata: { + description: + "Revoke users' subscriber or owner access to a monday.com board. Use to unshare a board with specific people; to remove a whole team use Remove Teams from Board. Requires board-owner rights. Removing users who are no longer members has no further effect.", + idempotent: true, + }, + outputSchema: removeBoardSubscribersActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + user_ids: mondayAiProps.userIds(), + }, + async run(context) { + const { board_id } = context.propsValue; + const userIds = mondayApi.toStringArray(context.propsValue.user_ids); + if (userIds.length === 0) { + throw new Error('Provide at least one user ID.'); + } + + const data = await makeClient(context.auth).query<{ delete_subscribers_from_board: { id: string; name: string }[] | null }>({ + query: `mutation ($board_id: ID!, $user_ids: [ID!]!) { + delete_subscribers_from_board(board_id: $board_id, user_ids: $user_ids) { id name } + }`, + variables: { board_id, user_ids: userIds }, + }); + + const users = (data.delete_subscribers_from_board ?? []).map((user) => ({ + id: user.id, + name: user.name, + })); + + return { board_id, removed_users: users, count: users.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-teams-from-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-teams-from-board.ts new file mode 100644 index 000000000000..8abc925b2b95 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/remove-teams-from-board.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { removeTeamsFromBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const removeTeamsFromBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_remove_teams_from_board', + classification: 'DESTRUCTIVE', + displayName: 'Remove Teams from Board', + description: 'Removes teams from a board.', + audience: 'ai', + aiMetadata: { + description: + "Revoke whole teams' access to a monday.com board. Use to unshare a board with a team; to remove individual people use Remove Users from Board. Requires board-owner rights. Removing teams that are no longer on the board has no further effect.", + idempotent: true, + }, + outputSchema: removeTeamsFromBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + team_ids: mondayAiProps.teamIds(), + }, + async run(context) { + const { board_id } = context.propsValue; + const teamIds = mondayApi.toStringArray(context.propsValue.team_ids); + if (teamIds.length === 0) { + throw new Error('Provide at least one team ID.'); + } + + const data = await makeClient(context.auth).query<{ delete_teams_from_board: { id: string; name: string }[] | null }>({ + query: `mutation ($board_id: ID!, $team_ids: [ID!]!) { + delete_teams_from_board(board_id: $board_id, team_ids: $team_ids) { id name } + }`, + variables: { board_id, team_ids: teamIds }, + }); + + const teams = (data.delete_teams_from_board ?? []).map((team) => ({ + id: team.id, + name: team.name, + })); + + return { board_id, removed_teams: teams, count: teams.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board-hierarchy.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board-hierarchy.ts new file mode 100644 index 000000000000..0e0b00304d5e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board-hierarchy.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { updateBoardHierarchyActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const updateBoardHierarchyAction = createAction({ + auth: mondayAuth, + name: 'monday_update_board_hierarchy', + classification: 'WRITE', + displayName: 'Move Board', + description: 'Moves a board to another workspace, folder or product.', + audience: 'ai', + aiMetadata: { + description: + 'Move a monday.com board to a different workspace, folder, or account product. Only the destinations you provide are changed. To rename a board or edit its description use Update Board. Requires board-owner rights in both places; moving to the same destination again is a no-op.', + idempotent: true, + }, + outputSchema: updateBoardHierarchyActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + workspace_id: Property.ShortText({ + displayName: 'Destination Workspace ID', + description: 'Resolve it with List Workspaces.', + required: false, + }), + folder_id: Property.ShortText({ + displayName: 'Destination Folder ID', + description: 'Must belong to the destination workspace. Resolve it with List Folders.', + required: false, + }), + account_product_id: Property.ShortText({ + displayName: 'Account Product ID', + description: 'The account product (e.g. work management, CRM) to move the board to. Requires the destination workspace ID too.', + required: false, + }), + }, + async run(context) { + const { board_id, workspace_id, folder_id, account_product_id } = context.propsValue; + const attributes = { + ...(workspace_id ? { workspace_id } : {}), + ...(folder_id ? { folder_id } : {}), + ...(account_product_id ? { account_product_id } : {}), + }; + if (Object.keys(attributes).length === 0) { + throw new Error('Provide at least one destination: workspace, folder, or account product.'); + } + if (account_product_id && !workspace_id) { + throw new Error('Moving to another account product also requires the destination workspace ID in that product.'); + } + + const data = await makeClient(context.auth).query<{ + update_board_hierarchy: { success: boolean; message: string | null; board: { id: string; workspace_id: string | null; board_folder_id: string | null } | null }; + }>({ + query: `mutation ($board_id: ID!, $attributes: UpdateBoardHierarchyAttributesInput!) { + update_board_hierarchy(board_id: $board_id, attributes: $attributes) { + success + message + board { id workspace_id board_folder_id } + } + }`, + variables: { board_id, attributes }, + }); + + const result = data.update_board_hierarchy; + if (!result.success) { + throw new Error(`monday.com did not move board ${board_id}: ${result.message ?? 'no reason given'}`); + } + return { + success: result.success, + message: result.message ?? null, + board_id: result.board?.id ?? board_id, + workspace_id: result.board?.workspace_id ?? null, + folder_id: result.board?.board_folder_id ?? null, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board.ts new file mode 100644 index 000000000000..77d5e8677bc7 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/boards/update-board.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { updateBoardActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const updateBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_update_board', + classification: 'WRITE', + displayName: 'Update Board', + description: "Updates a board's name, description or communication value.", + audience: 'ai', + aiMetadata: { + description: + "Change one attribute of an existing monday.com board: its name, description, or communication value. Only the chosen attribute is changed. To move a board to another workspace or folder use Update Board Hierarchy. Setting the same value again leaves the board unchanged, so it is safe to retry.", + idempotent: true, + }, + outputSchema: updateBoardActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + board_attribute: Property.StaticDropdown({ + displayName: 'Attribute', + required: true, + options: { + options: [ + { label: 'Name', value: 'name' }, + { label: 'Description', value: 'description' }, + { label: 'Communication', value: 'communication' }, + ], + }, + }), + new_value: Property.LongText({ + displayName: 'New Value', + required: true, + }), + }, + async run(context) { + const { board_id, board_attribute, new_value } = context.propsValue; + + const data = await makeClient(context.auth).query<{ update_board: unknown }>({ + query: `mutation ($board_id: ID!, $board_attribute: BoardAttributes!, $new_value: String!) { + update_board(board_id: $board_id, board_attribute: $board_attribute, new_value: $new_value) + }`, + variables: { board_id, board_attribute, new_value }, + }); + + const result = parseResult(data.update_board); + return { + board_id, + attribute: board_attribute, + new_value, + success: result.success, + }; + }, +}); + +function parseResult(raw: unknown): { success: boolean } { + const value = typeof raw === 'string' ? safeParse(raw) : raw; + if (typeof value === 'object' && value !== null && 'success' in value) { + return { success: Boolean(value.success) }; + } + return { success: true }; +} + +function safeParse(raw: string): unknown { + try { + return JSON.parse(raw); + } catch { + return raw; + } +} diff --git a/packages/pieces/community/monday/src/lib/actions/ai/columns/add-board-column.ts b/packages/pieces/community/monday/src/lib/actions/ai/columns/add-board-column.ts new file mode 100644 index 000000000000..2c1e59da330d --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/columns/add-board-column.ts @@ -0,0 +1,92 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { addBoardColumnActionOutputSchema } from '../../../output-schemas'; + +export const addBoardColumnAction = createAction({ + auth: mondayAuth, + name: 'monday_add_board_column', + classification: 'WRITE', + displayName: 'Add Board Column', + description: 'Adds a new column of a given type to a board.', + audience: 'ai', + aiMetadata: { + description: + 'Add a new column to a monday.com board by type (e.g. text, status, date, numbers, people, dropdown, long_text, email, link). Optionally set a description, position it after another column, and pass type defaults such as status labels (shape from Get Column Type Schema). Check List Columns first to avoid duplicates; each call creates a new column.', + idempotent: false, + }, + outputSchema: addBoardColumnActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + title: Property.ShortText({ + displayName: 'Title', + required: true, + }), + column_type: Property.ShortText({ + displayName: 'Column Type', + description: 'monday.com column type, e.g. text, long_text, status, dropdown, date, timeline, numbers, people, email, phone, link, checkbox, rating, tags, file.', + required: true, + }), + description: Property.ShortText({ + displayName: 'Description', + required: false, + }), + after_column_id: Property.ShortText({ + displayName: 'After Column ID', + description: 'Place the new column after this column ID. Leave empty to append.', + required: false, + }), + defaults: Property.Json({ + displayName: 'Defaults', + description: 'Type-specific settings, e.g. {"labels": {"1": "Done", "2": "Stuck"}} for status. See Get Column Type Schema.', + required: false, + }), + }, + async run(context) { + const { board_id, title, column_type, description, after_column_id, defaults } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_column: MondayColumn }>({ + query: `mutation ($boardId: ID!, $title: String!, $columnType: ColumnType!, $description: String, $afterColumnId: ID, $defaults: JSON) { + create_column( + board_id: $boardId + title: $title + column_type: $columnType + description: $description + after_column_id: $afterColumnId + defaults: $defaults + ) { + id + title + type + description + } + }`, + variables: { + boardId: board_id, + title, + columnType: column_type.trim(), + ...(isNil(description) ? {} : { description }), + ...(isNil(after_column_id) ? {} : { afterColumnId: after_column_id }), + ...(isNil(defaults) ? {} : { defaults: mondayApi.toJsonString(defaults) }), + }, + }); + + const column = data.create_column; + return { + id: column.id, + board_id, + title: column.title, + type: column.type, + description: column.description ?? null, + }; + }, +}); + +type MondayColumn = { + id: string; + title: string; + type: string; + description: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/columns/delete-column.ts b/packages/pieces/community/monday/src/lib/actions/ai/columns/delete-column.ts new file mode 100644 index 000000000000..900f51edb279 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/columns/delete-column.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { deleteColumnActionOutputSchema } from '../../../output-schemas'; + +export const deleteColumnAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_column', + classification: 'DESTRUCTIVE', + displayName: 'Delete Column', + description: 'Deletes a column and all its values from a board.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a column from a monday.com board, removing that column\'s values from every item. Cannot be undone through the API; to only rename use Update Column. A retry after success fails because the column no longer exists.', + idempotent: false, + }, + outputSchema: deleteColumnActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + column_id: mondayAiProps.columnId(true), + }, + async run(context) { + const { board_id, column_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ delete_column: { id: string } }>({ + query: `mutation ($boardId: ID!, $columnId: String!) { + delete_column(board_id: $boardId, column_id: $columnId) { id } + }`, + variables: { boardId: board_id, columnId: column_id }, + }); + + return { id: data.delete_column.id, board_id, deleted: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/columns/get-column-type-schema.ts b/packages/pieces/community/monday/src/lib/actions/ai/columns/get-column-type-schema.ts new file mode 100644 index 000000000000..e9e700132fc3 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/columns/get-column-type-schema.ts @@ -0,0 +1,41 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { getColumnTypeSchemaActionOutputSchema } from '../../../output-schemas'; + +export const getColumnTypeSchemaAction = createAction({ + auth: mondayAuth, + name: 'monday_get_column_type_schema', + classification: 'READ', + displayName: 'Get Column Type Schema', + description: 'Gets the JSON schema of the settings a column type accepts.', + audience: 'ai', + aiMetadata: { + description: + 'Return the JSON Schema (draft-07) describing the settings/defaults a monday.com column type accepts, e.g. status labels or dropdown options. Use before Add Board Column with defaults so the payload is valid. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getColumnTypeSchemaActionOutputSchema, + props: { + column_type: Property.ShortText({ + displayName: 'Column Type', + description: 'monday.com column type, e.g. status, dropdown, date, numbers, people, timeline, rating.', + required: true, + }), + }, + async run(context) { + const columnType = context.propsValue.column_type.trim(); + + const data = await makeClient(context.auth).query<{ get_column_type_schema: unknown }>({ + query: `query ($type: ColumnType!) { + get_column_type_schema(type: $type) + }`, + variables: { type: columnType }, + }); + + return { + column_type: columnType, + schema: JSON.stringify(data.get_column_type_schema), + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/columns/list-columns.ts b/packages/pieces/community/monday/src/lib/actions/ai/columns/list-columns.ts new file mode 100644 index 000000000000..4ac53bb5406b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/columns/list-columns.ts @@ -0,0 +1,81 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { listColumnsActionOutputSchema } from '../../../output-schemas'; + +export const listColumnsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_columns', + classification: 'SEARCH', + displayName: 'List Columns', + description: 'Lists the columns of a board with their IDs, types and settings.', + audience: 'ai', + aiMetadata: { + description: + 'List the columns of a monday.com board with each column\'s ID, title, type, description and settings (e.g. status labels, dropdown options). Call this before writing column values to learn the column IDs and accepted labels; use Get Column Type Schema for the JSON shape of a type. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listColumnsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + column_ids: Property.Array({ + displayName: 'Column IDs', + description: 'Only return these columns. Leave empty for all columns.', + required: false, + }), + }, + async run(context) { + const { board_id } = context.propsValue; + const columnIds = mondayApi.toStringArray(context.propsValue.column_ids); + + const data = await makeClient(context.auth).query<{ boards: { id: string; columns: MondayColumn[] | null }[] }>({ + query: `query ($boardIds: [ID!], $columnIds: [String]) { + boards(ids: $boardIds) { + id + columns(ids: $columnIds) { + id + title + type + description + settings + archived + width + } + } + }`, + variables: { + boardIds: [board_id], + ...(columnIds.length > 0 ? { columnIds } : {}), + }, + }); + + const board = data.boards[0]; + if (!board) { + throw new Error(`Board ${board_id} was not found or is not accessible.`); + } + + const columns = (board.columns ?? []).map((column) => ({ + id: column.id, + title: column.title, + type: column.type, + description: column.description ?? null, + settings: isNil(column.settings) ? null : JSON.stringify(column.settings), + archived: column.archived, + width: column.width ?? null, + })); + + return { board_id: board.id, columns, count: columns.length }; + }, +}); + +type MondayColumn = { + id: string; + title: string; + type: string; + description: string | null; + settings: unknown; + archived: boolean; + width: number | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/columns/update-column.ts b/packages/pieces/community/monday/src/lib/actions/ai/columns/update-column.ts new file mode 100644 index 000000000000..c93bc191a702 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/columns/update-column.ts @@ -0,0 +1,73 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { mondayAiProps } from '../../../common/ai-props'; +import { updateColumnActionOutputSchema } from '../../../output-schemas'; + +export const updateColumnAction = createAction({ + auth: mondayAuth, + name: 'monday_update_column', + classification: 'WRITE', + displayName: 'Update Column', + description: 'Changes a column\'s title and/or description.', + audience: 'ai', + aiMetadata: { + description: + 'Rename a monday.com board column and/or change its description. Only the fields you provide change; the column type and settings are untouched. Resolve the column ID with List Columns. Re-applying the same values is safe.', + idempotent: true, + }, + outputSchema: updateColumnActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + column_id: mondayAiProps.columnId(true), + title: Property.ShortText({ + displayName: 'New Title', + description: 'Leave empty to keep the current title.', + required: false, + }), + description: Property.ShortText({ + displayName: 'New Description', + description: 'Leave empty to keep the current description.', + required: false, + }), + }, + async run(context) { + const { board_id, column_id, title, description } = context.propsValue; + if (isNil(title) && isNil(description)) { + throw new Error('Provide a New Title and/or a New Description.'); + } + const client = makeClient(context.auth); + + const renamed = isNil(title) + ? null + : await client.query<{ change_column_title: MondayColumn }>({ + query: `mutation ($boardId: ID!, $columnId: String!, $title: String!) { + change_column_title(board_id: $boardId, column_id: $columnId, title: $title) { id title description } + }`, + variables: { boardId: board_id, columnId: column_id, title }, + }); + + const described = isNil(description) + ? null + : await client.query<{ change_column_metadata: MondayColumn }>({ + query: `mutation ($boardId: ID!, $columnId: String!, $value: String) { + change_column_metadata(board_id: $boardId, column_id: $columnId, column_property: description, value: $value) { id title description } + }`, + variables: { boardId: board_id, columnId: column_id, value: description }, + }); + + const column = described?.change_column_metadata ?? renamed?.change_column_title; + return { + id: column?.id ?? column_id, + board_id, + title: column?.title ?? null, + description: column?.description ?? null, + }; + }, +}); + +type MondayColumn = { + id: string; + title: string; + description: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/append-markdown-to-doc.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/append-markdown-to-doc.ts new file mode 100644 index 000000000000..bcbd8880876a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/append-markdown-to-doc.ts @@ -0,0 +1,68 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { appendMarkdownToDocActionOutputSchema } from '../../../output-schemas'; + +export const appendMarkdownToDocAction = createAction({ + auth: mondayAuth, + name: 'monday_append_markdown_to_doc', + classification: 'WRITE', + displayName: 'Append Markdown to Doc', + description: 'Adds markdown content to an existing monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Convert markdown (headings, lists, bold, code, quotes, tables) into blocks and add them to an existing monday.com doc, at the end or after a given block ID. Prefer this over Create Doc Block for anything beyond a single block. Not idempotent: each call inserts another copy of the content.', + idempotent: false, + }, + outputSchema: appendMarkdownToDocActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + markdown: Property.LongText({ + displayName: 'Markdown', + required: true, + }), + after_block_id: Property.ShortText({ + displayName: 'After Block ID', + description: 'Insert the content after this block. Resolve it with Get Doc Blocks. Leave empty to append.', + required: false, + }), + }, + async run(context) { + const { doc_id, markdown, after_block_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ add_content_to_doc_from_markdown: MarkdownResult | null }>({ + query: `mutation ($docId: ID!, $markdown: String!, $afterBlockId: String) { + add_content_to_doc_from_markdown(docId: $docId, markdown: $markdown, afterBlockId: $afterBlockId) { + success + block_ids + error + } + }`, + variables: { + docId: doc_id, + markdown, + afterBlockId: after_block_id || undefined, + }, + }); + + const result = data.add_content_to_doc_from_markdown; + if (!result || !result.success) { + throw new Error(`monday.com could not add the markdown: ${result?.error ?? 'unknown error'}`); + } + + return { + doc_id, + success: result.success, + block_ids: (result.block_ids ?? []).join(', '), + block_count: (result.block_ids ?? []).length, + }; + }, +}); + +type MarkdownResult = { + success: boolean; + block_ids: string[] | null; + error: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc-block.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc-block.ts new file mode 100644 index 000000000000..8407c4542075 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc-block.ts @@ -0,0 +1,108 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { createDocBlockActionOutputSchema } from '../../../output-schemas'; + +export const createDocBlockAction = createAction({ + auth: mondayAuth, + name: 'monday_create_doc_block', + classification: 'WRITE', + displayName: 'Create Doc Block', + description: 'Adds a single block to a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Insert one typed block (text, title, list, quote, code, divider, notice box, table, layout, image, video, page break) into a monday.com doc, at the top or after a given block. The content is the block JSON, e.g. {"deltaFormat":[{"insert":"Hello"}]} for text; tables need column_count and row_count. For multi-block text prefer Append Markdown to Doc. Each call inserts a new block.', + idempotent: false, + }, + outputSchema: createDocBlockActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + type: Property.StaticDropdown({ + displayName: 'Block Type', + required: true, + defaultValue: 'normal_text', + options: { + options: [ + { label: 'Normal text', value: 'normal_text' }, + { label: 'Large title', value: 'large_title' }, + { label: 'Medium title', value: 'medium_title' }, + { label: 'Small title', value: 'small_title' }, + { label: 'Bulleted list', value: 'bulleted_list' }, + { label: 'Numbered list', value: 'numbered_list' }, + { label: 'Check list', value: 'check_list' }, + { label: 'Quote', value: 'quote' }, + { label: 'Code', value: 'code' }, + { label: 'Notice box', value: 'notice_box' }, + { label: 'Divider', value: 'divider' }, + { label: 'Page break', value: 'page_break' }, + { label: 'Table', value: 'table' }, + { label: 'Layout', value: 'layout' }, + { label: 'Image', value: 'image' }, + { label: 'Video', value: 'video' }, + ], + }, + }), + content: Property.Json({ + displayName: 'Content', + description: 'Block content JSON, e.g. {"alignment":"left","direction":"ltr","deltaFormat":[{"insert":"new block"}]}.', + required: true, + }), + after_block_id: Property.ShortText({ + displayName: 'After Block ID', + description: 'Place the new block after this block. Resolve it with Get Doc Blocks. Leave empty to insert at the top.', + required: false, + }), + parent_block_id: Property.ShortText({ + displayName: 'Parent Block ID', + description: 'Create the block inside this parent block (e.g. a table cell or layout).', + required: false, + }), + }, + async run(context) { + const { doc_id, type, content, after_block_id, parent_block_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_doc_block: DocBlock | null }>({ + query: `mutation ($docId: ID!, $type: DocBlockContentType!, $content: JSON!, $afterBlockId: String, $parentBlockId: String) { + create_doc_block(doc_id: $docId, type: $type, content: $content, after_block_id: $afterBlockId, parent_block_id: $parentBlockId) { + id + type + position + parent_block_id + content + } + }`, + variables: { + docId: doc_id, + type, + content: mondayApi.toJsonString(content), + afterBlockId: after_block_id || undefined, + parentBlockId: parent_block_id || undefined, + }, + }); + + const block = data.create_doc_block; + if (!block) { + throw new Error('monday.com did not return the created block.'); + } + + return { + id: block.id, + doc_id, + type: block.type ?? null, + position: block.position ?? null, + parent_block_id: block.parent_block_id ?? null, + content: mondayApi.toJsonString(block.content ?? null), + }; + }, +}); + +type DocBlock = { + id: string; + type: string | null; + position: number | null; + parent_block_id: string | null; + content: unknown; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc.ts new file mode 100644 index 000000000000..31140922781e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/create-doc.ts @@ -0,0 +1,143 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { createDocActionOutputSchema } from '../../../output-schemas'; + +export const createDocAction = createAction({ + auth: mondayAuth, + name: 'monday_create_doc', + classification: 'WRITE', + displayName: 'Create Doc', + description: 'Creates an empty monday doc in a workspace or in an item doc column.', + audience: 'ai', + aiMetadata: { + description: + 'Create an empty monday.com doc either in a workspace (needs workspace ID and name) or inside a doc column on an item (needs item ID and doc column ID). Add content afterwards with Append Markdown to Doc; to create a doc with content in one step from HTML use Import Doc from HTML. Each call creates a new doc.', + idempotent: false, + }, + outputSchema: createDocActionOutputSchema, + props: { + location: Property.StaticDropdown({ + displayName: 'Location', + required: true, + defaultValue: 'workspace', + options: { + options: [ + { label: 'Workspace', value: 'workspace' }, + { label: 'Item doc column', value: 'item' }, + ], + }, + }), + workspace_id: Property.ShortText({ + displayName: 'Workspace ID', + description: 'Required for the workspace location. Resolve it with List Workspaces.', + required: false, + }), + name: Property.ShortText({ + displayName: 'Doc Name', + description: 'Required for the workspace location.', + required: false, + }), + kind: Property.StaticDropdown({ + displayName: 'Doc Kind', + description: 'Access level for a workspace doc (default public).', + required: false, + options: { + options: [ + { label: 'Public', value: 'public' }, + { label: 'Private', value: 'private' }, + { label: 'Shareable', value: 'share' }, + ], + }, + }), + item_id: Property.ShortText({ + displayName: 'Item ID', + description: 'Required for the item location. Resolve it with List Board Items.', + required: false, + }), + column_id: Property.ShortText({ + displayName: 'Doc Column ID', + description: 'Required for the item location: the ID of a doc-type column. Resolve it with List Columns.', + required: false, + }), + }, + async run(context) { + const { location, workspace_id, name, kind, item_id, column_id } = context.propsValue; + + const docLocation = buildLocation({ + location, + workspaceId: workspace_id, + name, + kind, + itemId: item_id, + columnId: column_id, + }); + + const data = await makeClient(context.auth).query<{ create_doc: CreatedDoc | null }>({ + query: `mutation ($location: CreateDocInput!) { + create_doc(location: $location) { + id + object_id + name + url + workspace_id + } + }`, + variables: { location: docLocation }, + }); + + const doc = data.create_doc; + if (!doc) { + throw new Error('monday.com did not return the created doc.'); + } + + return { + id: doc.id, + object_id: doc.object_id, + name: doc.name, + url: doc.url ?? null, + workspace_id: doc.workspace_id ?? null, + }; + }, +}); + +function buildLocation({ + location, + workspaceId, + name, + kind, + itemId, + columnId, +}: { + location: string; + workspaceId: string | undefined; + name: string | undefined; + kind: string | undefined; + itemId: string | undefined; + columnId: string | undefined; +}): Record { + if (location === 'item') { + if (!itemId || !columnId) { + throw new Error('Item ID and Doc Column ID are required to create a doc in an item doc column.'); + } + return { board: { item_id: itemId, column_id: columnId } }; + } + if (!workspaceId || !name) { + throw new Error('Workspace ID and Doc Name are required to create a doc in a workspace.'); + } + return { + workspace: { + workspace_id: workspaceId, + name, + ...(kind ? { kind } : {}), + }, + }; +} + +type CreatedDoc = { + id: string; + object_id: string; + name: string; + url: string | null; + workspace_id: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc-block.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc-block.ts new file mode 100644 index 000000000000..6039016fad88 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc-block.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { deleteDocBlockActionOutputSchema } from '../../../output-schemas'; + +export const deleteDocBlockAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_doc_block', + classification: 'DESTRUCTIVE', + displayName: 'Delete Doc Block', + description: 'Deletes a block from a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Delete one block from a monday.com doc by block ID; the rest of the doc is untouched. Resolve the block ID with Get Doc Blocks. Not idempotent: a retry on an already-deleted block errors.', + idempotent: false, + }, + outputSchema: deleteDocBlockActionOutputSchema, + props: { + block_id: Property.ShortText({ + displayName: 'Block ID', + description: 'The block to delete. Resolve it with Get Doc Blocks.', + required: true, + }), + }, + async run(context) { + const { block_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ delete_doc_block: { id: string } | null }>({ + query: `mutation ($blockId: String!) { + delete_doc_block(block_id: $blockId) { + id + } + }`, + variables: { blockId: block_id }, + }); + + return { + id: data.delete_doc_block?.id ?? block_id, + deleted: true, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc.ts new file mode 100644 index 000000000000..2e6e4f15f96f --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/delete-doc.ts @@ -0,0 +1,40 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { deleteDocActionOutputSchema } from '../../../output-schemas'; + +export const deleteDocAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_doc', + classification: 'DESTRUCTIVE', + displayName: 'Delete Doc', + description: 'Deletes a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a monday.com doc by its internal doc ID, removing all of its content. Only use when the user explicitly asks to delete the doc. Not idempotent: a retry on an already-deleted doc errors.', + idempotent: false, + }, + outputSchema: deleteDocActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + }, + async run(context) { + const { doc_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ delete_doc: unknown }>({ + query: `mutation ($docId: ID!) { + delete_doc(docId: $docId) + }`, + variables: { docId: doc_id }, + }); + + return { + doc_id, + deleted: true, + result: mondayApi.toJsonString(data.delete_doc), + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/duplicate-doc.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/duplicate-doc.ts new file mode 100644 index 000000000000..b060aecff940 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/duplicate-doc.ts @@ -0,0 +1,74 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { duplicateDocActionOutputSchema } from '../../../output-schemas'; + +export const duplicateDocAction = createAction({ + auth: mondayAuth, + name: 'monday_duplicate_doc', + classification: 'WRITE', + displayName: 'Duplicate Doc', + description: 'Creates a copy of a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Create an exact copy of an existing monday.com doc, with its content and optionally its updates, and return the new doc ID. Use to start from an existing doc as a template. Each call creates another copy.', + idempotent: false, + }, + outputSchema: duplicateDocActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + duplicate_type: Property.StaticDropdown({ + displayName: 'Duplicate Type', + required: false, + defaultValue: 'duplicate_doc_with_content', + options: { + options: [ + { label: 'Content only', value: 'duplicate_doc_with_content' }, + { label: 'Content and updates', value: 'duplicate_doc_with_content_and_updates' }, + ], + }, + }), + }, + async run(context) { + const { doc_id, duplicate_type } = context.propsValue; + const data = await makeClient(context.auth).query<{ duplicate_doc: unknown }>({ + query: `mutation ($docId: ID!, $duplicateType: DuplicateType) { + duplicate_doc(docId: $docId, duplicateType: $duplicateType) + }`, + variables: { + docId: doc_id, + duplicateType: duplicate_type ?? undefined, + }, + }); + + return { + source_doc_id: doc_id, + new_doc_id: extractDocId(data.duplicate_doc), + result: mondayApi.toJsonString(data.duplicate_doc), + }; + }, +}); + +function extractDocId(result: unknown): string | null { + const parsed = typeof result === 'string' ? safeParse(result) : result; + if (typeof parsed === 'object' && parsed !== null) { + const record: Record = { ...parsed }; + const id = record['doc_id'] ?? record['id']; + return id === undefined || id === null ? null : String(id); + } + if (typeof parsed === 'number' || typeof parsed === 'string') { + return String(parsed); + } + return null; +} + +function safeParse(value: string): unknown { + try { + return JSON.parse(value); + } catch { + return value; + } +} diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/export-doc-as-markdown.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/export-doc-as-markdown.ts new file mode 100644 index 000000000000..2e68e118e511 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/export-doc-as-markdown.ts @@ -0,0 +1,63 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { exportDocAsMarkdownActionOutputSchema } from '../../../output-schemas'; + +export const exportDocAsMarkdownAction = createAction({ + auth: mondayAuth, + name: 'monday_export_doc_as_markdown', + classification: 'READ', + displayName: 'Export Doc as Markdown', + description: 'Exports the content of a monday doc as markdown.', + audience: 'ai', + aiMetadata: { + description: + 'Read a monday.com doc (or only selected block IDs) as a single markdown string. Prefer this over Get Doc Blocks whenever you need to read or summarize the doc content. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: exportDocAsMarkdownActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + block_ids: Property.Array({ + displayName: 'Block IDs', + description: 'Only export these blocks. Resolve them with Get Doc Blocks. Leave empty for the whole doc.', + required: false, + }), + }, + async run(context) { + const { doc_id } = context.propsValue; + const blockIds = mondayApi.toStringArray(context.propsValue.block_ids); + + const data = await makeClient(context.auth).query<{ export_markdown_from_doc: ExportResult | null }>({ + query: `query ($docId: ID!, $blockIds: [String!]) { + export_markdown_from_doc(docId: $docId, blockIds: $blockIds) { + success + markdown + error + } + }`, + variables: { + docId: doc_id, + blockIds: blockIds.length > 0 ? blockIds : undefined, + }, + }); + + const result = data.export_markdown_from_doc; + if (!result || !result.success) { + throw new Error(`monday.com could not export the doc: ${result?.error ?? 'unknown error'}`); + } + + return { + doc_id, + markdown: result.markdown ?? '', + }; + }, +}); + +type ExportResult = { + success: boolean; + markdown: string | null; + error: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/get-doc-blocks.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/get-doc-blocks.ts new file mode 100644 index 000000000000..2bc0b575771c --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/get-doc-blocks.ts @@ -0,0 +1,88 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { getDocBlocksActionOutputSchema } from '../../../output-schemas'; + +export const getDocBlocksAction = createAction({ + auth: mondayAuth, + name: 'monday_get_doc_blocks', + classification: 'READ', + displayName: 'Get Doc Blocks', + description: 'Gets the content blocks of a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Fetch the raw content blocks (ID, type, position, parent and JSON content) of a monday.com doc, paginated. Use when you need block IDs to update, delete or insert after a specific block; to read the doc as text prefer Export Doc as Markdown. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getDocBlocksActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + limit: Property.Number({ + displayName: 'Limit', + description: 'Number of blocks per page (default 25).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + }), + }, + async run(context) { + const { doc_id, limit, page } = context.propsValue; + + const data = await makeClient(context.auth).query<{ docs: { id: string; blocks: MondayDocBlock[] | null }[] | null }>({ + query: `query ($ids: [ID!], $limit: Int, $page: Int) { + docs(ids: $ids) { + id + blocks(limit: $limit, page: $page) { + id + type + position + parent_block_id + content + created_at + updated_at + created_by { id } + } + } + }`, + variables: { + ids: [doc_id], + limit: limit ?? undefined, + page: page ?? undefined, + }, + }); + + const doc = (data.docs ?? [])[0]; + if (!doc) { + throw new Error(`Doc ${doc_id} was not found or is not accessible.`); + } + + const blocks = (doc.blocks ?? []).map((block) => ({ + id: block.id, + type: block.type ?? null, + position: block.position ?? null, + parent_block_id: block.parent_block_id ?? null, + content: typeof block.content === 'string' ? block.content : JSON.stringify(block.content ?? null), + created_by_id: block.created_by?.id ?? null, + created_at: block.created_at ?? null, + updated_at: block.updated_at ?? null, + })); + + return { doc_id: doc.id, blocks, count: blocks.length }; + }, +}); + +type MondayDocBlock = { + id: string; + type: string | null; + position: number | null; + parent_block_id: string | null; + content: unknown; + created_at: string | null; + updated_at: string | null; + created_by: { id: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/import-doc-from-html.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/import-doc-from-html.ts new file mode 100644 index 000000000000..7c1284c7c9cf --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/import-doc-from-html.ts @@ -0,0 +1,80 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { importDocFromHtmlActionOutputSchema } from '../../../output-schemas'; + +export const importDocFromHtmlAction = createAction({ + auth: mondayAuth, + name: 'monday_import_doc_from_html', + classification: 'WRITE', + displayName: 'Import Doc from HTML', + description: 'Creates a new monday doc in a workspace from HTML content.', + audience: 'ai', + aiMetadata: { + description: + 'Create a new monday.com doc in a workspace (optionally inside a folder) from HTML; text styles, headings, lists, quotes, tables, code and dividers are converted to doc blocks. Use when you have HTML content; for an empty doc use Create Doc, to add to an existing doc use Append Markdown to Doc. Each call creates a new doc.', + idempotent: false, + }, + outputSchema: importDocFromHtmlActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + html: Property.LongText({ + displayName: 'HTML', + required: true, + }), + title: Property.ShortText({ + displayName: 'Title', + description: 'Doc title. If empty, monday.com infers it from the HTML.', + required: false, + }), + folder_id: mondayAiProps.folderId(false), + kind: Property.StaticDropdown({ + displayName: 'Doc Kind', + required: false, + options: { + options: [ + { label: 'Public', value: 'public' }, + { label: 'Private', value: 'private' }, + { label: 'Shareable', value: 'share' }, + ], + }, + }), + }, + async run(context) { + const { workspace_id, html, title, folder_id, kind } = context.propsValue; + + const data = await makeClient(context.auth).query<{ import_doc_from_html: ImportResult | null }>({ + query: `mutation ($html: String!, $workspaceId: ID!, $title: String, $folderId: ID, $kind: DocKind) { + import_doc_from_html(html: $html, workspaceId: $workspaceId, title: $title, folderId: $folderId, kind: $kind) { + success + doc_id + error + } + }`, + variables: { + html, + workspaceId: workspace_id, + title: title || undefined, + folderId: folder_id || undefined, + kind: kind ?? undefined, + }, + }); + + const result = data.import_doc_from_html; + if (!result || !result.success) { + throw new Error(`monday.com could not import the HTML: ${result?.error ?? 'unknown error'}`); + } + + return { + doc_id: result.doc_id ?? null, + success: result.success, + }; + }, +}); + +type ImportResult = { + success: boolean; + doc_id: string | null; + error: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/list-docs.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/list-docs.ts new file mode 100644 index 000000000000..74510e2efc80 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/list-docs.ts @@ -0,0 +1,117 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { listDocsActionOutputSchema } from '../../../output-schemas'; + +export const listDocsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_docs', + classification: 'SEARCH', + displayName: 'List Docs', + description: 'Lists monday docs, optionally filtered by workspace or ID.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com docs (workdocs) with their internal doc ID, object ID, name, workspace and URL, optionally filtered by workspace IDs, doc IDs or object IDs (the ID in the doc URL). Use to resolve a doc ID for the other doc atomics; to find a doc by keyword use Search. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listDocsActionOutputSchema, + props: { + workspace_ids: Property.Array({ + displayName: 'Workspace IDs', + description: 'Only return docs in these workspaces. Resolve them with List Workspaces.', + required: false, + }), + doc_ids: Property.Array({ + displayName: 'Doc IDs', + description: 'Only return these internal doc IDs.', + required: false, + }), + object_ids: Property.Array({ + displayName: 'Object IDs', + description: 'Only return docs with these object IDs (the ID that appears in the doc URL and in doc column values).', + required: false, + }), + order_by: Property.StaticDropdown({ + displayName: 'Order By', + required: false, + options: { + options: [ + { label: 'Created at (newest first)', value: 'created_at' }, + { label: 'Last used', value: 'used_at' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Number of docs per page (default 25).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + }), + }, + async run(context) { + const { order_by, limit, page } = context.propsValue; + const workspaceIds = mondayApi.toStringArray(context.propsValue.workspace_ids); + const docIds = mondayApi.toStringArray(context.propsValue.doc_ids); + const objectIds = mondayApi.toStringArray(context.propsValue.object_ids); + + const data = await makeClient(context.auth).query<{ docs: MondayDoc[] | null }>({ + query: `query ($ids: [ID!], $object_ids: [ID!], $workspace_ids: [ID], $order_by: DocsOrderBy, $limit: Int, $page: Int) { + docs(ids: $ids, object_ids: $object_ids, workspace_ids: $workspace_ids, order_by: $order_by, limit: $limit, page: $page) { + id + object_id + name + doc_kind + url + workspace_id + doc_folder_id + created_at + updated_at + created_by { id name } + } + }`, + variables: { + ids: docIds.length > 0 ? docIds : undefined, + object_ids: objectIds.length > 0 ? objectIds : undefined, + workspace_ids: workspaceIds.length > 0 ? workspaceIds : undefined, + order_by: order_by ?? undefined, + limit: limit ?? undefined, + page: page ?? undefined, + }, + }); + + const docs = (data.docs ?? []).map((doc) => ({ + id: doc.id, + object_id: doc.object_id, + name: doc.name, + doc_kind: doc.doc_kind, + url: doc.url ?? null, + workspace_id: doc.workspace_id ?? null, + folder_id: doc.doc_folder_id ?? null, + created_by_id: doc.created_by?.id ?? null, + created_by_name: doc.created_by?.name ?? null, + created_at: doc.created_at ?? null, + updated_at: doc.updated_at ?? null, + })); + + return { docs, count: docs.length }; + }, +}); + +type MondayDoc = { + id: string; + object_id: string; + name: string; + doc_kind: string; + url: string | null; + workspace_id: string | null; + doc_folder_id: string | null; + created_at: string | null; + updated_at: string | null; + created_by: { id: string; name: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/rename-doc.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/rename-doc.ts new file mode 100644 index 000000000000..464a96c665bf --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/rename-doc.ts @@ -0,0 +1,43 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { renameDocActionOutputSchema } from '../../../output-schemas'; + +export const renameDocAction = createAction({ + auth: mondayAuth, + name: 'monday_rename_doc', + classification: 'WRITE', + displayName: 'Rename Doc', + description: 'Changes the name of a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Rename an existing monday.com doc by its internal doc ID. Only changes the title, never the content. Safe to retry: re-applying the same name leaves the doc unchanged.', + idempotent: true, + }, + outputSchema: renameDocActionOutputSchema, + props: { + doc_id: mondayAiProps.docId(), + name: Property.ShortText({ + displayName: 'New Name', + required: true, + }), + }, + async run(context) { + const { doc_id, name } = context.propsValue; + const data = await makeClient(context.auth).query<{ update_doc_name: unknown }>({ + query: `mutation ($docId: ID!, $name: String!) { + update_doc_name(docId: $docId, name: $name) + }`, + variables: { docId: doc_id, name }, + }); + + return { + doc_id, + name, + result: mondayApi.toJsonString(data.update_doc_name), + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/docs/update-doc-block.ts b/packages/pieces/community/monday/src/lib/actions/ai/docs/update-doc-block.ts new file mode 100644 index 000000000000..e5c9a8eb7bac --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/docs/update-doc-block.ts @@ -0,0 +1,72 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { updateDocBlockActionOutputSchema } from '../../../output-schemas'; + +export const updateDocBlockAction = createAction({ + auth: mondayAuth, + name: 'monday_update_doc_block', + classification: 'WRITE', + displayName: 'Update Doc Block', + description: 'Replaces the content of a block in a monday doc.', + audience: 'ai', + aiMetadata: { + description: + 'Replace the JSON content of one existing monday.com doc block by block ID (e.g. {"deltaFormat":[{"insert":"new text"}]}). The block type cannot change and table dimensions cannot be edited. Read the current content first with Get Doc Blocks when you only want to change part of it. Safe to retry: the same content converges on the same block.', + idempotent: true, + }, + outputSchema: updateDocBlockActionOutputSchema, + props: { + block_id: Property.ShortText({ + displayName: 'Block ID', + description: 'The block to update. Resolve it with Get Doc Blocks.', + required: true, + }), + content: Property.Json({ + displayName: 'Content', + description: 'The full new block content JSON.', + required: true, + }), + }, + async run(context) { + const { block_id, content } = context.propsValue; + + const data = await makeClient(context.auth).query<{ update_doc_block: DocBlock | null }>({ + query: `mutation ($blockId: String!, $content: JSON!) { + update_doc_block(block_id: $blockId, content: $content) { + id + type + position + parent_block_id + content + } + }`, + variables: { + blockId: block_id, + content: mondayApi.toJsonString(content), + }, + }); + + const block = data.update_doc_block; + if (!block) { + throw new Error(`Block ${block_id} was not found or could not be updated.`); + } + + return { + id: block.id, + type: block.type ?? null, + position: block.position ?? null, + parent_block_id: block.parent_block_id ?? null, + content: mondayApi.toJsonString(block.content ?? null), + }; + }, +}); + +type DocBlock = { + id: string; + type: string | null; + position: number | null; + parent_block_id: string | null; + content: unknown; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/files/get-assets.ts b/packages/pieces/community/monday/src/lib/actions/ai/files/get-assets.ts new file mode 100644 index 000000000000..654a1d4f3aee --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/files/get-assets.ts @@ -0,0 +1,77 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { getAssetsActionOutputSchema } from '../../../output-schemas'; + +export const getAssetsAction = createAction({ + auth: mondayAuth, + name: 'monday_get_assets', + classification: 'READ', + displayName: 'Get Assets', + description: 'Gets file (asset) metadata and download links by asset ID.', + audience: 'ai', + aiMetadata: { + description: + 'Fetch metadata for monday.com files (assets) by ID: name, size, extension, uploader, an account-restricted URL and a public URL valid for about one hour. Asset IDs appear in file column values (from Get Items) and in upload results. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getAssetsActionOutputSchema, + props: { + asset_ids: Property.Array({ + displayName: 'Asset IDs', + description: 'Numeric asset IDs.', + required: true, + }), + }, + async run(context) { + const ids = mondayApi.toStringArray(context.propsValue.asset_ids); + if (ids.length === 0) { + throw new Error('Provide at least one asset ID.'); + } + + const data = await makeClient(context.auth).query<{ assets: MondayAsset[] }>({ + query: `query ($ids: [ID!]!) { + assets(ids: $ids) { + id + name + url + public_url + url_thumbnail + file_extension + file_size + created_at + uploaded_by { id name } + } + }`, + variables: { ids }, + }); + + const assets = data.assets.map((asset) => ({ + id: asset.id, + name: asset.name, + url: asset.url, + public_url: asset.public_url, + thumbnail_url: asset.url_thumbnail ?? null, + file_extension: asset.file_extension, + file_size: asset.file_size, + created_at: asset.created_at ?? null, + uploaded_by_id: asset.uploaded_by?.id ?? null, + uploaded_by_name: asset.uploaded_by?.name ?? null, + })); + + return { assets, count: assets.length }; + }, +}); + +type MondayAsset = { + id: string; + name: string; + url: string; + public_url: string; + url_thumbnail: string | null; + file_extension: string; + file_size: number; + created_at: string | null; + uploaded_by: { id: string; name: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/files/upload-file.ts b/packages/pieces/community/monday/src/lib/actions/ai/files/upload-file.ts new file mode 100644 index 000000000000..e25334e1fc16 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/files/upload-file.ts @@ -0,0 +1,111 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { uploadFileActionOutputSchema } from '../../../output-schemas'; + +export const uploadFileAction = createAction({ + auth: mondayAuth, + name: 'monday_upload_file', + classification: 'WRITE', + displayName: 'Upload File', + description: 'Uploads a file to an item\'s file column or to an update.', + audience: 'ai', + aiMetadata: { + description: + 'Upload a file (URL or base64, max 500 MB) to monday.com, attaching it either to a file-type column on an item (Target "column" with Item ID and Column ID) or to an existing update (Target "update" with Update ID). Resolve file column IDs with List Columns. Each call uploads another copy, so retries duplicate.', + idempotent: false, + }, + outputSchema: uploadFileActionOutputSchema, + props: { + target: Property.StaticDropdown({ + displayName: 'Target', + description: 'Where to attach the file.', + required: true, + defaultValue: 'column', + options: { + disabled: false, + options: [ + { label: 'Item file column', value: 'column' }, + { label: 'Update', value: 'update' }, + ], + }, + }), + item_id: mondayAiProps.itemId(false), + column_id: Property.ShortText({ + displayName: 'File Column ID', + description: 'ID of a file-type column (required when Target is "column"). Resolve it with List Columns.', + required: false, + }), + update_id: mondayAiProps.updateId(false), + file: Property.File({ + displayName: 'File', + description: 'The file URL or base64 to upload.', + required: true, + }), + file_name: Property.ShortText({ + displayName: 'File Name', + description: 'Name to store the file under, including extension. Defaults to the source file name.', + required: false, + }), + }, + async run(context) { + const { target, item_id, column_id, update_id, file, file_name } = context.propsValue; + const fileName = file_name ?? file.filename; + const fileBuffer = Buffer.from(file.base64, 'base64'); + + if (target === 'update') { + if (isNil(update_id)) { + throw new Error('Update ID is required when Target is "update".'); + } + const data = await mondayApi.uploadFile<{ add_file_to_update: MondayAsset }>({ + apiKey: context.auth.secret_text, + query: `mutation ($updateId: ID!, $file: File!) { + add_file_to_update(update_id: $updateId, file: $file) { ${ASSET_FIELDS} } + }`, + variables: { updateId: update_id }, + file: fileBuffer, + fileName, + }); + return toAssetRow({ asset: data.add_file_to_update, target, parentId: update_id }); + } + + if (isNil(item_id) || isNil(column_id)) { + throw new Error('Item ID and File Column ID are required when Target is "column".'); + } + const data = await mondayApi.uploadFile<{ add_file_to_column: MondayAsset }>({ + apiKey: context.auth.secret_text, + query: `mutation ($itemId: ID!, $columnId: String!, $file: File!) { + add_file_to_column(item_id: $itemId, column_id: $columnId, file: $file) { ${ASSET_FIELDS} } + }`, + variables: { itemId: item_id, columnId: column_id }, + file: fileBuffer, + fileName, + }); + return toAssetRow({ asset: data.add_file_to_column, target: 'column', parentId: item_id }); + }, +}); + +function toAssetRow({ asset, target, parentId }: { asset: MondayAsset; target: string; parentId: string }) { + return { + id: asset.id, + name: asset.name, + url: asset.url, + file_extension: asset.file_extension, + file_size: asset.file_size, + created_at: asset.created_at ?? null, + target, + target_id: parentId, + }; +} + +const ASSET_FIELDS = 'id name url file_extension file_size created_at'; + +type MondayAsset = { + id: string; + name: string; + url: string; + file_extension: string; + file_size: number; + created_at: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/folders/create-folder.ts b/packages/pieces/community/monday/src/lib/actions/ai/folders/create-folder.ts new file mode 100644 index 000000000000..e501117e4c25 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/folders/create-folder.ts @@ -0,0 +1,78 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { createFolderActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const createFolderAction = createAction({ + auth: mondayAuth, + name: 'monday_create_folder', + classification: 'WRITE', + displayName: 'Create Folder', + description: 'Creates a folder in a workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Create a folder in a monday.com workspace, optionally nested under a parent folder, to organize boards and docs. Use before placing boards with Create Board or Move Board. Each call creates another folder even with the same name, so retries duplicate.', + idempotent: false, + }, + outputSchema: createFolderActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + parent_folder_id: Property.ShortText({ + displayName: 'Parent Folder ID', + description: 'Nest the new folder under this folder. Resolve it with List Folders.', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + description: 'Folder color enum value, e.g. DONE_GREEN, BRIGHT_BLUE, WORKING_ORANGE, STUCK_RED.', + required: false, + }), + }, + async run(context) { + const { workspace_id, name, parent_folder_id, color } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_folder: MondayFolder }>({ + query: `mutation ($name: String!, $workspace_id: ID, $parent_folder_id: ID, $color: FolderColor) { + create_folder(name: $name, workspace_id: $workspace_id, parent_folder_id: $parent_folder_id, color: $color) { + id + name + color + created_at + workspace { id } + parent { id } + } + }`, + variables: { + name, + workspace_id, + parent_folder_id: parent_folder_id || undefined, + color: color || undefined, + }, + }); + + const folder = data.create_folder; + return { + id: folder.id, + name: folder.name, + color: folder.color ?? null, + created_at: folder.created_at, + workspace_id: folder.workspace?.id ?? workspace_id, + parent_folder_id: folder.parent?.id ?? null, + }; + }, +}); + +type MondayFolder = { + id: string; + name: string; + color: string | null; + created_at: string; + workspace: { id: string } | null; + parent: { id: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/folders/delete-folder.ts b/packages/pieces/community/monday/src/lib/actions/ai/folders/delete-folder.ts new file mode 100644 index 000000000000..609d57fe390c --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/folders/delete-folder.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { deleteFolderActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const deleteFolderAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_folder', + classification: 'DESTRUCTIVE', + displayName: 'Delete Folder', + description: 'Deletes a folder and everything inside it.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a monday.com folder together with ALL boards, docs and sub-folders inside it. Destructive: only use when the user explicitly asks to delete the folder and its contents; to keep the boards, move them out first with Move Board, or archive boards individually with Archive Board. A retry after success fails because the folder is gone.', + idempotent: false, + }, + outputSchema: deleteFolderActionOutputSchema, + props: { + folder_id: mondayAiProps.folderId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ delete_folder: { id: string; name: string | null } }>({ + query: `mutation ($folder_id: ID!) { + delete_folder(folder_id: $folder_id) { id name } + }`, + variables: { folder_id: context.propsValue.folder_id }, + }); + + return { + id: data.delete_folder.id, + name: data.delete_folder.name ?? null, + deleted: true, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/folders/list-folders.ts b/packages/pieces/community/monday/src/lib/actions/ai/folders/list-folders.ts new file mode 100644 index 000000000000..c508766d1cbb --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/folders/list-folders.ts @@ -0,0 +1,99 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { listFoldersActionOutputSchema } from '../../../output-schemas'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const listFoldersAction = createAction({ + auth: mondayAuth, + name: 'monday_list_folders', + classification: 'SEARCH', + displayName: 'List Folders', + description: 'Lists folders in workspaces.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com folders with their workspace, parent folder, sub-folders and the boards inside them. Use to resolve a folder name to the folder ID needed by Create Board, Move Board and the folder actions. Filter by workspace IDs or folder IDs; max 100 per page. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listFoldersActionOutputSchema, + props: { + workspace_ids: Property.Array({ + displayName: 'Workspace IDs', + description: 'Only return folders in these workspaces. Resolve them with List Workspaces.', + required: false, + }), + folder_ids: Property.Array({ + displayName: 'Folder IDs', + description: 'Only return these folders.', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Folders per page (default 25, max 100).', + required: false, + defaultValue: 25, + }), + page: Property.Number({ + displayName: 'Page', + required: false, + defaultValue: 1, + }), + }, + async run(context) { + const { limit, page } = context.propsValue; + const workspaceIds = mondayApi.toStringArray(context.propsValue.workspace_ids); + const folderIds = mondayApi.toStringArray(context.propsValue.folder_ids); + + const data = await makeClient(context.auth).query<{ folders: MondayFolder[] }>({ + query: `query ($ids: [ID!], $workspace_ids: [ID], $limit: Int, $page: Int) { + folders(ids: $ids, workspace_ids: $workspace_ids, limit: $limit, page: $page) { + id + name + color + created_at + owner_id + workspace { id name } + parent { id name } + sub_folders { id name } + children { id name } + } + }`, + variables: { + ids: folderIds.length > 0 ? folderIds : undefined, + workspace_ids: workspaceIds.length > 0 ? workspaceIds : undefined, + limit: Math.min(limit ?? 25, 100), + page: page ?? 1, + }, + }); + + const folders = data.folders.map((folder) => ({ + id: folder.id, + name: folder.name, + color: folder.color ?? null, + created_at: folder.created_at, + owner_id: folder.owner_id ?? null, + workspace_id: folder.workspace?.id ?? null, + workspace_name: folder.workspace?.name ?? null, + parent_folder_id: folder.parent?.id ?? null, + parent_folder_name: folder.parent?.name ?? null, + sub_folder_ids: folder.sub_folders.map((sub) => sub.id).join(', '), + board_ids: folder.children.filter((board) => board !== null).map((board) => board.id).join(', '), + board_names: folder.children.filter((board) => board !== null).map((board) => board.name).join(', '), + })); + + return { folders, count: folders.length }; + }, +}); + +type MondayFolder = { + id: string; + name: string; + color: string | null; + created_at: string; + owner_id: string | null; + workspace: { id: string; name: string } | null; + parent: { id: string; name: string } | null; + sub_folders: { id: string; name: string }[]; + children: ({ id: string; name: string } | null)[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/folders/update-folder.ts b/packages/pieces/community/monday/src/lib/actions/ai/folders/update-folder.ts new file mode 100644 index 000000000000..95d2e24e2b4a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/folders/update-folder.ts @@ -0,0 +1,114 @@ +import { createAction, isNil, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { updateFolderActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { mondayClient } from '../../../common/client'; + +export const updateFolderAction = createAction({ + auth: mondayAuth, + name: 'monday_update_folder', + classification: 'WRITE', + displayName: 'Update Folder', + description: "Updates a folder's name, color, parent folder or workspace.", + audience: 'ai', + aiMetadata: { + description: + "Rename, recolor, or move a monday.com folder to another parent folder or workspace. Only the fields you provide are changed; omitted fields keep their current value. Setting the same values again leaves the folder unchanged, so it is safe to retry.", + idempotent: true, + }, + outputSchema: updateFolderActionOutputSchema, + props: { + folder_id: mondayAiProps.folderId(), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + color: Property.ShortText({ + displayName: 'Color', + description: 'Folder color enum value, e.g. DONE_GREEN, BRIGHT_BLUE, WORKING_ORANGE, STUCK_RED.', + required: false, + }), + parent_folder_id: Property.ShortText({ + displayName: 'New Parent Folder ID', + description: 'Move the folder under this folder. Resolve it with List Folders.', + required: false, + }), + workspace_id: Property.ShortText({ + displayName: 'New Workspace ID', + description: 'Move the folder to this workspace. Resolve it with List Workspaces.', + required: false, + }), + }, + async run(context) { + const { folder_id, name, color, parent_folder_id, workspace_id } = context.propsValue; + const changes = { + ...(name ? { name } : {}), + ...(color ? { color } : {}), + ...(parent_folder_id ? { parent_folder_id } : {}), + ...(workspace_id ? { workspace_id } : {}), + }; + if (Object.keys(changes).length === 0) { + throw new Error('Provide at least one field to update.'); + } + + const client = makeClient(context.auth); + const preservedColor = isNil(color) ? await readCurrentColor({ client, folderId: folder_id }) : null; + const changesWithPreserved = { + ...changes, + ...(isNil(preservedColor) ? {} : { color: preservedColor }), + }; + + const argumentTypes: Record = { + name: 'String', + color: 'FolderColor', + parent_folder_id: 'ID', + workspace_id: 'ID', + }; + const suppliedKeys = Object.keys(changesWithPreserved); + const variableDeclarations = suppliedKeys.map((key) => `, $${key}: ${argumentTypes[key]}`).join(''); + const argumentList = suppliedKeys.map((key) => `, ${key}: $${key}`).join(''); + + const data = await client.query<{ update_folder: MondayFolder }>({ + query: `mutation ($folder_id: ID!${variableDeclarations}) { + update_folder(folder_id: $folder_id${argumentList}) { + id + name + color + workspace { id } + parent { id } + } + }`, + variables: { folder_id, ...changesWithPreserved }, + }); + + const folder = data.update_folder; + return { + id: folder.id, + name: folder.name, + color: folder.color ?? null, + workspace_id: folder.workspace?.id ?? null, + parent_folder_id: folder.parent?.id ?? null, + }; + }, +}); + +async function readCurrentColor({ client, folderId }: { client: mondayClient; folderId: string }): Promise { + const data = await client.query<{ folders: { color: string | null }[] | null }>({ + query: `query ($ids: [ID!]) { + folders(ids: $ids) { + color + } + }`, + variables: { ids: [folderId] }, + }); + return data.folders?.[0]?.color ?? null; +} + +type MondayFolder = { + id: string; + name: string; + color: string | null; + workspace: { id: string } | null; + parent: { id: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/add-board-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/add-board-group.ts new file mode 100644 index 000000000000..e7e60c570e83 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/add-board-group.ts @@ -0,0 +1,81 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { addBoardGroupActionOutputSchema } from '../../../output-schemas'; + +export const addBoardGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_add_board_group', + classification: 'WRITE', + displayName: 'Add Board Group', + description: 'Creates a new group on a board.', + audience: 'ai', + aiMetadata: { + description: + 'Create a new group (section that holds items) on a monday.com board, optionally with a HEX color and positioned before or after an existing group. Check List Groups first to avoid duplicates; each call creates a new group, so retries duplicate.', + idempotent: false, + }, + outputSchema: addBoardGroupActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + group_name: Property.ShortText({ + displayName: 'Group Name', + description: 'Max 255 characters.', + required: true, + }), + group_color: Property.ShortText({ + displayName: 'Group Color', + description: 'HEX color, e.g. #ff642e.', + required: false, + }), + relative_to: Property.ShortText({ + displayName: 'Relative To Group ID', + description: 'Position the new group next to this group ID.', + required: false, + }), + position_relative_method: Property.StaticDropdown({ + displayName: 'Position', + description: 'Place before or after the Relative To group.', + required: false, + options: { + disabled: false, + options: [ + { label: 'Before', value: 'before_at' }, + { label: 'After', value: 'after_at' }, + ], + }, + }), + }, + async run(context) { + const { board_id, group_name, group_color, relative_to, position_relative_method } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_group: MondayGroup }>({ + query: `mutation ($boardId: ID!, $groupName: String!, $groupColor: String, $relativeTo: String, $positionRelativeMethod: PositionRelative) { + create_group( + board_id: $boardId + group_name: $groupName + group_color: $groupColor + relative_to: $relativeTo + position_relative_method: $positionRelativeMethod + ) { id title color } + }`, + variables: { + boardId: board_id, + groupName: group_name, + ...(isNil(group_color) ? {} : { groupColor: group_color }), + ...(isNil(relative_to) ? {} : { relativeTo: relative_to }), + ...(isNil(position_relative_method) ? {} : { positionRelativeMethod: position_relative_method }), + }, + }); + + const group = data.create_group; + return { id: group.id, board_id, title: group.title, color: group.color }; + }, +}); + +type MondayGroup = { + id: string; + title: string; + color: string; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/archive-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/archive-group.ts new file mode 100644 index 000000000000..a6a0cb0aa845 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/archive-group.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { archiveGroupActionOutputSchema } from '../../../output-schemas'; + +export const archiveGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_archive_group', + classification: 'DESTRUCTIVE', + displayName: 'Archive Group', + description: 'Archives a group and its items.', + audience: 'ai', + aiMetadata: { + description: + 'Archive a monday.com group, hiding it and its items from the board while keeping them restorable from the board archive in the monday.com UI. Prefer this over Delete Group when the items may be needed later. Archiving an already-archived group has no further effect.', + idempotent: true, + }, + outputSchema: archiveGroupActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + group_id: mondayAiProps.groupId(true), + }, + async run(context) { + const { board_id, group_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ archive_group: { id: string; title: string } }>({ + query: `mutation ($boardId: ID!, $groupId: String!) { + archive_group(board_id: $boardId, group_id: $groupId) { id title } + }`, + variables: { boardId: board_id, groupId: group_id }, + }); + + const group = data.archive_group; + return { id: group.id, board_id, title: group.title, archived: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/delete-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/delete-group.ts new file mode 100644 index 000000000000..d84d928ad25d --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/delete-group.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { deleteGroupActionOutputSchema } from '../../../output-schemas'; + +export const deleteGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_group', + classification: 'DESTRUCTIVE', + displayName: 'Delete Group', + description: 'Deletes a group and all of its items.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a group and every item in it from a monday.com board. Prefer Archive Group when the items may be needed later. A board\'s last remaining group cannot be deleted. A retry after success fails because the group no longer exists.', + idempotent: false, + }, + outputSchema: deleteGroupActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + group_id: mondayAiProps.groupId(true), + }, + async run(context) { + const { board_id, group_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ delete_group: { id: string } }>({ + query: `mutation ($boardId: ID!, $groupId: String!) { + delete_group(board_id: $boardId, group_id: $groupId) { id } + }`, + variables: { boardId: board_id, groupId: group_id }, + }); + + return { id: data.delete_group.id, board_id, deleted: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/duplicate-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/duplicate-group.ts new file mode 100644 index 000000000000..583090ada35a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/duplicate-group.ts @@ -0,0 +1,52 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { duplicateGroupActionOutputSchema } from '../../../output-schemas'; + +export const duplicateGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_duplicate_group', + classification: 'WRITE', + displayName: 'Duplicate Group', + description: 'Duplicates a group with its items.', + audience: 'ai', + aiMetadata: { + description: + 'Duplicate a monday.com group, including its items, on the same board, optionally with a new title and placed at the top. Limited by monday.com to 40 duplications per minute. Each call creates another copy, so retries duplicate.', + idempotent: false, + }, + outputSchema: duplicateGroupActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + group_id: mondayAiProps.groupId(true), + group_title: Property.ShortText({ + displayName: 'New Group Title', + description: 'Title for the copy. Defaults to the original title with a "Duplicate of" prefix.', + required: false, + }), + add_to_top: Property.Checkbox({ + displayName: 'Add To Top', + description: 'Place the copy at the top of the board.', + required: false, + }), + }, + async run(context) { + const { board_id, group_id, group_title, add_to_top } = context.propsValue; + + const data = await makeClient(context.auth).query<{ duplicate_group: { id: string; title: string; color: string } }>({ + query: `mutation ($boardId: ID!, $groupId: String!, $addToTop: Boolean, $groupTitle: String) { + duplicate_group(board_id: $boardId, group_id: $groupId, add_to_top: $addToTop, group_title: $groupTitle) { id title color } + }`, + variables: { + boardId: board_id, + groupId: group_id, + addToTop: add_to_top ?? false, + ...(isNil(group_title) ? {} : { groupTitle: group_title }), + }, + }); + + const group = data.duplicate_group; + return { id: group.id, board_id, source_group_id: group_id, title: group.title, color: group.color }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/list-groups.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/list-groups.ts new file mode 100644 index 000000000000..7b7f3529e28a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/list-groups.ts @@ -0,0 +1,59 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { listGroupsActionOutputSchema } from '../../../output-schemas'; + +export const listGroupsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_groups', + classification: 'SEARCH', + displayName: 'List Groups', + description: 'Lists the groups of a board.', + audience: 'ai', + aiMetadata: { + description: + 'List the groups (sections that hold items) of a monday.com board with each group\'s ID, title, color and archived state. Use to resolve a group ID before creating, moving or filtering items. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listGroupsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + }, + async run(context) { + const { board_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ boards: { id: string; groups: MondayGroup[] | null }[] }>({ + query: `query ($boardIds: [ID!]) { + boards(ids: $boardIds) { + id + groups { id title color archived deleted } + } + }`, + variables: { boardIds: [board_id] }, + }); + + const board = data.boards[0]; + if (!board) { + throw new Error(`Board ${board_id} was not found or is not accessible.`); + } + + const groups = (board.groups ?? []).map((group) => ({ + id: group.id, + title: group.title, + color: group.color, + archived: group.archived ?? false, + deleted: group.deleted ?? false, + })); + + return { board_id: board.id, groups, count: groups.length }; + }, +}); + +type MondayGroup = { + id: string; + title: string; + color: string; + archived: boolean | null; + deleted: boolean | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/groups/update-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/groups/update-group.ts new file mode 100644 index 000000000000..9c33d586b897 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/groups/update-group.ts @@ -0,0 +1,100 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { mondayClient } from '../../../common/client'; +import { mondayAiProps } from '../../../common/ai-props'; +import { updateGroupActionOutputSchema } from '../../../output-schemas'; + +export const updateGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_update_group', + classification: 'WRITE', + displayName: 'Update Group', + description: 'Renames, recolors or repositions a group.', + audience: 'ai', + aiMetadata: { + description: + 'Change a monday.com group\'s title and/or color, or move it before or after another group. Only the fields you provide change. Resolve group IDs with List Groups. Re-applying the same values is safe.', + idempotent: true, + }, + outputSchema: updateGroupActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + group_id: mondayAiProps.groupId(true), + title: Property.ShortText({ + displayName: 'New Title', + required: false, + }), + color: Property.ShortText({ + displayName: 'New Color', + description: 'monday.com group color name or HEX value.', + required: false, + }), + move_after_group_id: Property.ShortText({ + displayName: 'Move After Group ID', + description: 'Move the group directly after this group ID.', + required: false, + }), + move_before_group_id: Property.ShortText({ + displayName: 'Move Before Group ID', + description: 'Move the group directly before this group ID.', + required: false, + }), + }, + async run(context) { + const { board_id, group_id, title, color, move_after_group_id, move_before_group_id } = context.propsValue; + const changes = [ + { attribute: 'title', value: title }, + { attribute: 'color', value: color }, + { attribute: 'relative_position_after', value: move_after_group_id }, + { attribute: 'relative_position_before', value: move_before_group_id }, + ].filter((change): change is { attribute: string; value: string } => !isNil(change.value)); + + if (changes.length === 0) { + throw new Error('Provide at least one of New Title, New Color, Move After Group ID or Move Before Group ID.'); + } + if (!isNil(move_after_group_id) && !isNil(move_before_group_id)) { + throw new Error('Provide either Move After Group ID or Move Before Group ID, not both.'); + } + + const results = await applyChanges({ client: makeClient(context.auth), boardId: board_id, groupId: group_id, changes }); + const group = results[results.length - 1]; + + return { + id: group?.id ?? group_id, + board_id, + title: group?.title ?? null, + color: group?.color ?? null, + updated_attributes: changes.map((change) => change.attribute).join(', '), + }; + }, +}); + +async function applyChanges({ + client, + boardId, + groupId, + changes, +}: { + client: mondayClient; + boardId: string | undefined; + groupId: string | undefined; + changes: { attribute: string; value: string }[]; +}): Promise { + return changes.reduce>(async (previous, change) => { + const done = await previous; + const data = await client.query<{ update_group: MondayGroup }>({ + query: `mutation ($boardId: ID!, $groupId: String!, $attribute: GroupAttributes!, $value: String!) { + update_group(board_id: $boardId, group_id: $groupId, group_attribute: $attribute, new_value: $value) { id title color } + }`, + variables: { boardId, groupId, attribute: change.attribute, value: change.value }, + }); + return [...done, data.update_group]; + }, Promise.resolve([])); +} + +type MondayGroup = { + id: string; + title: string; + color: string; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/archive-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/archive-item.ts new file mode 100644 index 000000000000..2ed6f503e855 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/archive-item.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { archiveItemActionOutputSchema } from '../../../output-schemas'; + +export const archiveItemAction = createAction({ + auth: mondayAuth, + name: 'monday_archive_item', + classification: 'DESTRUCTIVE', + displayName: 'Archive Item', + description: 'Archives an item.', + audience: 'ai', + aiMetadata: { + description: + 'Archive a monday.com item, removing it from board views while keeping it restorable from the archive. Prefer this over Delete Item unless permanent removal is explicitly wanted. Safe to retry: archiving an archived item is a no-op.', + idempotent: true, + }, + outputSchema: archiveItemActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ archive_item: MondayItemSummary }>({ + query: `mutation ($itemId: ID!) { + archive_item(item_id: $itemId) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { itemId: context.propsValue.item_id }, + }); + + return itemCommon.mapItemSummary(data.archive_item); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/change-column-value.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/change-column-value.ts new file mode 100644 index 000000000000..65f9c1f0b341 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/change-column-value.ts @@ -0,0 +1,58 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { changeColumnValueActionOutputSchema } from '../../../output-schemas'; + +export const changeColumnValueAction = createAction({ + auth: mondayAuth, + name: 'monday_change_column_value', + classification: 'WRITE', + displayName: 'Change Column Value', + description: 'Sets one column of an item using a JSON value.', + audience: 'ai', + aiMetadata: { + description: + 'Set a single column on a monday.com item using the column type\'s JSON value format, e.g. timeline {"from": "2026-01-01", "to": "2026-01-31"}, people {"personsAndTeams": [{"id": 123, "kind": "person"}]}, or {"clear_all": true} to clear a file column. Get the exact format with Get Column Type Schema. Use Change Simple Column Value for plain labels/text. Safe to retry.', + idempotent: true, + }, + outputSchema: changeColumnValueActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + column_id: mondayAiProps.columnId(), + value: Property.Json({ + displayName: 'Value', + description: 'The column value as a JSON object, in the format for that column type.', + required: true, + }), + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels If Missing', + description: 'Create missing status/dropdown labels. Requires permission to change the board structure.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, item_id, column_id, value, create_labels_if_missing } = context.propsValue; + + const data = await makeClient(context.auth).query<{ change_column_value: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $columnId: String!, $value: JSON!, $createLabels: Boolean) { + change_column_value(board_id: $boardId, item_id: $itemId, column_id: $columnId, value: $value, create_labels_if_missing: $createLabels) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemId: item_id, + columnId: column_id, + value: mondayApi.toJsonString(value), + createLabels: create_labels_if_missing ?? false, + }, + }); + + return itemCommon.mapItemSummary(data.change_column_value); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/change-item-position.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/change-item-position.ts new file mode 100644 index 000000000000..c369544170f5 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/change-item-position.ts @@ -0,0 +1,82 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { changeItemPositionActionOutputSchema } from '../../../output-schemas'; + +export const changeItemPositionAction = createAction({ + auth: mondayAuth, + name: 'monday_change_item_position', + classification: 'WRITE', + displayName: 'Change Item Position', + description: 'Reorders an item relative to another item or to the top/bottom of a group.', + audience: 'ai', + aiMetadata: { + description: + 'Reorder a monday.com item: place it before or after another item on the same board, or at the top or bottom of a group. Provide either Relative To Item ID with Position, or Group ID with Group Placement. Subitems cannot be repositioned. Use Move Item to Group for a plain group change. Safe to retry.', + idempotent: true, + }, + outputSchema: changeItemPositionActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + relative_to: Property.ShortText({ + displayName: 'Relative To Item ID', + description: 'An item on the same board to position this item next to.', + required: false, + }), + position_relative_method: Property.StaticDropdown({ + displayName: 'Position', + description: 'Where to place the item relative to Relative To Item ID.', + required: false, + options: { + options: [ + { label: 'After (below)', value: 'after_at' }, + { label: 'Before (above)', value: 'before_at' }, + ], + }, + }), + group_id: mondayAiProps.groupId(false), + group_top: Property.StaticDropdown({ + displayName: 'Group Placement', + description: 'Place the item at the top or bottom of Group ID.', + required: false, + options: { + options: [ + { label: 'Top of group', value: 'top' }, + { label: 'Bottom of group', value: 'bottom' }, + ], + }, + }), + }, + async run(context) { + const { item_id, relative_to, position_relative_method, group_id, group_top } = context.propsValue; + const hasRelative = !isNil(relative_to) && relative_to !== ''; + const hasGroup = !isNil(group_id) && group_id !== ''; + + if (hasRelative === hasGroup) { + throw new Error('Provide either Relative To Item ID with Position, or Group ID with Group Placement.'); + } + if (hasRelative && isNil(position_relative_method)) { + throw new Error('Position is required when Relative To Item ID is set.'); + } + if (hasGroup && isNil(group_top)) { + throw new Error('Group Placement is required when Group ID is set.'); + } + + const data = await makeClient(context.auth).query<{ change_item_position: MondayItemSummary }>({ + query: `mutation ($itemId: ID!, $relativeTo: ID, $positionRelativeMethod: PositionRelative, $groupId: ID, $groupTop: Boolean) { + change_item_position(item_id: $itemId, relative_to: $relativeTo, position_relative_method: $positionRelativeMethod, group_id: $groupId, group_top: $groupTop) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + itemId: item_id, + ...(hasRelative ? { relativeTo: relative_to, positionRelativeMethod: position_relative_method } : {}), + ...(hasGroup ? { groupId: group_id, groupTop: group_top === 'top' } : {}), + }, + }); + + return itemCommon.mapItemSummary(data.change_item_position); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/change-simple-column-value.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/change-simple-column-value.ts new file mode 100644 index 000000000000..18e32ac6a456 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/change-simple-column-value.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { changeSimpleColumnValueActionOutputSchema } from '../../../output-schemas'; + +export const changeSimpleColumnValueAction = createAction({ + auth: mondayAuth, + name: 'monday_change_simple_column_value', + classification: 'WRITE', + displayName: 'Change Simple Column Value', + description: 'Sets one column of an item using a plain string.', + audience: 'ai', + aiMetadata: { + description: + 'Set a single column on a monday.com item from a plain string, e.g. a status or dropdown label ("Done"), text, a number, or a date ("2026-01-31"). Use Change Column Value for structured JSON values (people, timeline, link) and Set Item Column Values to change several columns at once. An empty string clears the column. Safe to retry.', + idempotent: true, + }, + outputSchema: changeSimpleColumnValueActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + column_id: mondayAiProps.columnId(), + value: Property.ShortText({ + displayName: 'Value', + description: 'The new value as a string. Leave empty to clear the column.', + required: false, + }), + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels If Missing', + description: 'Create a missing status/dropdown label. Requires permission to change the board structure.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, item_id, column_id, value, create_labels_if_missing } = context.propsValue; + + const data = await makeClient(context.auth).query<{ change_simple_column_value: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $columnId: String!, $value: String, $createLabels: Boolean) { + change_simple_column_value(board_id: $boardId, item_id: $itemId, column_id: $columnId, value: $value, create_labels_if_missing: $createLabels) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemId: item_id, + columnId: column_id, + value: value ?? '', + createLabels: create_labels_if_missing ?? false, + }, + }); + + return itemCommon.mapItemSummary(data.change_simple_column_value); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/clear-item-updates.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/clear-item-updates.ts new file mode 100644 index 000000000000..11a98cf2e12b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/clear-item-updates.ts @@ -0,0 +1,39 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { clearItemUpdatesActionOutputSchema } from '../../../output-schemas'; + +export const clearItemUpdatesAction = createAction({ + auth: mondayAuth, + name: 'monday_clear_item_updates', + classification: 'DESTRUCTIVE', + displayName: 'Clear Item Updates', + description: 'Deletes all updates on an item.', + audience: 'ai', + aiMetadata: { + description: + 'Permanently delete every update (comment thread) on a monday.com item. Use Delete Update to remove a single update instead. Irreversible; re-running on an item with no updates is harmless.', + idempotent: true, + }, + outputSchema: clearItemUpdatesActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ clear_item_updates: MondayItemSummary }>({ + query: `mutation ($itemId: ID!) { + clear_item_updates(item_id: $itemId) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { itemId: context.propsValue.item_id }, + }); + + return { + success: true, + ...itemCommon.mapItemSummary(data.clear_item_updates), + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/create-board-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/create-board-item.ts new file mode 100644 index 000000000000..7cb488f1e4ac --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/create-board-item.ts @@ -0,0 +1,62 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { createBoardItemActionOutputSchema } from '../../../output-schemas'; + +export const createBoardItemAction = createAction({ + auth: mondayAuth, + name: 'monday_create_board_item', + classification: 'WRITE', + displayName: 'Create Board Item', + description: 'Creates an item on a board with optional column values.', + audience: 'ai', + aiMetadata: { + description: + 'Create a new item (row) on a monday.com board by board ID, optionally in a specific group and with column values given as a JSON map of column ID to value. Use Create Subitem for a child item instead. Check column IDs and value formats with List Columns and Get Column Type Schema first. Each call creates a new item, so retries duplicate.', + idempotent: false, + }, + outputSchema: createBoardItemActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_name: Property.ShortText({ + displayName: 'Item Name', + required: true, + }), + group_id: mondayAiProps.groupId(false), + column_values: Property.Json({ + displayName: 'Column Values', + description: + 'JSON map of column ID to value, e.g. {"status": {"label": "Done"}, "date4": {"date": "2026-01-31"}, "text": "Hello"}.', + required: false, + }), + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels If Missing', + description: 'Create missing status/dropdown labels. Requires permission to change the board structure.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, item_name, group_id, column_values, create_labels_if_missing } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_item: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemName: String!, $groupId: String, $columnValues: JSON, $createLabels: Boolean) { + create_item(board_id: $boardId, item_name: $itemName, group_id: $groupId, column_values: $columnValues, create_labels_if_missing: $createLabels) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemName: item_name, + ...(isNil(group_id) || group_id === '' ? {} : { groupId: group_id }), + ...(isNil(column_values) ? {} : { columnValues: mondayApi.toJsonString(column_values) }), + createLabels: create_labels_if_missing ?? false, + }, + }); + + return itemCommon.mapItemSummary(data.create_item); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/create-subitem.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/create-subitem.ts new file mode 100644 index 000000000000..c01f84ab9637 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/create-subitem.ts @@ -0,0 +1,65 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { createSubitemActionOutputSchema } from '../../../output-schemas'; + +export const createSubitemAction = createAction({ + auth: mondayAuth, + name: 'monday_create_subitem', + classification: 'WRITE', + displayName: 'Create Subitem', + description: 'Creates a subitem under an item.', + audience: 'ai', + aiMetadata: { + description: + 'Create a subitem under a parent monday.com item, optionally with column values as a JSON map of subitem-board column ID to value. Subitems live on a separate subitems board, so their column IDs differ from the parent board; read them with List Subitems or List Columns on that board. Each call creates a new subitem, so retries duplicate.', + idempotent: false, + }, + outputSchema: createSubitemActionOutputSchema, + props: { + parent_item_id: Property.ShortText({ + displayName: 'Parent Item ID', + description: 'The item to add the subitem under. Resolve it with List Board Items.', + required: true, + }), + item_name: Property.ShortText({ + displayName: 'Subitem Name', + required: true, + }), + column_values: Property.Json({ + displayName: 'Column Values', + description: 'JSON map of subitem column ID to value, e.g. {"status": {"label": "Working on it"}}.', + required: false, + }), + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels If Missing', + description: 'Create missing status/dropdown labels. Requires permission to change the board structure.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { parent_item_id, item_name, column_values, create_labels_if_missing } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_subitem: MondayItemSummary }>({ + query: `mutation ($parentItemId: ID!, $itemName: String!, $columnValues: JSON, $createLabels: Boolean) { + create_subitem(parent_item_id: $parentItemId, item_name: $itemName, column_values: $columnValues, create_labels_if_missing: $createLabels) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + parentItemId: parent_item_id, + itemName: item_name, + ...(isNil(column_values) ? {} : { columnValues: mondayApi.toJsonString(column_values) }), + createLabels: create_labels_if_missing ?? false, + }, + }); + + return { + ...itemCommon.mapItemSummary(data.create_subitem), + parent_item_id, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/delete-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/delete-item.ts new file mode 100644 index 000000000000..b49a3c6d8777 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/delete-item.ts @@ -0,0 +1,36 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { deleteItemActionOutputSchema } from '../../../output-schemas'; + +export const deleteItemAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_item', + classification: 'DESTRUCTIVE', + displayName: 'Delete Item', + description: 'Deletes an item.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a monday.com item or subitem; it moves to the account trash and cannot be restored through the API. Prefer Archive Item when the item may be needed later. A retry on an already-deleted item fails.', + idempotent: false, + }, + outputSchema: deleteItemActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ delete_item: { id: string } | null }>({ + query: `mutation ($itemId: ID!) { + delete_item(item_id: $itemId) { id } + }`, + variables: { itemId: context.propsValue.item_id }, + }); + + return { + success: true, + id: data.delete_item?.id ?? context.propsValue.item_id, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/duplicate-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/duplicate-item.ts new file mode 100644 index 000000000000..88e9eaf8e31d --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/duplicate-item.ts @@ -0,0 +1,49 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { duplicateItemActionOutputSchema } from '../../../output-schemas'; + +export const duplicateItemAction = createAction({ + auth: mondayAuth, + name: 'monday_duplicate_item', + classification: 'WRITE', + displayName: 'Duplicate Item', + description: 'Creates a copy of an item on the same board.', + audience: 'ai', + aiMetadata: { + description: + 'Duplicate a monday.com item on its board, copying its column values and optionally its updates; returns the new item. Use Create Board Item to start from scratch instead. Each call creates another copy, so retries duplicate.', + idempotent: false, + }, + outputSchema: duplicateItemActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + with_updates: Property.Checkbox({ + displayName: 'Include Updates', + description: 'Also copy the item\'s updates.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, item_id, with_updates } = context.propsValue; + + const data = await makeClient(context.auth).query<{ duplicate_item: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $withUpdates: Boolean) { + duplicate_item(board_id: $boardId, item_id: $itemId, with_updates: $withUpdates) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemId: item_id, + withUpdates: with_updates ?? false, + }, + }); + + return itemCommon.mapItemSummary(data.duplicate_item); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/get-items.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/get-items.ts new file mode 100644 index 000000000000..93ef881dfcb5 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/get-items.ts @@ -0,0 +1,89 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { getItemsActionOutputSchema } from '../../../output-schemas'; + +export const getItemsAction = createAction({ + auth: mondayAuth, + name: 'monday_get_items', + classification: 'READ', + displayName: 'Get Items', + description: 'Gets one or more items (or subitems) by ID with their column values.', + audience: 'ai', + aiMetadata: { + description: + 'Fetch up to 100 monday.com items or subitems by ID, including every column value as text plus raw JSON. Use when you already hold item IDs; to enumerate a board use List Board Items, to find items by a column value use Search Items by Column Values. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getItemsActionOutputSchema, + props: { + item_ids: Property.Array({ + displayName: 'Item IDs', + description: 'Numeric item or subitem IDs (max 100).', + required: true, + }), + }, + async run(context) { + const ids = mondayApi.toStringArray(context.propsValue.item_ids); + if (ids.length === 0) { + throw new Error('Provide at least one item ID.'); + } + if (ids.length > 100) { + throw new Error('monday.com accepts at most 100 item IDs per request.'); + } + + const data = await makeClient(context.auth).query<{ items: MondayItem[] }>({ + query: `query ($ids: [ID!]) { + items(ids: $ids) { + id + name + state + created_at + updated_at + url + board { id name } + group { id title } + parent_item { id } + column_values { id type text value } + } + }`, + variables: { ids }, + }); + + const items = data.items.map((item) => ({ + id: item.id, + name: item.name, + state: item.state, + url: item.url, + board_id: item.board?.id ?? null, + board_name: item.board?.name ?? null, + group_id: item.group?.id ?? null, + group_title: item.group?.title ?? null, + parent_item_id: item.parent_item?.id ?? null, + created_at: item.created_at, + updated_at: item.updated_at, + column_values: item.column_values.map((cv) => ({ + column_id: cv.id, + type: cv.type, + text: cv.text ?? null, + value: cv.value ?? null, + })), + })); + + return { items, count: items.length }; + }, +}); + +type MondayItem = { + id: string; + name: string; + state: string; + created_at: string; + updated_at: string; + url: string; + board: { id: string; name: string } | null; + group: { id: string; title: string } | null; + parent_item: { id: string } | null; + column_values: { id: string; type: string; text: string | null; value: string | null }[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/item-common.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/item-common.ts new file mode 100644 index 000000000000..d035865f3838 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/item-common.ts @@ -0,0 +1,80 @@ +import { isNil } from '@activepieces/pieces-framework'; + +function mapItemSummary(item: MondayItemSummary) { + return { + id: item.id, + name: item.name, + state: item.state ?? null, + url: item.url ?? null, + board_id: item.board?.id ?? null, + group_id: item.group?.id ?? null, + }; +} + +function mapItemWithValues(item: MondayItemWithValues) { + return { + ...mapItemSummary(item), + group_title: item.group?.title ?? null, + created_at: item.created_at ?? null, + updated_at: item.updated_at ?? null, + column_values: item.column_values.map((cv) => ({ + column_id: cv.id, + type: cv.type, + text: cv.text ?? null, + value: cv.value ?? null, + })), + }; +} + +function columnIdsVariable(columnIds: unknown): string[] | null { + if (!Array.isArray(columnIds)) { + return null; + } + const ids = columnIds.filter((id) => !isNil(id) && String(id).trim() !== '').map((id) => String(id).trim()); + return ids.length > 0 ? ids : null; +} + +function clampLimit(limit: number | undefined): number { + if (isNil(limit) || Number.isNaN(limit)) { + return 50; + } + return Math.min(Math.max(Math.trunc(limit), 1), 500); +} + +function readString({ row, key }: { row: unknown; key: string }): string | null { + if (typeof row !== 'object' || row === null || !(key in row)) { + return null; + } + const value: unknown = Reflect.get(row, key); + if (isNil(value) || String(value).trim() === '') { + return null; + } + return String(value).trim(); +} + +export const itemCommon = { + mapItemSummary, + mapItemWithValues, + columnIdsVariable, + clampLimit, + readString, +}; + +export const ITEM_SUMMARY_FIELDS = 'id name state url board { id } group { id }'; + +export const ITEM_WITH_VALUES_FIELDS = `id name state url created_at updated_at board { id } group { id title } column_values(ids: $columnIds) { id type text value }`; + +export type MondayItemSummary = { + id: string; + name: string; + state?: string | null; + url?: string | null; + board?: { id: string } | null; + group?: { id: string; title?: string } | null; +}; + +export type MondayItemWithValues = MondayItemSummary & { + created_at?: string | null; + updated_at?: string | null; + column_values: { id: string; type: string; text: string | null; value: string | null }[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/list-board-items.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/list-board-items.ts new file mode 100644 index 000000000000..d8d04caa30bf --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/list-board-items.ts @@ -0,0 +1,116 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_WITH_VALUES_FIELDS, itemCommon, MondayItemWithValues } from './item-common'; +import { listBoardItemsActionOutputSchema } from '../../../output-schemas'; + +export const listBoardItemsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_board_items', + classification: 'SEARCH', + displayName: 'List Board Items', + description: 'Lists items on a board (or one group) with cursor pagination.', + audience: 'ai', + aiMetadata: { + description: + 'List items on a monday.com board, or only one group, with their column values, one page (up to 500) at a time; pass the returned cursor to get the next page (cursors expire 60 minutes after the first request). Supports server-side filtering and sorting via Query Params. For an exact match on a column value prefer Search Items by Column Values; for known IDs use Get Items. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listBoardItemsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + group_id: mondayAiProps.groupId(false), + limit: Property.Number({ + displayName: 'Limit', + description: 'Items per page, 1-500. Defaults to 50.', + required: false, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'Cursor from a previous page. When set, Group ID and Query Params are ignored.', + required: false, + }), + query_params: Property.Json({ + displayName: 'Query Params', + description: + 'Optional ItemsQuery filter, e.g. {"rules": [{"column_id": "status", "compare_value": [1], "operator": "any_of"}], "operator": "and", "order_by": [{"column_id": "date4", "direction": "desc"}]}.', + required: false, + }), + column_ids: Property.Array({ + displayName: 'Column IDs', + description: 'Only return these column values. Leave empty for all columns.', + required: false, + }), + }, + async run(context) { + const { board_id, group_id, cursor, query_params } = context.propsValue; + const client = makeClient(context.auth); + const limit = itemCommon.clampLimit(context.propsValue.limit); + const columnIds = itemCommon.columnIdsVariable(context.propsValue.column_ids); + const columnIdsVariable = columnIds === null ? {} : { columnIds }; + + if (!isNil(cursor) && cursor !== '') { + const data = await client.query<{ next_items_page: MondayItemsPage }>({ + query: `query ($cursor: String!, $limit: Int!, $columnIds: [String!]) { + next_items_page(cursor: $cursor, limit: $limit) { + cursor + items { ${ITEM_WITH_VALUES_FIELDS} } + } + }`, + variables: { cursor, limit, ...columnIdsVariable }, + }); + return toOutput({ page: data.next_items_page }); + } + + const queryParamsVariable = isNil(query_params) ? {} : { queryParams: query_params }; + + if (!isNil(group_id) && group_id !== '') { + const data = await client.query<{ boards: { groups: { items_page: MondayItemsPage }[] }[] }>({ + query: `query ($boardId: [ID!], $groupId: [String], $limit: Int!, $queryParams: ItemsQuery, $columnIds: [String!]) { + boards(ids: $boardId) { + groups(ids: $groupId) { + items_page(limit: $limit, query_params: $queryParams) { + cursor + items { ${ITEM_WITH_VALUES_FIELDS} } + } + } + } + }`, + variables: { boardId: [board_id], groupId: [group_id], limit, ...queryParamsVariable, ...columnIdsVariable }, + }); + const page = data.boards[0]?.groups[0]?.items_page; + if (isNil(page)) { + throw new Error(`Group ${group_id} was not found on board ${board_id}.`); + } + return toOutput({ page }); + } + + const data = await client.query<{ boards: { items_page: MondayItemsPage }[] }>({ + query: `query ($boardId: [ID!], $limit: Int!, $queryParams: ItemsQuery, $columnIds: [String!]) { + boards(ids: $boardId) { + items_page(limit: $limit, query_params: $queryParams) { + cursor + items { ${ITEM_WITH_VALUES_FIELDS} } + } + } + }`, + variables: { boardId: [board_id], limit, ...queryParamsVariable, ...columnIdsVariable }, + }); + const page = data.boards[0]?.items_page; + if (isNil(page)) { + throw new Error(`Board ${board_id} was not found or is not accessible.`); + } + return toOutput({ page }); + }, +}); + +function toOutput({ page }: { page: MondayItemsPage }) { + const items = page.items.map((item) => itemCommon.mapItemWithValues(item)); + return { items, count: items.length, cursor: page.cursor ?? null }; +} + +type MondayItemsPage = { + cursor: string | null; + items: MondayItemWithValues[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/list-subitems.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/list-subitems.ts new file mode 100644 index 000000000000..3a18f9610e2b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/list-subitems.ts @@ -0,0 +1,56 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_WITH_VALUES_FIELDS, itemCommon, MondayItemWithValues } from './item-common'; +import { makeClient } from '../../../common'; +import { listSubitemsActionOutputSchema } from '../../../output-schemas'; + +export const listSubitemsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_subitems', + classification: 'SEARCH', + displayName: 'List Subitems', + description: 'Lists the subitems of an item with their column values.', + audience: 'ai', + aiMetadata: { + description: + 'List all subitems of a monday.com item with their column values and the subitems board ID (needed to update subitem columns). Use Get Items when you already hold subitem IDs. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listSubitemsActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + column_ids: Property.Array({ + displayName: 'Column IDs', + description: 'Only return these subitem column values. Leave empty for all columns.', + required: false, + }), + }, + async run(context) { + const columnIds = itemCommon.columnIdsVariable(context.propsValue.column_ids); + + const data = await makeClient(context.auth).query<{ items: { id: string; subitems: MondayItemWithValues[] | null }[] }>({ + query: `query ($ids: [ID!], $columnIds: [String!]) { + items(ids: $ids) { + id + subitems { ${ITEM_WITH_VALUES_FIELDS} } + } + }`, + variables: { + ids: [context.propsValue.item_id], + ...(columnIds === null ? {} : { columnIds }), + }, + }); + + const parent = data.items[0]; + if (!parent) { + throw new Error(`Item ${context.propsValue.item_id} was not found or is not accessible.`); + } + + const subitems = (parent.subitems ?? []).map((subitem) => ({ + ...itemCommon.mapItemWithValues(subitem), + parent_item_id: parent.id, + })); + return { subitems, count: subitems.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-board.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-board.ts new file mode 100644 index 000000000000..5cedd137ab99 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-board.ts @@ -0,0 +1,81 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { moveItemToBoardActionOutputSchema } from '../../../output-schemas'; + +export const moveItemToBoardAction = createAction({ + auth: mondayAuth, + name: 'monday_move_item_to_board', + classification: 'WRITE', + displayName: 'Move Item to Board', + description: 'Moves an item to a group on a different board.', + audience: 'ai', + aiMetadata: { + description: + 'Move a monday.com item (with its subitems) to a group on another board, optionally mapping source column IDs to target column IDs so values carry over; unmapped columns may lose their values. Use Move Item to Group for a move within the same board. Safe to retry: moving to where it already is is a no-op.', + idempotent: true, + }, + outputSchema: moveItemToBoardActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + board_id: Property.ShortText({ + displayName: 'Target Board ID', + description: 'The board to move the item to. Resolve it with List Boards.', + required: true, + }), + group_id: Property.ShortText({ + displayName: 'Target Group ID', + description: 'The group on the target board. Resolve it with List Groups.', + required: true, + }), + columns_mapping: Property.Array({ + displayName: 'Columns Mapping', + description: 'Optional mapping of source column IDs to target column IDs.', + required: false, + properties: { + source: Property.ShortText({ + displayName: 'Source Column ID', + required: true, + }), + target: Property.ShortText({ + displayName: 'Target Column ID', + required: true, + }), + }, + }), + }, + async run(context) { + const { item_id, board_id, group_id, columns_mapping } = context.propsValue; + const mapping = toColumnsMapping({ rows: columns_mapping }); + + const data = await makeClient(context.auth).query<{ move_item_to_board: MondayItemSummary }>({ + query: `mutation ($itemId: ID!, $boardId: ID!, $groupId: ID!, $columnsMapping: [ColumnMappingInput!]) { + move_item_to_board(item_id: $itemId, board_id: $boardId, group_id: $groupId, columns_mapping: $columnsMapping) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + itemId: item_id, + boardId: board_id, + groupId: group_id, + ...(mapping.length > 0 ? { columnsMapping: mapping } : {}), + }, + }); + + return itemCommon.mapItemSummary(data.move_item_to_board); + }, +}); + +function toColumnsMapping({ rows }: { rows: unknown }): { source: string; target: string }[] { + if (!Array.isArray(rows)) { + return []; + } + return rows + .map((row: unknown) => ({ + source: itemCommon.readString({ row, key: 'source' }), + target: itemCommon.readString({ row, key: 'target' }), + })) + .filter((pair): pair is { source: string; target: string } => !isNil(pair.source) && !isNil(pair.target)); +} diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-group.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-group.ts new file mode 100644 index 000000000000..3917155f5583 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/move-item-to-group.ts @@ -0,0 +1,39 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { moveItemToGroupActionOutputSchema } from '../../../output-schemas'; + +export const moveItemToGroupAction = createAction({ + auth: mondayAuth, + name: 'monday_move_item_to_group', + classification: 'WRITE', + displayName: 'Move Item to Group', + description: 'Moves an item to another group on the same board.', + audience: 'ai', + aiMetadata: { + description: + 'Move a monday.com item (with its subitems) to a different group on the same board. Use Move Item to Board to move it across boards, or Change Item Position to reorder it within a group. Safe to retry: moving to the group it is already in is a no-op.', + idempotent: true, + }, + outputSchema: moveItemToGroupActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(), + group_id: mondayAiProps.groupId(), + }, + async run(context) { + const { item_id, group_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ move_item_to_group: MondayItemSummary }>({ + query: `mutation ($itemId: ID!, $groupId: String!) { + move_item_to_group(item_id: $itemId, group_id: $groupId) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { itemId: item_id, groupId: group_id }, + }); + + return itemCommon.mapItemSummary(data.move_item_to_group); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/rename-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/rename-item.ts new file mode 100644 index 000000000000..f49099ed0cd3 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/rename-item.ts @@ -0,0 +1,47 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { renameItemActionOutputSchema } from '../../../output-schemas'; + +export const renameItemAction = createAction({ + auth: mondayAuth, + name: 'monday_rename_item', + classification: 'WRITE', + displayName: 'Rename Item', + description: 'Changes the name of an item.', + audience: 'ai', + aiMetadata: { + description: + 'Rename an existing monday.com item or subitem (its name column) without touching other columns. Use Set Item Column Values to change other fields. Safe to retry: setting the same name leaves the item unchanged.', + idempotent: true, + }, + outputSchema: renameItemActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + name: Property.ShortText({ + displayName: 'New Name', + required: true, + }), + }, + async run(context) { + const { board_id, item_id, name } = context.propsValue; + + const data = await makeClient(context.auth).query<{ change_multiple_column_values: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $columnValues: JSON!) { + change_multiple_column_values(board_id: $boardId, item_id: $itemId, column_values: $columnValues) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemId: item_id, + columnValues: JSON.stringify({ name }), + }, + }); + + return itemCommon.mapItemSummary(data.change_multiple_column_values); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/search-items-by-column-values.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/search-items-by-column-values.ts new file mode 100644 index 000000000000..0c17b071b437 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/search-items-by-column-values.ts @@ -0,0 +1,122 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_WITH_VALUES_FIELDS, itemCommon, MondayItemWithValues } from './item-common'; +import { makeClient } from '../../../common'; +import { searchItemsByColumnValuesActionOutputSchema } from '../../../output-schemas'; + +export const searchItemsByColumnValuesAction = createAction({ + auth: mondayAuth, + name: 'monday_search_items_by_column_values', + classification: 'SEARCH', + displayName: 'Search Items by Column Values', + description: 'Finds items on a board whose columns match given values.', + audience: 'ai', + aiMetadata: { + description: + 'Find items on a monday.com board whose column values exactly match the given values (e.g. an email, a status label, the item name via column "name"); values for the same column are OR-ed, different columns are AND-ed. Supported column types include text, status, dropdown, email, phone, numbers, date, people, checkbox and long text; not formula, mirror, connect-boards, file or tags. For fuzzy or rule-based filters use List Board Items with Query Params. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: searchItemsByColumnValuesActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + filters: Property.Array({ + displayName: 'Filters', + description: 'One row per column/value pair.', + required: false, + properties: { + column_id: Property.ShortText({ + displayName: 'Column ID', + required: true, + }), + value: Property.ShortText({ + displayName: 'Value', + description: 'Exact value to match, e.g. a status label text or an email.', + required: true, + }), + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Items per page, 1-500. Defaults to 50.', + required: false, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'Cursor from a previous page. When set, Filters are ignored.', + required: false, + }), + column_ids: Property.Array({ + displayName: 'Column IDs', + description: 'Only return these column values. Leave empty for all columns.', + required: false, + }), + }, + async run(context) { + const { board_id, filters, cursor } = context.propsValue; + const limit = itemCommon.clampLimit(context.propsValue.limit); + const columnIds = itemCommon.columnIdsVariable(context.propsValue.column_ids); + const columnIdsVariable = columnIds === null ? {} : { columnIds }; + const hasCursor = !isNil(cursor) && cursor !== ''; + const columns = hasCursor ? null : toColumnsFilter({ filters }); + + if (!hasCursor && (isNil(columns) || columns.length === 0)) { + throw new Error('Provide at least one filter, or a cursor from a previous page.'); + } + + const data = await makeClient(context.auth).query<{ items_page_by_column_values: MondayItemsPage }>({ + query: `query ($boardId: ID!, $limit: Int!, $cursor: String, $columns: [ItemsPageByColumnValuesQuery!], $columnIds: [String!]) { + items_page_by_column_values(board_id: $boardId, limit: $limit, cursor: $cursor, columns: $columns) { + cursor + items { ${ITEM_WITH_VALUES_FIELDS} } + } + }`, + variables: { + boardId: board_id, + limit, + ...columnIdsVariable, + ...(hasCursor ? { cursor } : { columns }), + }, + }); + + const page = data.items_page_by_column_values; + const items = page.items.map((item) => itemCommon.mapItemWithValues(item)); + return { items, count: items.length, cursor: page.cursor ?? null }; + }, +}); + +function toColumnsFilter({ filters }: { filters: unknown }): { column_id: string; column_values: string[] }[] { + if (!Array.isArray(filters)) { + return []; + } + const pairs = filters.flatMap((row: unknown) => { + const columnId = itemCommon.readString({ row, key: 'column_id' }); + if (isNil(columnId)) { + return []; + } + const value = readFilterValue({ row }); + if (isNil(value)) { + throw new Error(`Filter for column "${columnId}" has no value. Use an empty string to match empty cells.`); + } + return [{ columnId, value }]; + }); + + const columnOrder = [...new Set(pairs.map((pair) => pair.columnId))]; + return columnOrder.map((columnId) => ({ + column_id: columnId, + column_values: pairs.filter((pair) => pair.columnId === columnId).map((pair) => pair.value), + })); +} + +function readFilterValue({ row }: { row: unknown }): string | null { + if (typeof row !== 'object' || row === null || !('value' in row)) { + return null; + } + const value: unknown = Reflect.get(row, 'value'); + return isNil(value) ? null : String(value).trim(); +} + +type MondayItemsPage = { + cursor: string | null; + items: MondayItemWithValues[]; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/set-item-column-values.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/set-item-column-values.ts new file mode 100644 index 000000000000..35068805bc3a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/set-item-column-values.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { ITEM_SUMMARY_FIELDS, itemCommon, MondayItemSummary } from './item-common'; +import { makeClient } from '../../../common'; +import { setItemColumnValuesActionOutputSchema } from '../../../output-schemas'; + +export const setItemColumnValuesAction = createAction({ + auth: mondayAuth, + name: 'monday_set_item_column_values', + classification: 'WRITE', + displayName: 'Set Item Column Values', + description: 'Updates several column values of an item at once.', + audience: 'ai', + aiMetadata: { + description: + 'Update several column values on an existing monday.com item in one call, given a JSON map of column ID to value; only the listed columns change. Prefer Change Simple Column Value for a single text/status/dropdown label, and Change Column Value for one complex column. File columns cannot be set here (use Upload File). Safe to retry: re-applying the same values converges on the same state.', + idempotent: true, + }, + outputSchema: setItemColumnValuesActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + column_values: Property.Json({ + displayName: 'Column Values', + description: + 'JSON map of column ID to value, e.g. {"status": {"label": "Done"}, "numbers": "42", "people": {"personsAndTeams": [{"id": 123, "kind": "person"}]}}.', + required: true, + }), + create_labels_if_missing: Property.Checkbox({ + displayName: 'Create Labels If Missing', + description: 'Create missing status/dropdown labels. Requires permission to change the board structure.', + required: false, + defaultValue: false, + }), + }, + async run(context) { + const { board_id, item_id, column_values, create_labels_if_missing } = context.propsValue; + + const data = await makeClient(context.auth).query<{ change_multiple_column_values: MondayItemSummary }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $columnValues: JSON!, $createLabels: Boolean) { + change_multiple_column_values(board_id: $boardId, item_id: $itemId, column_values: $columnValues, create_labels_if_missing: $createLabels) { + ${ITEM_SUMMARY_FIELDS} + } + }`, + variables: { + boardId: board_id, + itemId: item_id, + columnValues: mondayApi.toJsonString(column_values), + createLabels: create_labels_if_missing ?? false, + }, + }); + + return itemCommon.mapItemSummary(data.change_multiple_column_values); + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/items/update-assets-on-item.ts b/packages/pieces/community/monday/src/lib/actions/ai/items/update-assets-on-item.ts new file mode 100644 index 000000000000..769c703e52b7 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/items/update-assets-on-item.ts @@ -0,0 +1,129 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { itemCommon } from './item-common'; +import { makeClient } from '../../../common'; +import { updateAssetsOnItemActionOutputSchema } from '../../../output-schemas'; + +export const updateAssetsOnItemAction = createAction({ + auth: mondayAuth, + name: 'monday_update_assets_on_item', + classification: 'WRITE', + displayName: 'Update Files on Item', + description: 'Sets the files of a file column from existing assets, docs or links.', + audience: 'ai', + aiMetadata: { + description: + 'Set the contents of a file column on a monday.com item from files that already exist: uploaded assets (by asset ID), monday docs (by doc object ID), or links (Google Drive, Dropbox, OneDrive, Box or a plain URL). Use Upload File to upload new file bytes. Safe to retry with the same list.', + idempotent: true, + }, + outputSchema: updateAssetsOnItemActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + item_id: mondayAiProps.itemId(), + column_id: Property.ShortText({ + displayName: 'File Column ID', + description: 'The ID of a file-type column. Resolve it with List Columns.', + required: true, + }), + files: Property.Array({ + displayName: 'Files', + required: true, + properties: { + file_type: Property.StaticDropdown({ + displayName: 'File Type', + required: true, + options: { + options: [ + { label: 'Uploaded asset', value: 'asset' }, + { label: 'monday doc', value: 'doc' }, + { label: 'Link', value: 'link' }, + { label: 'Google Drive', value: 'google_drive' }, + { label: 'Dropbox', value: 'dropbox' }, + { label: 'OneDrive', value: 'onedrive' }, + { label: 'Box', value: 'box' }, + ], + }, + }), + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + asset_id: Property.ShortText({ + displayName: 'Asset ID', + description: 'Required for uploaded assets. Resolve it with Get Assets.', + required: false, + }), + object_id: Property.ShortText({ + displayName: 'Doc Object ID', + description: 'Required for monday docs.', + required: false, + }), + link_to_file: Property.ShortText({ + displayName: 'Link', + description: 'Required for links and cloud storage files.', + required: false, + }), + }, + }), + }, + async run(context) { + const { board_id, item_id, column_id } = context.propsValue; + const files = toFileInputs({ rows: context.propsValue.files }); + if (files.length === 0) { + throw new Error('Provide at least one file with a File Type and Name.'); + } + + const data = await makeClient(context.auth).query<{ + update_assets_on_item: { id: string; assets: { id: string; name: string; url: string | null }[] | null }; + }>({ + query: `mutation ($boardId: ID!, $itemId: ID!, $columnId: String!, $files: [FileInput!]!) { + update_assets_on_item(board_id: $boardId, item_id: $itemId, column_id: $columnId, files: $files) { + id + assets { id name url } + } + }`, + variables: { boardId: board_id, itemId: item_id, columnId: column_id, files }, + }); + + const assets = (data.update_assets_on_item.assets ?? []).map((asset) => ({ + id: asset.id, + name: asset.name, + url: asset.url ?? null, + })); + return { item_id: data.update_assets_on_item.id, assets, count: assets.length }; + }, +}); + +function toFileInputs({ rows }: { rows: unknown }): FileInput[] { + if (!Array.isArray(rows)) { + return []; + } + return rows + .map((row: unknown) => { + const fileType = itemCommon.readString({ row, key: 'file_type' }); + const name = itemCommon.readString({ row, key: 'name' }); + if (isNil(fileType) || isNil(name)) { + return null; + } + const assetId = itemCommon.readString({ row, key: 'asset_id' }); + const objectId = itemCommon.readString({ row, key: 'object_id' }); + const linkToFile = itemCommon.readString({ row, key: 'link_to_file' }); + return { + fileType, + name, + ...(isNil(assetId) ? {} : { assetId }), + ...(isNil(objectId) ? {} : { objectId }), + ...(isNil(linkToFile) ? {} : { linkToFile }), + }; + }) + .filter((file): file is FileInput => !isNil(file)); +} + +type FileInput = { + fileType: string; + name: string; + assetId?: string; + objectId?: string; + linkToFile?: string; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/aggregate-board-data.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/aggregate-board-data.ts new file mode 100644 index 000000000000..7e8508d5ca26 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/aggregate-board-data.ts @@ -0,0 +1,142 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { aggregateBoardDataActionOutputSchema } from '../../../output-schemas'; + +export const aggregateBoardDataAction = createAction({ + auth: mondayAuth, + name: 'monday_aggregate_board_data', + classification: 'READ', + displayName: 'Aggregate Board Data', + description: 'Counts or summarizes a board\'s items, optionally grouped by a column.', + audience: 'ai', + aiMetadata: { + description: + 'Compute a count, sum, average, median, min or max over a monday.com board\'s items server-side, optionally grouped by one column (e.g. items per status, story points per owner) and filtered with an items_page-style query. Prefer this over listing items and counting yourself: it is far cheaper and exact. Group values are raw: status returns hex colors, people "person-", dates epoch ms. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: aggregateBoardDataActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + function: Property.StaticDropdown({ + displayName: 'Function', + required: true, + defaultValue: 'COUNT_ITEMS', + options: { + options: [ + { label: 'Count items', value: 'COUNT_ITEMS' }, + { label: 'Count subitems', value: 'COUNT_SUBITEMS' }, + { label: 'Count non-empty values in column', value: 'COUNT' }, + { label: 'Count distinct values in column', value: 'COUNT_DISTINCT' }, + { label: 'Sum of column', value: 'SUM' }, + { label: 'Average of column', value: 'AVERAGE' }, + { label: 'Median of column', value: 'MEDIAN' }, + { label: 'Minimum of column', value: 'MIN' }, + { label: 'Maximum of column', value: 'MAX' }, + ], + }, + }), + column_id: Property.ShortText({ + displayName: 'Column ID', + description: 'The column to aggregate. Required for every function except the item/subitem counts. Resolve it with List Columns.', + required: false, + }), + group_by_column_id: Property.ShortText({ + displayName: 'Group By Column ID', + description: 'Optional column to group results by (e.g. "status", "person").', + required: false, + }), + group_limit: Property.Number({ + displayName: 'Group Limit', + description: 'Maximum number of groups to return (default 1000).', + required: false, + }), + filter: Property.Json({ + displayName: 'Filter', + description: 'Optional items query, e.g. {"rules":[{"column_id":"status","compare_value":[1],"operator":"any_of"}]}.', + required: false, + }), + }, + async run(context) { + const { board_id, column_id, group_by_column_id, group_limit, filter } = context.propsValue; + const fn = context.propsValue.function; + if (!ITEM_LEVEL_FUNCTIONS.includes(fn) && !column_id) { + throw new Error(`The ${fn} function needs a Column ID.`); + } + + const metricSelect = { + type: 'FUNCTION', + as: METRIC_ALIAS, + function: { + function: fn, + ...(ITEM_LEVEL_FUNCTIONS.includes(fn) + ? {} + : { params: [{ type: 'COLUMN', column: { column_id }, as: column_id }] }), + }, + }; + const select = group_by_column_id + ? [metricSelect, { type: 'COLUMN', column: { column_id: group_by_column_id }, as: group_by_column_id }] + : [metricSelect]; + + const aggregateQuery = { + from: { type: 'TABLE', id: board_id }, + select, + ...(group_by_column_id + ? { group_by: [{ column_id: group_by_column_id, ...(group_limit ? { limit: group_limit } : {}) }] } + : {}), + ...(filter && Object.keys(filter).length > 0 ? { query: filter } : {}), + }; + + const data = await makeClient(context.auth).query<{ aggregate: { results: { entries: AggregateEntry[] | null }[] | null } | null }>({ + query: `query ($query: AggregateQueryInput!) { + aggregate(query: $query) { + results { + entries { + alias + value { + ... on AggregateBasicAggregationResult { result } + ... on AggregateGroupByResult { value } + } + } + } + } + }`, + variables: { query: aggregateQuery }, + }); + + const rows = (data.aggregate?.results ?? []).map((set) => { + const entries = set.entries ?? []; + const metric = entries.find((e) => e.alias === METRIC_ALIAS); + const group = group_by_column_id ? entries.find((e) => e.alias === group_by_column_id) : undefined; + return { + group_value: group ? toGroupValue(group.value?.value) : null, + value: metric?.value?.result ?? null, + }; + }); + + return { + board_id, + function: fn, + column_id: column_id ?? null, + group_by_column_id: group_by_column_id ?? null, + rows, + count: rows.length, + }; + }, +}); + +function toGroupValue(value: unknown): string | null { + if (value === undefined || value === null) { + return null; + } + return typeof value === 'object' ? JSON.stringify(value) : String(value); +} + +const METRIC_ALIAS = 'metric'; +const ITEM_LEVEL_FUNCTIONS = ['COUNT_ITEMS', 'COUNT_SUBITEMS']; + +type AggregateEntry = { + alias: string | null; + value: { result?: number | null; value?: unknown } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/create-or-get-tag.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/create-or-get-tag.ts new file mode 100644 index 000000000000..f3a9756e8689 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/create-or-get-tag.ts @@ -0,0 +1,54 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { createOrGetTagActionOutputSchema } from '../../../output-schemas'; + +export const createOrGetTagAction = createAction({ + auth: mondayAuth, + name: 'monday_create_or_get_tag', + classification: 'WRITE', + displayName: 'Create or Get Tag', + description: 'Creates a tag, or returns it if a tag with that name already exists.', + audience: 'ai', + aiMetadata: { + description: + 'Return the monday.com tag with the given name, creating it first if it does not exist, and give back its ID for use in a tags column value ({"tag_ids":[id]}). Pass a board ID only for private or shareable boards; omit it for a public tag. A new tag only shows in the UI once it is used. Safe to retry: the same name returns the same tag.', + idempotent: true, + }, + outputSchema: createOrGetTagActionOutputSchema, + props: { + tag_name: Property.ShortText({ + displayName: 'Tag Name', + required: true, + }), + board_id: Property.ShortText({ + displayName: 'Board ID', + description: 'Only for private or shareable boards. Leave empty to create a public tag.', + required: false, + }), + }, + async run(context) { + const { tag_name, board_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_or_get_tag: { id: number | string; name: string; color: string | null } | null }>({ + query: `mutation ($tagName: String, $boardId: ID) { + create_or_get_tag(tag_name: $tagName, board_id: $boardId) { id name color } + }`, + variables: { + tagName: tag_name, + boardId: board_id || undefined, + }, + }); + + const tag = data.create_or_get_tag; + if (!tag) { + throw new Error('monday.com did not return the tag.'); + } + + return { + id: String(tag.id), + name: tag.name, + color: tag.color ?? null, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/get-board-mute-settings.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/get-board-mute-settings.ts new file mode 100644 index 000000000000..5019e413e0f2 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/get-board-mute-settings.ts @@ -0,0 +1,50 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { getBoardMuteSettingsActionOutputSchema } from '../../../output-schemas'; + +export const getBoardMuteSettingsAction = createAction({ + auth: mondayAuth, + name: 'monday_get_board_mute_settings', + classification: 'READ', + displayName: 'Get Board Mute Settings', + description: 'Gets the notification mute state of one or more boards.', + audience: 'ai', + aiMetadata: { + description: + 'Return the notification mute state of one or more monday.com boards for the connected user (e.g. CURRENT_USER_MUTE_ALL, MENTIONS_AND_ASSIGNS_ONLY, or an owner-level MUTE_ALL / NOT_MUTED). Use before changing it with Update Board Mute Settings. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getBoardMuteSettingsActionOutputSchema, + props: { + board_ids: Property.Array({ + displayName: 'Board IDs', + description: 'Boards to check. Resolve them with List Boards.', + required: true, + }), + }, + async run(context) { + const boardIds = mondayApi.toStringArray(context.propsValue.board_ids); + if (boardIds.length === 0) { + throw new Error('Provide at least one board ID.'); + } + + const data = await makeClient(context.auth).query<{ mute_board_settings: { board_id: string | null; mute_state: string | null }[] | null }>({ + query: `query ($boardIds: [ID!]!) { + mute_board_settings(board_ids: $boardIds) { + board_id + mute_state + } + }`, + variables: { boardIds }, + }); + + const settings = (data.mute_board_settings ?? []).map((s) => ({ + board_id: s.board_id ?? null, + mute_state: s.mute_state ?? null, + })); + + return { settings, count: settings.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/get-favorites.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/get-favorites.ts new file mode 100644 index 000000000000..357db093a534 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/get-favorites.ts @@ -0,0 +1,57 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { getFavoritesActionOutputSchema } from '../../../output-schemas'; + +export const getFavoritesAction = createAction({ + auth: mondayAuth, + name: 'monday_get_favorites', + classification: 'SEARCH', + displayName: 'Get Favorites', + description: 'Lists the connected user\'s favorite boards, dashboards and docs.', + audience: 'ai', + aiMetadata: { + description: + 'List the monday.com favorites of the user who owns the API token, each with the favorited object\'s ID and type (e.g. Board, Overview for dashboards, Document). Use to find the boards and docs the user works with most. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getFavoritesActionOutputSchema, + props: {}, + async run(context) { + const data = await makeClient(context.auth).query<{ favorites: (MondayFavorite | null)[] | null }>({ + query: `query { + favorites { + id + position + folderId + createdAt + updatedAt + object { id type } + } + }`, + }); + + const favorites = (data.favorites ?? []) + .filter((f): f is MondayFavorite => f !== null) + .map((f) => ({ + id: f.id ?? null, + object_id: f.object?.id ?? null, + object_type: f.object?.type ?? null, + folder_id: f.folderId ?? null, + position: f.position ?? null, + created_at: f.createdAt ?? null, + updated_at: f.updatedAt ?? null, + })); + + return { favorites, count: favorites.length }; + }, +}); + +type MondayFavorite = { + id: string | null; + position: number | null; + folderId: string | null; + createdAt: string | null; + updatedAt: string | null; + object: { id: string; type: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/get-form.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/get-form.ts new file mode 100644 index 000000000000..283a5f63fc40 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/get-form.ts @@ -0,0 +1,82 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { getFormActionOutputSchema } from '../../../output-schemas'; + +export const getFormAction = createAction({ + auth: mondayAuth, + name: 'monday_get_form', + classification: 'READ', + displayName: 'Get Form', + description: 'Gets a monday.com WorkForm and its questions by form token.', + audience: 'ai', + aiMetadata: { + description: + 'Fetch a monday.com WorkForm\'s metadata and questions (ID, title, type, required, visible) by its form token, the string after /forms/ in the form URL. Use to learn what a form asks before interpreting its board items. Requires access to the form\'s board. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getFormActionOutputSchema, + props: { + form_token: Property.ShortText({ + displayName: 'Form Token', + description: 'The token after "/forms/" and before "?" in the form\'s share URL.', + required: true, + }), + }, + async run(context) { + const token = extractToken(context.propsValue.form_token); + + const data = await makeClient(context.auth).query<{ form: MondayForm | null }>({ + query: `query ($formToken: String!) { + form(formToken: $formToken) { + id + title + description + active + isAnonymous + ownerId + questions { id title type required visible } + } + }`, + variables: { formToken: token }, + }); + + const form = data.form; + if (!form) { + throw new Error('Form not found or not accessible with this token.'); + } + + return { + id: String(form.id), + title: form.title, + description: form.description ?? null, + active: form.active, + is_anonymous: form.isAnonymous, + owner_id: form.ownerId === null || form.ownerId === undefined ? null : String(form.ownerId), + question_count: (form.questions ?? []).length, + questions: (form.questions ?? []).map((q) => ({ + id: q.id, + title: q.title ?? null, + type: q.type ?? null, + required: q.required ?? null, + visible: q.visible ?? null, + })), + }; + }, +}); + +function extractToken(value: string): string { + const trimmed = value.trim(); + const match = trimmed.match(/\/forms\/([^/?#]+)/); + return match ? match[1] : trimmed; +} + +type MondayForm = { + id: number | string; + title: string; + description: string | null; + active: boolean; + isAnonymous: boolean; + ownerId: number | null; + questions: { id: string; title: string | null; type: string | null; required: boolean | null; visible: boolean | null }[] | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/list-notifications.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/list-notifications.ts new file mode 100644 index 000000000000..e6ff18a7dfed --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/list-notifications.ts @@ -0,0 +1,95 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { listNotificationsActionOutputSchema } from '../../../output-schemas'; + +export const listNotificationsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_notifications', + classification: 'SEARCH', + displayName: 'List My Notifications', + description: 'Lists the connected user\'s monday.com notifications.', + audience: 'ai', + aiMetadata: { + description: + 'List the bell notifications of the monday.com user who owns the API token, newest first, with the linked board, item and update IDs; optionally only unread ones or since a date. Use to see what needs the user\'s attention. Page with the last notification ID as the cursor. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listNotificationsActionOutputSchema, + props: { + unread_only: Property.Checkbox({ + displayName: 'Unread Only', + required: false, + defaultValue: false, + }), + since: Property.DateTime({ + displayName: 'Since', + description: 'Only return notifications from this date and time (inclusive).', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Number of notifications to return (default 25).', + required: false, + }), + cursor: Property.ShortText({ + displayName: 'Cursor', + description: 'The ID of the last notification from the previous page.', + required: false, + }), + }, + async run(context) { + const { unread_only, since, limit, cursor } = context.propsValue; + + const data = await makeClient(context.auth).query<{ notifications: MondayNotification[] | null }>({ + query: `query ($filterRead: Boolean, $since: ISO8601DateTime, $limit: Int, $cursor: ID) { + notifications(filter_read: $filterRead, since: $since, limit: $limit, cursor: $cursor) { + id + title + text + read + created_at + board { id name } + item { id name } + update { id } + } + }`, + variables: { + filterRead: unread_only ? true : undefined, + since: since || undefined, + limit: limit ?? undefined, + cursor: cursor || undefined, + }, + }); + + const notifications = (data.notifications ?? []).map((n) => ({ + id: n.id, + title: n.title ?? null, + text: n.text ?? null, + read: n.read, + created_at: n.created_at ?? null, + board_id: n.board?.id ?? null, + board_name: n.board?.name ?? null, + item_id: n.item?.id ?? null, + item_name: n.item?.name ?? null, + update_id: n.update?.id ?? null, + })); + + return { + notifications, + count: notifications.length, + next_cursor: notifications.length > 0 ? notifications[notifications.length - 1].id : null, + }; + }, +}); + +type MondayNotification = { + id: string; + title: string | null; + text: string | null; + read: boolean; + created_at: string | null; + board: { id: string; name: string } | null; + item: { id: string; name: string } | null; + update: { id: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/list-tags.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/list-tags.ts new file mode 100644 index 000000000000..3b4cee1858de --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/list-tags.ts @@ -0,0 +1,75 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { mondayClient } from '../../../common/client'; +import { mondayApi } from '../../../common/monday-api'; +import { listTagsActionOutputSchema } from '../../../output-schemas'; + +export const listTagsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_tags', + classification: 'SEARCH', + displayName: 'List Tags', + description: 'Lists public tags in the account, or the tags of a private or shareable board.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com tags (ID, name, color). Without a board ID it returns the account\'s public tags; with a board ID it returns the tags stored on that private or shareable board. Use to resolve tag IDs before writing a tags column; to create a missing tag use Create or Get Tag. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listTagsActionOutputSchema, + props: { + board_id: Property.ShortText({ + displayName: 'Board ID', + description: 'Only for private or shareable boards: return that board\'s tags. Resolve it with List Boards.', + required: false, + }), + tag_ids: Property.Array({ + displayName: 'Tag IDs', + description: 'Only return these public tag IDs.', + required: false, + }), + }, + async run(context) { + const { board_id } = context.propsValue; + const tagIds = mondayApi.toStringArray(context.propsValue.tag_ids); + + const rawTags = board_id + ? await listBoardTags({ client: makeClient(context.auth), boardId: board_id }) + : await listPublicTags({ client: makeClient(context.auth), tagIds }); + + const tags = rawTags.map((tag) => ({ + id: String(tag.id), + name: tag.name, + color: tag.color ?? null, + })); + + return { tags, count: tags.length }; + }, +}); + +async function listPublicTags({ client, tagIds }: { client: mondayClient; tagIds: string[] }): Promise { + const data = await client.query<{ tags: MondayTag[] | null }>({ + query: `query ($ids: [ID!]) { + tags(ids: $ids) { id name color } + }`, + variables: { ids: tagIds.length > 0 ? tagIds : undefined }, + }); + return data.tags ?? []; +} + +async function listBoardTags({ client, boardId }: { client: mondayClient; boardId: string }): Promise { + const data = await client.query<{ boards: { tags: MondayTag[] | null }[] | null }>({ + query: `query ($ids: [ID!]) { + boards(ids: $ids) { tags { id name color } } + }`, + variables: { ids: [boardId] }, + }); + return (data.boards ?? [])[0]?.tags ?? []; +} + +type MondayTag = { + id: number | string; + name: string; + color: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/search.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/search.ts new file mode 100644 index 000000000000..04df68208d13 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/search.ts @@ -0,0 +1,148 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { searchActionOutputSchema } from '../../../output-schemas'; + +export const searchAction = createAction({ + auth: mondayAuth, + name: 'monday_search', + classification: 'SEARCH', + displayName: 'Search', + description: 'Searches items, boards, docs or workspaces across the account by keyword.', + audience: 'ai', + aiMetadata: { + description: + 'Search the whole monday.com account by free-text query (keyword plus semantic matching) for items, boards, docs or workspaces and get back IDs, names and URLs ranked by relevance (max 20). Use when you know roughly what something is called but not its ID; to filter items on a known board by exact column values use Search Items by Column Values. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: searchActionOutputSchema, + props: { + entity_type: Property.StaticDropdown({ + displayName: 'Search For', + required: true, + defaultValue: 'items', + options: { + options: [ + { label: 'Items', value: 'items' }, + { label: 'Boards', value: 'boards' }, + { label: 'Docs', value: 'docs' }, + { label: 'Workspaces', value: 'workspaces' }, + ], + }, + }), + query: Property.ShortText({ + displayName: 'Query', + required: true, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Maximum results (default 10, max 20).', + required: false, + }), + board_ids: Property.Array({ + displayName: 'Board IDs', + description: 'Items and boards only: restrict to these boards.', + required: false, + }), + workspace_ids: Property.Array({ + displayName: 'Workspace IDs', + description: 'Items, boards and docs only: restrict to these workspaces.', + required: false, + }), + }, + async run(context) { + const { entity_type, query, limit } = context.propsValue; + if (limit !== undefined && limit !== null && (limit < 1 || limit > 20)) { + throw new Error('Limit must be between 1 and 20.'); + } + const boardIds = mondayApi.toStringArray(context.propsValue.board_ids); + const workspaceIds = mondayApi.toStringArray(context.propsValue.workspace_ids); + const spec = SEARCH_SPECS[entity_type]; + if (!spec) { + throw new Error(`Unsupported entity type: ${entity_type}`); + } + + const data = await makeClient(context.auth).query<{ search: Record | null }>({ + query: `query (${spec.variableDefs}) { + search { + ${entity_type}(${spec.args}) { + results { + id + indexed_data { ${spec.fields} } + } + } + } + }`, + variables: { + query, + limit: limit ?? undefined, + ...(spec.supportsBoards && boardIds.length > 0 ? { board_ids: boardIds } : {}), + ...(spec.supportsWorkspaces && workspaceIds.length > 0 ? { workspace_ids: workspaceIds } : {}), + }, + }); + + const results = (data.search?.[entity_type]?.results ?? []).map((result) => { + const indexed = result.indexed_data ?? {}; + return { + id: result.id, + entity_type, + name: toNullableString(indexed['name']), + url: toNullableString(indexed['url']), + board_id: toNullableString(indexed['board_id']), + workspace_id: toNullableString(indexed['workspace_id']), + description: toNullableString(indexed['description']), + }; + }); + + return { results, count: results.length }; + }, +}); + +function toNullableString(value: unknown): string | null { + return value === undefined || value === null ? null : String(value); +} + +const SEARCH_SPECS: Record = { + items: { + variableDefs: '$query: String!, $limit: Int, $board_ids: [ID!], $workspace_ids: [ID!]', + args: 'query: $query, limit: $limit, board_ids: $board_ids, workspace_ids: $workspace_ids', + fields: 'name url board_id workspace_id', + supportsBoards: true, + supportsWorkspaces: true, + }, + boards: { + variableDefs: '$query: String!, $limit: Int, $board_ids: [ID!], $workspace_ids: [ID!]', + args: 'query: $query, limit: $limit, board_ids: $board_ids, workspace_ids: $workspace_ids', + fields: 'name url description workspace_id', + supportsBoards: true, + supportsWorkspaces: true, + }, + docs: { + variableDefs: '$query: String!, $limit: Int, $workspace_ids: [ID!]', + args: 'query: $query, limit: $limit, workspace_ids: $workspace_ids', + fields: 'name workspace_id', + supportsBoards: false, + supportsWorkspaces: true, + }, + workspaces: { + variableDefs: '$query: String!, $limit: Int', + args: 'query: $query, limit: $limit', + fields: 'name description', + supportsBoards: false, + supportsWorkspaces: false, + }, +}; + +type SearchSpec = { + variableDefs: string; + args: string; + fields: string; + supportsBoards: boolean; + supportsWorkspaces: boolean; +}; + +type SearchResult = { + id: string; + indexed_data: Record | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/send-notification.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/send-notification.ts new file mode 100644 index 000000000000..7a18cfd8f90e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/send-notification.ts @@ -0,0 +1,71 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { sendNotificationActionOutputSchema } from '../../../output-schemas'; + +export const sendNotificationAction = createAction({ + auth: mondayAuth, + name: 'monday_send_notification', + classification: 'WRITE', + displayName: 'Send Notification', + description: 'Sends a bell notification to a monday.com user.', + audience: 'ai', + aiMetadata: { + description: + 'Send a bell notification (and possibly an email, per the recipient\'s preferences) to one monday.com user, linked to an item or board (target type Project) or to an update or reply (target type Post). Use to alert a specific person; to comment on an item for everyone use Post Item Update. monday.com returns no notification ID. Each call sends another notification.', + idempotent: false, + }, + outputSchema: sendNotificationActionOutputSchema, + props: { + user_id: Property.ShortText({ + displayName: 'User ID', + description: 'The recipient. Resolve it with List Users.', + required: true, + }), + target_type: Property.StaticDropdown({ + displayName: 'Target Type', + required: true, + defaultValue: 'Project', + options: { + options: [ + { label: 'Item or board', value: 'Project' }, + { label: 'Update or reply', value: 'Post' }, + ], + }, + }), + target_id: Property.ShortText({ + displayName: 'Target ID', + description: 'An item or board ID for "Item or board", or an update/reply ID for "Update or reply".', + required: true, + }), + text: Property.LongText({ + displayName: 'Text', + required: true, + }), + }, + async run(context) { + const { user_id, target_type, target_id, text } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_notification: { text: string | null } | null }>({ + query: `mutation ($userId: ID!, $targetId: ID!, $targetType: NotificationTargetType!, $text: String!) { + create_notification(user_id: $userId, target_id: $targetId, target_type: $targetType, text: $text) { + text + } + }`, + variables: { + userId: user_id, + targetId: target_id, + targetType: target_type, + text, + }, + }); + + return { + sent: true, + user_id, + target_type, + target_id, + text: data.create_notification?.text ?? text, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/other/update-board-mute-settings.ts b/packages/pieces/community/monday/src/lib/actions/ai/other/update-board-mute-settings.ts new file mode 100644 index 000000000000..3d28d7858e94 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/other/update-board-mute-settings.ts @@ -0,0 +1,84 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { updateBoardMuteSettingsActionOutputSchema } from '../../../output-schemas'; + +export const updateBoardMuteSettingsAction = createAction({ + auth: mondayAuth, + name: 'monday_update_board_mute_settings', + classification: 'WRITE', + displayName: 'Update Board Mute Settings', + description: 'Changes which notifications the connected user gets from a board.', + audience: 'ai', + aiMetadata: { + description: + 'Set the connected user\'s own notification preferences for one monday.com board: mute everything, only mentions and assignments, or custom (choose any of assigned, mentioned, automation notify). Affects only the current user, never other board members. Safe to retry: the same settings converge on the same state.', + idempotent: true, + }, + outputSchema: updateBoardMuteSettingsActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(), + mute_state: Property.StaticDropdown({ + displayName: 'Mute State', + required: true, + defaultValue: 'CUSTOM_SETTINGS', + options: { + options: [ + { label: 'Custom (choose below)', value: 'CUSTOM_SETTINGS' }, + { label: 'Only mentions and assignments', value: 'MENTIONS_AND_ASSIGNS_ONLY' }, + { label: 'Mute all for me', value: 'CURRENT_USER_MUTE_ALL' }, + ], + }, + }), + enabled: Property.StaticMultiSelectDropdown({ + displayName: 'Notify Me When', + description: 'Only used with the Custom mute state.', + required: false, + options: { + options: [ + { label: 'I am assigned', value: 'IM_ASSIGNED' }, + { label: 'I am mentioned', value: 'IM_MENTIONED' }, + { label: 'An automation notifies me', value: 'AUTOMATION_NOTIFIED' }, + ], + }, + }), + }, + async run(context) { + const { board_id, mute_state, enabled } = context.propsValue; + if (!ALLOWED_STATES.includes(mute_state)) { + throw new Error(`Unsupported mute state: ${mute_state}`); + } + const enabledSettings = mondayApi.toStringArray(enabled); + if (mute_state === 'CUSTOM_SETTINGS' && enabledSettings.length === 0) { + throw new Error('Choose at least one "Notify Me When" option for the Custom mute state.'); + } + + const data = await makeClient(context.auth).query<{ + update_mute_board_settings: { board_id: string | null; mute_state: string | null; enabled: string[] | null }[] | null; + }>({ + query: `mutation ($boardId: String!, $muteState: BoardMuteState!, $enabled: [CustomizableBoardSettings!]) { + update_mute_board_settings(board_id: $boardId, mute_state: $muteState, enabled: $enabled) { + board_id + mute_state + enabled + } + }`, + variables: { + boardId: board_id, + muteState: mute_state, + enabled: mute_state === 'CUSTOM_SETTINGS' ? enabledSettings : undefined, + }, + }); + + const setting = (data.update_mute_board_settings ?? [])[0]; + return { + board_id: setting?.board_id ?? board_id, + mute_state: setting?.mute_state ?? mute_state, + enabled: (setting?.enabled ?? []).join(', ') || null, + }; + }, +}); + +const ALLOWED_STATES = ['CUSTOM_SETTINGS', 'MENTIONS_AND_ASSIGNS_ONLY', 'CURRENT_USER_MUTE_ALL']; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/delete-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/delete-update.ts new file mode 100644 index 000000000000..1616ad4800f9 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/delete-update.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { deleteUpdateActionOutputSchema } from '../../../output-schemas'; + +export const deleteUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_update', + classification: 'DESTRUCTIVE', + displayName: 'Delete Update', + description: 'Permanently deletes an update or reply.', + audience: 'ai', + aiMetadata: { + description: + 'Permanently delete a monday.com update or reply by its ID. Irreversible; prefer Edit Update to correct text. A retry after success fails because the update no longer exists.', + idempotent: false, + }, + outputSchema: deleteUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + }, + async run(context) { + const { update_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ delete_update: { id: string } }>({ + query: `mutation ($id: ID!) { + delete_update(id: $id) { id } + }`, + variables: { id: update_id }, + }); + + return { id: data.delete_update.id, deleted: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/edit-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/edit-update.ts new file mode 100644 index 000000000000..312062fdea13 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/edit-update.ts @@ -0,0 +1,61 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { editUpdateActionOutputSchema } from '../../../output-schemas'; + +export const editUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_edit_update', + classification: 'WRITE', + displayName: 'Edit Update', + description: 'Replaces the text of an existing update.', + audience: 'ai', + aiMetadata: { + description: + 'Replace the body of an existing monday.com update or reply with new text. Use to correct a posted comment; to add a new comment use Post Item Update. Only the update\'s author can edit it. Safe to retry with the same body.', + idempotent: true, + }, + outputSchema: editUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + body: Property.LongText({ + displayName: 'Body', + description: 'The new update text. HTML is supported. Replaces the whole body.', + required: true, + }), + }, + async run(context) { + const { update_id, body } = context.propsValue; + + const data = await makeClient(context.auth).query<{ edit_update: MondayUpdate }>({ + query: `mutation ($id: ID!, $body: String!) { + edit_update(id: $id, body: $body) { + id + body + text_body + item_id + updated_at + } + }`, + variables: { id: update_id, body }, + }); + + const update = data.edit_update; + return { + id: update.id, + item_id: update.item_id ?? null, + body: update.body, + text_body: update.text_body ?? null, + updated_at: update.updated_at ?? null, + }; + }, +}); + +type MondayUpdate = { + id: string; + body: string; + text_body: string | null; + item_id: string | null; + updated_at: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/like-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/like-update.ts new file mode 100644 index 000000000000..fada0d0af0aa --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/like-update.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { likeUpdateActionOutputSchema } from '../../../output-schemas'; + +export const likeUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_like_update', + classification: 'WRITE', + displayName: 'Like Update', + description: 'Likes an update as the connected user.', + audience: 'ai', + aiMetadata: { + description: + 'Add a like from the connected user to a monday.com update. Use to acknowledge an update without replying; undo with Unlike Update. Liking an already-liked update is a no-op, so it is safe to retry.', + idempotent: true, + }, + outputSchema: likeUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + }, + async run(context) { + const { update_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ like_update: { id: string; item_id: string | null } }>({ + query: `mutation ($updateId: ID!) { + like_update(update_id: $updateId) { id item_id } + }`, + variables: { updateId: update_id }, + }); + + return { id: data.like_update.id, item_id: data.like_update.item_id ?? null, liked: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/list-board-updates.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/list-board-updates.ts new file mode 100644 index 000000000000..dd7f5dda5654 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/list-board-updates.ts @@ -0,0 +1,101 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { listBoardUpdatesActionOutputSchema } from '../../../output-schemas'; + +export const listBoardUpdatesAction = createAction({ + auth: mondayAuth, + name: 'monday_list_board_updates', + classification: 'SEARCH', + displayName: 'List Board Updates', + description: 'Lists updates across all items of a board, optionally within a date range.', + audience: 'ai', + aiMetadata: { + description: + 'List updates (comments) posted across a whole monday.com board, optionally limited to a date range, each tagged with its item ID. Use for board-wide activity digests; for one item\'s conversation with replies use List Item Updates. Updates posted in the last ~30 seconds may not appear yet. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listBoardUpdatesActionOutputSchema, + props: { + board_id: mondayAiProps.boardId(true), + from_date: Property.ShortText({ + displayName: 'From Date', + description: 'Only updates created on or after this ISO 8601 date-time (e.g. 2026-09-01T00:00:00Z).', + required: false, + }), + to_date: Property.ShortText({ + displayName: 'To Date', + description: 'Only updates created on or before this ISO 8601 date-time (e.g. 2026-09-30T23:59:59Z).', + required: false, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Updates per page (default 25, max 100).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + }), + }, + async run(context) { + const { board_id, from_date, to_date, limit, page } = context.propsValue; + + const data = await makeClient(context.auth).query<{ boards: { id: string; updates: MondayUpdate[] | null }[] }>({ + query: `query ($ids: [ID!], $limit: Int, $page: Int, $fromDate: ISO8601DateTime, $toDate: ISO8601DateTime) { + boards(ids: $ids) { + id + updates(limit: $limit, page: $page, from_date: $fromDate, to_date: $toDate) { + id + body + text_body + item_id + creator_id + creator { name } + created_at + updated_at + } + } + }`, + variables: { + ids: [board_id], + limit: limit ?? 25, + page: page ?? 1, + ...(isNil(from_date) ? {} : { fromDate: from_date }), + ...(isNil(to_date) ? {} : { toDate: to_date }), + }, + }); + + const board = data.boards[0]; + if (!board) { + throw new Error(`Board ${board_id} was not found or is not accessible.`); + } + + const updates = (board.updates ?? []).map((update) => ({ + id: update.id, + board_id: board.id, + item_id: update.item_id ?? null, + body: update.body, + text_body: update.text_body ?? null, + creator_id: update.creator_id ?? null, + creator_name: update.creator?.name ?? null, + created_at: update.created_at ?? null, + updated_at: update.updated_at ?? null, + })); + + return { updates, count: updates.length }; + }, +}); + +type MondayUpdate = { + id: string; + body: string; + text_body: string | null; + item_id: string | null; + creator_id: string | null; + creator: { name: string } | null; + created_at: string | null; + updated_at: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/list-item-updates.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/list-item-updates.ts new file mode 100644 index 000000000000..d582aa7e42e4 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/list-item-updates.ts @@ -0,0 +1,98 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { listItemUpdatesActionOutputSchema } from '../../../output-schemas'; + +export const listItemUpdatesAction = createAction({ + auth: mondayAuth, + name: 'monday_list_item_updates', + classification: 'SEARCH', + displayName: 'List Item Updates', + description: 'Lists the updates on an item, including their replies.', + audience: 'ai', + aiMetadata: { + description: + 'List the updates (comments) posted on one monday.com item, newest first, each with its replies. Use to read an item\'s conversation or to find an Update ID for Edit, Delete, Like, Pin or reply. For updates across a whole board use List Board Updates. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listItemUpdatesActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(true), + limit: Property.Number({ + displayName: 'Limit', + description: 'Updates per page (default 25, max 100).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + }), + }, + async run(context) { + const { item_id, limit, page } = context.propsValue; + + const data = await makeClient(context.auth).query<{ items: { id: string; updates: MondayUpdate[] | null }[] }>({ + query: `query ($ids: [ID!], $limit: Int, $page: Int) { + items(ids: $ids) { + id + updates(limit: $limit, page: $page) { + id + body + text_body + creator_id + creator { name } + created_at + updated_at + replies { id body text_body creator_id created_at } + } + } + }`, + variables: { ids: [item_id], limit: limit ?? 25, page: page ?? 1 }, + }); + + const item = data.items[0]; + if (!item) { + throw new Error(`Item ${item_id} was not found or is not accessible.`); + } + + const updates = (item.updates ?? []).map((update) => ({ + id: update.id, + item_id: item.id, + body: update.body, + text_body: update.text_body ?? null, + creator_id: update.creator_id ?? null, + creator_name: update.creator?.name ?? null, + created_at: update.created_at ?? null, + updated_at: update.updated_at ?? null, + reply_count: (update.replies ?? []).length, + replies: (update.replies ?? []).map((reply) => ({ + id: reply.id, + body: reply.body, + text_body: reply.text_body ?? null, + creator_id: reply.creator_id ?? null, + created_at: reply.created_at ?? null, + })), + })); + + return { updates, count: updates.length }; + }, +}); + +type MondayUpdate = { + id: string; + body: string; + text_body: string | null; + creator_id: string | null; + creator: { name: string } | null; + created_at: string | null; + updated_at: string | null; + replies: { + id: string; + body: string; + text_body: string | null; + creator_id: string | null; + created_at: string | null; + }[] | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/pin-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/pin-update.ts new file mode 100644 index 000000000000..8e02f9f56c1b --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/pin-update.ts @@ -0,0 +1,36 @@ +import { createAction, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { pinUpdateActionOutputSchema } from '../../../output-schemas'; + +export const pinUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_pin_update', + classification: 'WRITE', + displayName: 'Pin Update', + description: 'Pins an update to the top of its item.', + audience: 'ai', + aiMetadata: { + description: + 'Pin a monday.com update to the top of its item\'s update feed so it stays visible. Undo with Unpin Update. Pinning an already-pinned update leaves it pinned, so it is safe to retry.', + idempotent: true, + }, + outputSchema: pinUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + item_id: mondayAiProps.itemId(false), + }, + async run(context) { + const { update_id, item_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ pin_to_top: { id: string; item_id: string | null } }>({ + query: `mutation ($id: ID!, $itemId: ID) { + pin_to_top(id: $id, item_id: $itemId) { id item_id } + }`, + variables: { id: update_id, ...(isNil(item_id) ? {} : { itemId: item_id }) }, + }); + + return { id: data.pin_to_top.id, item_id: data.pin_to_top.item_id ?? null, pinned: true }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/post-item-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/post-item-update.ts new file mode 100644 index 000000000000..a4799c8223fc --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/post-item-update.ts @@ -0,0 +1,77 @@ +import { createAction, Property, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { postItemUpdateActionOutputSchema } from '../../../output-schemas'; + +export const postItemUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_post_item_update', + classification: 'WRITE', + displayName: 'Post Item Update', + description: 'Posts an update on an item, or a reply to an existing update.', + audience: 'ai', + aiMetadata: { + description: + 'Post a new update (comment) to a monday.com item, or reply to an existing update by passing its Parent Update ID instead of the item. Body accepts HTML. Use Edit Update to change an existing update. Each call posts a new update, so retries duplicate.', + idempotent: false, + }, + outputSchema: postItemUpdateActionOutputSchema, + props: { + item_id: mondayAiProps.itemId(false), + parent_id: Property.ShortText({ + displayName: 'Parent Update ID', + description: 'Set to reply to an existing update (from List Item Updates). Leave empty to post a top-level update on the item.', + required: false, + }), + body: Property.LongText({ + displayName: 'Body', + description: 'The update text. HTML is supported.', + required: true, + }), + }, + async run(context) { + const { item_id, parent_id, body } = context.propsValue; + if (isNil(item_id) && isNil(parent_id)) { + throw new Error('Provide an Item ID to post an update, or a Parent Update ID to reply.'); + } + + const data = await makeClient(context.auth).query<{ create_update: MondayUpdate }>({ + query: `mutation ($itemId: ID, $parentId: ID, $body: String!) { + create_update(item_id: $itemId, parent_id: $parentId, body: $body) { + id + body + text_body + item_id + creator_id + created_at + } + }`, + variables: { + ...(isNil(item_id) ? {} : { itemId: item_id }), + ...(isNil(parent_id) ? {} : { parentId: parent_id }), + body, + }, + }); + + const update = data.create_update; + return { + id: update.id, + item_id: update.item_id ?? null, + parent_id: parent_id ?? null, + body: update.body, + text_body: update.text_body ?? null, + creator_id: update.creator_id ?? null, + created_at: update.created_at ?? null, + }; + }, +}); + +type MondayUpdate = { + id: string; + body: string; + text_body: string | null; + item_id: string | null; + creator_id: string | null; + created_at: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/unlike-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/unlike-update.ts new file mode 100644 index 000000000000..4a0bef2c7c15 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/unlike-update.ts @@ -0,0 +1,35 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { unlikeUpdateActionOutputSchema } from '../../../output-schemas'; + +export const unlikeUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_unlike_update', + classification: 'WRITE', + displayName: 'Unlike Update', + description: 'Removes the connected user\'s like from an update.', + audience: 'ai', + aiMetadata: { + description: + 'Remove the connected user\'s like from a monday.com update, reversing Like Update. Unliking an update that is not liked leaves it unchanged, so it is safe to retry.', + idempotent: true, + }, + outputSchema: unlikeUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + }, + async run(context) { + const { update_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ unlike_update: { id: string; item_id: string | null } }>({ + query: `mutation ($updateId: ID!) { + unlike_update(update_id: $updateId) { id item_id } + }`, + variables: { updateId: update_id }, + }); + + return { id: data.unlike_update.id, item_id: data.unlike_update.item_id ?? null, liked: false }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/updates/unpin-update.ts b/packages/pieces/community/monday/src/lib/actions/ai/updates/unpin-update.ts new file mode 100644 index 000000000000..86937bb40ea1 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/updates/unpin-update.ts @@ -0,0 +1,36 @@ +import { createAction, isNil } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; +import { unpinUpdateActionOutputSchema } from '../../../output-schemas'; + +export const unpinUpdateAction = createAction({ + auth: mondayAuth, + name: 'monday_unpin_update', + classification: 'WRITE', + displayName: 'Unpin Update', + description: 'Unpins an update from the top of its item.', + audience: 'ai', + aiMetadata: { + description: + 'Unpin a monday.com update from the top of its item\'s update feed, reversing Pin Update. Unpinning an update that is not pinned leaves it unchanged, so it is safe to retry.', + idempotent: true, + }, + outputSchema: unpinUpdateActionOutputSchema, + props: { + update_id: mondayAiProps.updateId(true), + item_id: mondayAiProps.itemId(false), + }, + async run(context) { + const { update_id, item_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ unpin_from_top: { id: string; item_id: string | null } }>({ + query: `mutation ($id: ID!, $itemId: ID) { + unpin_from_top(id: $id, item_id: $itemId) { id item_id } + }`, + variables: { id: update_id, ...(isNil(item_id) ? {} : { itemId: item_id }) }, + }); + + return { id: data.unpin_from_top.id, item_id: data.unpin_from_top.item_id ?? null, pinned: false }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/users/get-account.ts b/packages/pieces/community/monday/src/lib/actions/ai/users/get-account.ts new file mode 100644 index 000000000000..6f04ab6cf568 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/users/get-account.ts @@ -0,0 +1,78 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { getAccountActionOutputSchema } from '../../../output-schemas'; + +export const getAccountAction = createAction({ + auth: mondayAuth, + name: 'monday_get_account', + classification: 'READ', + displayName: 'Get Account', + description: 'Gets details of the connected monday.com account.', + audience: 'ai', + aiMetadata: { + description: + 'Return the connected monday.com account: ID, name, slug, tier, plan, trial state, active member count, first day of week and active products. Use to check plan limits or account settings before relying on a plan-gated feature. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getAccountActionOutputSchema, + props: {}, + async run(context) { + const data = await makeClient(context.auth).query<{ account: MondayAccount | null }>({ + query: `query { + account { + id + name + slug + tier + country_code + active_members_count + first_day_of_the_week + is_during_trial + is_trial_expired + show_timeline_weekends + plan { tier period max_users version } + products { id kind tier } + } + }`, + }); + + const account = data.account; + if (!account) { + throw new Error('monday.com did not return the account.'); + } + + return { + id: account.id, + name: account.name, + slug: account.slug, + tier: account.tier ?? null, + country_code: account.country_code ?? null, + active_members_count: account.active_members_count ?? null, + first_day_of_the_week: account.first_day_of_the_week ?? null, + is_during_trial: account.is_during_trial ?? null, + is_trial_expired: account.is_trial_expired ?? null, + show_timeline_weekends: account.show_timeline_weekends ?? null, + plan_tier: account.plan?.tier ?? null, + plan_period: account.plan?.period ?? null, + plan_max_users: account.plan?.max_users ?? null, + plan_version: account.plan?.version ?? null, + products: (account.products ?? []).map((p) => [p.kind, p.tier].filter(Boolean).join(':')).join(', ') || null, + }; + }, +}); + +type MondayAccount = { + id: string; + name: string; + slug: string; + tier: string | null; + country_code: string | null; + active_members_count: number | null; + first_day_of_the_week: string | null; + is_during_trial: boolean | null; + is_trial_expired: boolean | null; + show_timeline_weekends: boolean | null; + plan: { tier: string | null; period: string | null; max_users: number | null; version: number | null } | null; + products: { id: string; kind: string | null; tier: string | null }[] | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/users/get-me.ts b/packages/pieces/community/monday/src/lib/actions/ai/users/get-me.ts new file mode 100644 index 000000000000..a9f470936e8e --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/users/get-me.ts @@ -0,0 +1,77 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { makeClient } from '../../../common'; +import { getMeActionOutputSchema } from '../../../output-schemas'; + +export const getMeAction = createAction({ + auth: mondayAuth, + name: 'monday_get_me', + classification: 'READ', + displayName: 'Get Current User', + description: 'Gets the monday.com user behind the connected API token.', + audience: 'ai', + aiMetadata: { + description: + 'Return the profile of the monday.com user who owns the connected API token (ID, name, email, role kind, status, time zone, account). Use to learn "who am I" before assigning items, sending notifications or filtering by the current user. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: getMeActionOutputSchema, + props: {}, + async run(context) { + const data = await makeClient(context.auth).query<{ me: MondayMe | null }>({ + query: `query { + me { + id + name + email + title + status + url + time_zone_identifier + country_code + created_at + user_config { kind } + photo_url { small } + account { id name slug } + } + }`, + }); + + const me = data.me; + if (!me) { + throw new Error('monday.com did not return the current user.'); + } + + return { + id: me.id, + name: me.name, + email: me.email, + title: me.title ?? null, + kind: me.user_config?.kind ?? null, + status: me.status ?? null, + url: me.url ?? null, + time_zone: me.time_zone_identifier ?? null, + country_code: me.country_code ?? null, + photo_url: me.photo_url?.small ?? null, + created_at: me.created_at ?? null, + account_id: me.account?.id ?? null, + account_name: me.account?.name ?? null, + account_slug: me.account?.slug ?? null, + }; + }, +}); + +type MondayMe = { + id: string; + name: string; + email: string; + title: string | null; + status: string | null; + url: string | null; + time_zone_identifier: string | null; + country_code: string | null; + created_at: string | null; + user_config: { kind: string } | null; + photo_url: { small: string | null } | null; + account: { id: string; name: string; slug: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/users/list-teams.ts b/packages/pieces/community/monday/src/lib/actions/ai/users/list-teams.ts new file mode 100644 index 000000000000..c1a1d1de1a31 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/users/list-teams.ts @@ -0,0 +1,71 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { listTeamsActionOutputSchema } from '../../../output-schemas'; + +export const listTeamsAction = createAction({ + auth: mondayAuth, + name: 'monday_list_teams', + classification: 'SEARCH', + displayName: 'List Teams', + description: 'Lists teams in the monday.com account with their members and owners.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com teams (optionally only given team IDs) with their member and owner user IDs and names. Use to resolve team IDs for board or workspace access, or to see who belongs to a team. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listTeamsActionOutputSchema, + props: { + team_ids: Property.Array({ + displayName: 'Team IDs', + description: 'Only return these team IDs. Leave empty for all teams.', + required: false, + }), + }, + async run(context) { + const teamIds = mondayApi.toStringArray(context.propsValue.team_ids); + + const data = await makeClient(context.auth).query<{ teams: MondayTeam[] | null }>({ + query: `query ($ids: [ID!]) { + teams(ids: $ids) { + id + name + picture_url + users { id name email } + owners { id name } + } + }`, + variables: { ids: teamIds.length > 0 ? teamIds : undefined }, + }); + + const teams = (data.teams ?? []).map((team) => { + const members = (team.users ?? []).filter((u): u is TeamUser => u !== null); + const owners = team.owners ?? []; + return { + id: team.id, + name: team.name, + picture_url: team.picture_url ?? null, + member_count: members.length, + member_ids: members.map((u) => u.id).join(', ') || null, + member_names: members.map((u) => u.name).join(', ') || null, + member_emails: members.map((u) => u.email).join(', ') || null, + owner_ids: owners.map((u) => u.id).join(', ') || null, + owner_names: owners.map((u) => u.name).join(', ') || null, + }; + }); + + return { teams, count: teams.length }; + }, +}); + +type TeamUser = { id: string; name: string; email: string }; + +type MondayTeam = { + id: string; + name: string; + picture_url: string | null; + users: (TeamUser | null)[] | null; + owners: { id: string; name: string }[] | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/users/list-users.ts b/packages/pieces/community/monday/src/lib/actions/ai/users/list-users.ts new file mode 100644 index 000000000000..1c738d8e2aac --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/users/list-users.ts @@ -0,0 +1,143 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; +import { listUsersActionOutputSchema } from '../../../output-schemas'; + +export const listUsersAction = createAction({ + auth: mondayAuth, + name: 'monday_list_users', + classification: 'SEARCH', + displayName: 'List Users', + description: 'Lists users in the monday.com account, with optional filters.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com account users with ID, name, email, role kind and status; filter by exact emails (the way to resolve a user ID from an email), fuzzy name, user IDs, kind or status. Use to find the user IDs needed for people columns, board/workspace access and notifications. Returns up to 200 per page by default (max 1000). Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listUsersActionOutputSchema, + props: { + emails: Property.Array({ + displayName: 'Emails', + description: 'Only return users with these exact email addresses.', + required: false, + }), + name: Property.ShortText({ + displayName: 'Name', + description: 'Fuzzy search by user name.', + required: false, + }), + user_ids: Property.Array({ + displayName: 'User IDs', + description: 'Only return these user IDs.', + required: false, + }), + user_kind: Property.StaticDropdown({ + displayName: 'User Kind', + description: 'Only return users of this kind.', + required: false, + options: { + options: [ + { label: 'Admin, member, guest and viewer', value: 'BASIC' }, + { label: 'Admin', value: 'ADMIN' }, + { label: 'Member', value: 'MEMBER' }, + { label: 'Guest', value: 'GUEST' }, + { label: 'View only', value: 'VIEW_ONLY' }, + ], + }, + }), + status: Property.StaticMultiSelectDropdown({ + displayName: 'Status', + description: 'Filter by activation status (default: active and pending).', + required: false, + options: { + options: [ + { label: 'Active', value: 'ACTIVE' }, + { label: 'Pending', value: 'PENDING' }, + { label: 'Inactive', value: 'INACTIVE' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Users per page (default 200, max 1000).', + required: false, + }), + page: Property.Number({ + displayName: 'Page', + description: 'Page number, starting at 1.', + required: false, + }), + }, + async run(context) { + const { name, user_kind, status, limit, page } = context.propsValue; + const emails = mondayApi.toStringArray(context.propsValue.emails); + const userIds = mondayApi.toStringArray(context.propsValue.user_ids); + if (limit !== undefined && limit !== null && (limit < 1 || limit > 1000)) { + throw new Error('Limit must be between 1 and 1000.'); + } + + const data = await makeClient(context.auth).query<{ users: (MondayUser | null)[] | null }>({ + query: `query ($emails: [String!], $ids: [ID!], $name: String, $user_kind: UserKindFilterInput, $status: [UserStatus!], $limit: Int, $page: Int) { + users(emails: $emails, ids: $ids, name: $name, user_kind: $user_kind, status: $status, limit: $limit, page: $page) { + id + name + email + title + status + url + time_zone_identifier + country_code + location + phone + created_at + user_config { kind } + } + }`, + variables: { + emails: emails.length > 0 ? emails : undefined, + ids: userIds.length > 0 ? userIds : undefined, + name: name || undefined, + user_kind: user_kind ? { in: [user_kind] } : undefined, + status: status && status.length > 0 ? status : undefined, + limit: limit ?? undefined, + page: page ?? undefined, + }, + }); + + const users = (data.users ?? []) + .filter((user): user is MondayUser => user !== null) + .map((user) => ({ + id: user.id, + name: user.name, + email: user.email, + title: user.title ?? null, + kind: user.user_config?.kind ?? null, + status: user.status ?? null, + url: user.url ?? null, + time_zone: user.time_zone_identifier ?? null, + country_code: user.country_code ?? null, + location: user.location ?? null, + phone: user.phone ?? null, + created_at: user.created_at ?? null, + })); + + return { users, count: users.length }; + }, +}); + +type MondayUser = { + id: string; + name: string; + email: string; + title: string | null; + status: string | null; + url: string | null; + time_zone_identifier: string | null; + country_code: string | null; + location: string | null; + phone: string | null; + created_at: string | null; + user_config: { kind: string } | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-teams-to-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-teams-to-workspace.ts new file mode 100644 index 000000000000..4e843486ac41 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-teams-to-workspace.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { addTeamsToWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const addTeamsToWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_add_teams_to_workspace', + classification: 'WRITE', + displayName: 'Add Teams to Workspace', + description: 'Adds teams to a workspace as subscribers or owners.', + audience: 'ai', + aiMetadata: { + description: + "Add whole teams to a monday.com workspace as subscribers or owners. Use to share a workspace with a team; for individual people use Add Users to Workspace. Re-adding teams already in the workspace leaves them unchanged, so it is safe to retry.", + idempotent: true, + }, + outputSchema: addTeamsToWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + team_ids: mondayAiProps.teamIds(), + kind: Property.StaticDropdown({ + displayName: 'Role', + required: true, + defaultValue: 'subscriber', + options: { + options: [ + { label: 'Subscriber', value: 'subscriber' }, + { label: 'Owner', value: 'owner' }, + ], + }, + }), + }, + async run(context) { + const { workspace_id, kind } = context.propsValue; + const ids = mondayApi.toStringArray(context.propsValue.team_ids); + if (ids.length === 0) { + throw new Error('Provide at least one team ID.'); + } + + const data = await makeClient(context.auth).query<{ add_teams_to_workspace: { id: string; name: string }[] | null }>({ + query: `mutation ($workspace_id: ID!, $team_ids: [ID!]!, $kind: WorkspaceSubscriberKind) { + add_teams_to_workspace(workspace_id: $workspace_id, team_ids: $team_ids, kind: $kind) { id name } + }`, + variables: { workspace_id, team_ids: ids, kind }, + }); + + const teams = (data.add_teams_to_workspace ?? []).map((entry) => ({ + id: entry.id, + name: entry.name, + })); + + return { workspace_id, role: kind, teams: teams, count: teams.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-users-to-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-users-to-workspace.ts new file mode 100644 index 000000000000..e7ed696f19c7 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/add-users-to-workspace.ts @@ -0,0 +1,57 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { addUsersToWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const addUsersToWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_add_users_to_workspace', + classification: 'WRITE', + displayName: 'Add Users to Workspace', + description: 'Adds users to a workspace as subscribers or owners.', + audience: 'ai', + aiMetadata: { + description: + "Add users to a monday.com workspace as subscribers or owners so they can see its boards and docs. Use for individual people; to add a whole team use Add Teams to Workspace. It sets each user role to the chosen kind, so adding an existing owner as a subscriber demotes them; repeating the same call is safe to retry.", + idempotent: true, + }, + outputSchema: addUsersToWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + user_ids: mondayAiProps.userIds(), + kind: Property.StaticDropdown({ + displayName: 'Role', + required: true, + defaultValue: 'subscriber', + options: { + options: [ + { label: 'Subscriber', value: 'subscriber' }, + { label: 'Owner', value: 'owner' }, + ], + }, + }), + }, + async run(context) { + const { workspace_id, kind } = context.propsValue; + const ids = mondayApi.toStringArray(context.propsValue.user_ids); + if (ids.length === 0) { + throw new Error('Provide at least one user ID.'); + } + + const data = await makeClient(context.auth).query<{ add_users_to_workspace: { id: string; name: string }[] | null }>({ + query: `mutation ($workspace_id: ID!, $user_ids: [ID!]!, $kind: WorkspaceSubscriberKind) { + add_users_to_workspace(workspace_id: $workspace_id, user_ids: $user_ids, kind: $kind) { id name } + }`, + variables: { workspace_id, user_ids: ids, kind }, + }); + + const users = (data.add_users_to_workspace ?? []).map((entry) => ({ + id: entry.id, + name: entry.name, + })); + + return { workspace_id, role: kind, users: users, count: users.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/create-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/create-workspace.ts new file mode 100644 index 000000000000..77f1bdaeb873 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/create-workspace.ts @@ -0,0 +1,87 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { createWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { makeClient } from '../../../common'; + +export const createWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_create_workspace', + classification: 'WRITE', + displayName: 'Create Workspace', + description: 'Creates a new workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Create a new monday.com workspace, open to the whole account or closed to invited members. Use before creating boards or folders for a new team or project. Account admins may restrict who can create workspaces. Each call creates another workspace, so retries duplicate.', + idempotent: false, + }, + outputSchema: createWorkspaceActionOutputSchema, + props: { + name: Property.ShortText({ + displayName: 'Name', + required: true, + }), + kind: Property.StaticDropdown({ + displayName: 'Kind', + description: 'Open workspaces are visible to everyone in the account; closed ones only to members.', + required: true, + defaultValue: 'open', + options: { + options: [ + { label: 'Open', value: 'open' }, + { label: 'Closed', value: 'closed' }, + ], + }, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + account_product_id: Property.ShortText({ + displayName: 'Account Product ID', + description: 'The account product (e.g. work management, CRM) to create the workspace in. Defaults to the main product.', + required: false, + }), + }, + async run(context) { + const { name, kind, description, account_product_id } = context.propsValue; + + const data = await makeClient(context.auth).query<{ create_workspace: MondayWorkspace }>({ + query: `mutation ($name: String!, $kind: WorkspaceKind!, $description: String, $account_product_id: ID) { + create_workspace(name: $name, kind: $kind, description: $description, account_product_id: $account_product_id) { + id + name + kind + description + state + created_at + } + }`, + variables: { + name, + kind, + description: description || undefined, + account_product_id: account_product_id || undefined, + }, + }); + + const workspace = data.create_workspace; + return { + id: workspace.id, + name: workspace.name, + kind: workspace.kind ?? null, + description: workspace.description ?? null, + state: workspace.state ?? null, + created_at: workspace.created_at ?? null, + }; + }, +}); + +type MondayWorkspace = { + id: string; + name: string; + kind: string | null; + description: string | null; + state: string | null; + created_at: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-teams-from-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-teams-from-workspace.ts new file mode 100644 index 000000000000..e63f727d3e73 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-teams-from-workspace.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { deleteTeamsFromWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const deleteTeamsFromWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_teams_from_workspace', + classification: 'DESTRUCTIVE', + displayName: 'Remove Teams from Workspace', + description: 'Removes teams from a workspace.', + audience: 'ai', + aiMetadata: { + description: + "Revoke whole teams' membership of a monday.com workspace. Use for teams; for individual people use Remove Users from Workspace. Removing teams that are no longer members has no further effect.", + idempotent: true, + }, + outputSchema: deleteTeamsFromWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + team_ids: mondayAiProps.teamIds(), + }, + async run(context) { + const { workspace_id } = context.propsValue; + const ids = mondayApi.toStringArray(context.propsValue.team_ids); + if (ids.length === 0) { + throw new Error('Provide at least one team ID.'); + } + + const data = await makeClient(context.auth).query<{ delete_teams_from_workspace: { id: string; name: string }[] | null }>({ + query: `mutation ($workspace_id: ID!, $team_ids: [ID!]!) { + delete_teams_from_workspace(workspace_id: $workspace_id, team_ids: $team_ids) { id name } + }`, + variables: { workspace_id, team_ids: ids }, + }); + + const teams = (data.delete_teams_from_workspace ?? []).map((entry) => ({ + id: entry.id, + name: entry.name, + })); + + return { workspace_id, removed_teams: teams, count: teams.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-users-from-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-users-from-workspace.ts new file mode 100644 index 000000000000..b5e15b7e6ce5 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-users-from-workspace.ts @@ -0,0 +1,46 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { deleteUsersFromWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const deleteUsersFromWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_users_from_workspace', + classification: 'DESTRUCTIVE', + displayName: 'Remove Users from Workspace', + description: 'Removes users from a workspace.', + audience: 'ai', + aiMetadata: { + description: + "Revoke users' membership of a monday.com workspace. Use for individual people; to remove a whole team use Remove Teams from Workspace. Removing users who are no longer members has no further effect.", + idempotent: true, + }, + outputSchema: deleteUsersFromWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + user_ids: mondayAiProps.userIds(), + }, + async run(context) { + const { workspace_id } = context.propsValue; + const ids = mondayApi.toStringArray(context.propsValue.user_ids); + if (ids.length === 0) { + throw new Error('Provide at least one user ID.'); + } + + const data = await makeClient(context.auth).query<{ delete_users_from_workspace: { id: string; name: string }[] | null }>({ + query: `mutation ($workspace_id: ID!, $user_ids: [ID!]!) { + delete_users_from_workspace(workspace_id: $workspace_id, user_ids: $user_ids) { id name } + }`, + variables: { workspace_id, user_ids: ids }, + }); + + const users = (data.delete_users_from_workspace ?? []).map((entry) => ({ + id: entry.id, + name: entry.name, + })); + + return { workspace_id, removed_users: users, count: users.length }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-workspace.ts new file mode 100644 index 000000000000..4ee085ec9f50 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/delete-workspace.ts @@ -0,0 +1,37 @@ +import { createAction } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { deleteWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const deleteWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_delete_workspace', + classification: 'DESTRUCTIVE', + displayName: 'Delete Workspace', + description: 'Deletes a workspace.', + audience: 'ai', + aiMetadata: { + description: + 'Delete a monday.com workspace. Destructive: the workspace and the boards, docs and folders inside it are removed for everyone, so only use when the user explicitly asks to delete this workspace. To remove a single board prefer Archive Board. A retry after success fails because the workspace is gone.', + idempotent: false, + }, + outputSchema: deleteWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + }, + async run(context) { + const data = await makeClient(context.auth).query<{ delete_workspace: { id: string; name: string | null } }>({ + query: `mutation ($workspace_id: ID!) { + delete_workspace(workspace_id: $workspace_id) { id name } + }`, + variables: { workspace_id: context.propsValue.workspace_id }, + }); + + return { + id: data.delete_workspace.id, + name: data.delete_workspace.name ?? null, + deleted: true, + }; + }, +}); diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/list-workspaces.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/list-workspaces.ts new file mode 100644 index 000000000000..509df596b89a --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/list-workspaces.ts @@ -0,0 +1,124 @@ +import { createAction, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { listWorkspacesActionOutputSchema } from '../../../output-schemas'; +import { mondayApi } from '../../../common/monday-api'; +import { makeClient } from '../../../common'; + +export const listWorkspacesAction = createAction({ + auth: mondayAuth, + name: 'monday_list_workspaces', + classification: 'SEARCH', + displayName: 'List Workspaces', + description: 'Lists workspaces.', + audience: 'ai', + aiMetadata: { + description: + 'List monday.com workspaces the connected user can see, with kind (open/closed), state and description. Use to resolve a workspace name to the workspace ID needed by List Boards, Create Board, List Folders and the workspace membership actions. Read-only and safe to retry.', + idempotent: true, + }, + outputSchema: listWorkspacesActionOutputSchema, + props: { + workspace_ids: Property.Array({ + displayName: 'Workspace IDs', + description: 'Only return these workspaces.', + required: false, + }), + kind: Property.StaticDropdown({ + displayName: 'Kind', + required: false, + options: { + options: [ + { label: 'Open', value: 'open' }, + { label: 'Closed', value: 'closed' }, + ], + }, + }), + state: Property.StaticDropdown({ + displayName: 'State', + required: false, + defaultValue: 'active', + options: { + options: [ + { label: 'Active', value: 'active' }, + { label: 'Archived', value: 'archived' }, + { label: 'Deleted', value: 'deleted' }, + { label: 'All', value: 'all' }, + ], + }, + }), + membership_kind: Property.StaticDropdown({ + displayName: 'Membership', + description: 'Only workspaces the user is a member of, or all visible workspaces.', + required: false, + defaultValue: 'all', + options: { + options: [ + { label: 'All visible', value: 'all' }, + { label: 'Member of', value: 'member' }, + ], + }, + }), + limit: Property.Number({ + displayName: 'Limit', + description: 'Workspaces per page (default 50).', + required: false, + defaultValue: 50, + }), + page: Property.Number({ + displayName: 'Page', + required: false, + defaultValue: 1, + }), + }, + async run(context) { + const { kind, state, membership_kind, limit, page } = context.propsValue; + const ids = mondayApi.toStringArray(context.propsValue.workspace_ids); + + const data = await makeClient(context.auth).query<{ workspaces: MondayWorkspace[] }>({ + query: `query ($ids: [ID!], $kind: WorkspaceKind, $state: State, $membership_kind: WorkspaceMembershipKind, $limit: Int, $page: Int) { + workspaces(ids: $ids, kind: $kind, state: $state, membership_kind: $membership_kind, limit: $limit, page: $page) { + id + name + kind + description + state + created_at + is_default_workspace + } + }`, + variables: { + ids: ids.length > 0 ? ids : undefined, + kind: kind ?? undefined, + state: state ?? 'active', + membership_kind: membership_kind ?? 'all', + limit: limit ?? 50, + page: page ?? 1, + }, + }); + + const workspaces = data.workspaces.map(toFlatWorkspace); + return { workspaces, count: workspaces.length }; + }, +}); + +function toFlatWorkspace(workspace: MondayWorkspace) { + return { + id: workspace.id, + name: workspace.name, + kind: workspace.kind ?? null, + description: workspace.description ?? null, + state: workspace.state ?? null, + created_at: workspace.created_at ?? null, + is_default_workspace: workspace.is_default_workspace ?? false, + }; +} + +type MondayWorkspace = { + id: string; + name: string; + kind: string | null; + description: string | null; + state: string | null; + created_at: string | null; + is_default_workspace: boolean | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/ai/workspaces/update-workspace.ts b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/update-workspace.ts new file mode 100644 index 000000000000..588879ef0fd9 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/actions/ai/workspaces/update-workspace.ts @@ -0,0 +1,99 @@ +import { createAction, isNil, Property } from '@activepieces/pieces-framework'; +import { mondayAuth } from '../../../auth'; +import { updateWorkspaceActionOutputSchema } from '../../../output-schemas'; +import { mondayAiProps } from '../../../common/ai-props'; +import { makeClient } from '../../../common'; + +export const updateWorkspaceAction = createAction({ + auth: mondayAuth, + name: 'monday_update_workspace', + classification: 'WRITE', + displayName: 'Update Workspace', + description: "Updates a workspace's name, description, kind or product.", + audience: 'ai', + aiMetadata: { + description: + "Change a monday.com workspace's name, description, kind (open/closed), or account product. Only the fields you provide are changed; omitted fields keep their current value. To remove the description, set Clear Description. Setting the same values again leaves the workspace unchanged, so it is safe to retry.", + idempotent: true, + }, + outputSchema: updateWorkspaceActionOutputSchema, + props: { + workspace_id: mondayAiProps.workspaceId(), + name: Property.ShortText({ + displayName: 'Name', + required: false, + }), + description: Property.LongText({ + displayName: 'Description', + required: false, + }), + clear_description: Property.Checkbox({ + displayName: 'Clear Description', + description: 'Remove the current description. Leave unchecked to keep it.', + required: false, + defaultValue: false, + }), + kind: Property.StaticDropdown({ + displayName: 'Kind', + description: 'Leave empty to keep the current kind.', + required: false, + options: { + options: [ + { label: 'Open', value: 'open' }, + { label: 'Closed', value: 'closed' }, + ], + }, + }), + account_product_id: Property.ShortText({ + displayName: 'Account Product ID', + description: 'Move the workspace to this account product.', + required: false, + }), + }, + async run(context) { + const { workspace_id, name, description, clear_description, kind, account_product_id } = context.propsValue; + if (clear_description && !isNil(description) && description !== '') { + throw new Error('Set either Description or Clear Description, not both.'); + } + const attributes = { + ...(name ? { name } : {}), + ...(clear_description ? { description: '' } : {}), + ...(!clear_description && !isNil(description) && description !== '' ? { description } : {}), + ...(kind ? { kind } : {}), + ...(account_product_id ? { account_product_id } : {}), + }; + if (Object.keys(attributes).length === 0) { + throw new Error('Provide at least one field to update.'); + } + + const data = await makeClient(context.auth).query<{ update_workspace: MondayWorkspace }>({ + query: `mutation ($id: ID!, $attributes: UpdateWorkspaceAttributesInput!) { + update_workspace(id: $id, attributes: $attributes) { + id + name + kind + description + state + } + }`, + variables: { id: workspace_id, attributes }, + }); + + const workspace = data.update_workspace; + return { + id: workspace.id, + name: workspace.name, + kind: workspace.kind ?? null, + description: workspace.description ?? null, + state: workspace.state ?? null, + }; + }, +}); + +type MondayWorkspace = { + id: string; + name: string; + kind: string | null; + description: string | null; + state: string | null; +}; diff --git a/packages/pieces/community/monday/src/lib/actions/create-column.ts b/packages/pieces/community/monday/src/lib/actions/create-column.ts index 1af2f98ff211..678869e8fe05 100644 --- a/packages/pieces/community/monday/src/lib/actions/create-column.ts +++ b/packages/pieces/community/monday/src/lib/actions/create-column.ts @@ -10,7 +10,7 @@ export const createColumnAction = createAction({ classification: 'WRITE', displayName: 'Create Column', description: 'Creates a new column in board.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Adds a new column of a chosen type (text, status, date, number, etc.) to a monday.com board. Use to extend a board\'s structure before writing data into it. Not idempotent: each call creates a separate column even with the same title.', idempotent: false }, outputSchema: createColumnActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/create-group.ts b/packages/pieces/community/monday/src/lib/actions/create-group.ts index 69ccf1800b81..55059b83d668 100644 --- a/packages/pieces/community/monday/src/lib/actions/create-group.ts +++ b/packages/pieces/community/monday/src/lib/actions/create-group.ts @@ -9,7 +9,7 @@ export const createGroupAction = createAction({ classification: 'WRITE', displayName: 'Create Group', description: 'Creates a new group in board.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Creates a new group (section that holds items) on a monday.com board. Use to organize items under a named section before adding them. Not idempotent: each call creates a separate group even with the same name.', idempotent: false }, outputSchema: createGroupActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/create-item.ts b/packages/pieces/community/monday/src/lib/actions/create-item.ts index 43261e87dc53..b6ce27d0a9c0 100644 --- a/packages/pieces/community/monday/src/lib/actions/create-item.ts +++ b/packages/pieces/community/monday/src/lib/actions/create-item.ts @@ -17,7 +17,7 @@ export const createItemAction = createAction({ classification: 'WRITE', displayName: 'Create Item', description: 'Creates a new item inside a board.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Creates a new item (row) on a monday.com board, optionally placed in a group and pre-populated with column values that are auto-coerced to each column\'s type. Use to add a record to a board. Requires the board id and an item name; enable create-labels-if-missing only when allowed to modify board structure. Not idempotent: each call creates a separate item.', idempotent: false }, outputSchema: createItemActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/create-update.ts b/packages/pieces/community/monday/src/lib/actions/create-update.ts index 7837d67e0342..990040940b4a 100644 --- a/packages/pieces/community/monday/src/lib/actions/create-update.ts +++ b/packages/pieces/community/monday/src/lib/actions/create-update.ts @@ -9,7 +9,7 @@ export const createUpdateAction = createAction({ classification: 'WRITE', displayName: 'Create Update', description: 'Creates a new update.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Posts an update (a comment/note in the item\'s update feed) to a monday.com item identified by item id. Use to add a message or log to an item. Not idempotent: each call appends a new update.', idempotent: false }, outputSchema: createUpdateActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/get-board-values.ts b/packages/pieces/community/monday/src/lib/actions/get-board-values.ts index 4ceace2b31a4..6ac7ac50b3d2 100644 --- a/packages/pieces/community/monday/src/lib/actions/get-board-values.ts +++ b/packages/pieces/community/monday/src/lib/actions/get-board-values.ts @@ -9,7 +9,7 @@ export const getBoardItemValuesAction = createAction({ classification: 'SEARCH', displayName: 'Get Board Values', description: "Gets a list of board's items.", - audience: 'both', + audience: 'human', aiMetadata: { description: 'Retrieves all items on a monday.com board with their column values, optionally narrowed to specific column ids. Use to read or list the rows of a board. Read-only and idempotent.', idempotent: true }, props: { workspace_id: mondayCommon.workspace_id(true), diff --git a/packages/pieces/community/monday/src/lib/actions/get-column-values.ts b/packages/pieces/community/monday/src/lib/actions/get-column-values.ts index 14bfb7b91905..cb083ab1f493 100644 --- a/packages/pieces/community/monday/src/lib/actions/get-column-values.ts +++ b/packages/pieces/community/monday/src/lib/actions/get-column-values.ts @@ -9,7 +9,7 @@ export const getItemsColumnValuesAction = createAction({ classification: 'READ', displayName: "Get an Item's Column Values", description: 'Gets column values of an item.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Retrieves the column values for a single monday.com item identified by board and item id, optionally limited to specific column ids. Use to read one item\'s field values. Read-only and idempotent.', idempotent: true }, props: { workspace_id: mondayCommon.workspace_id(true), diff --git a/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts b/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts index d51f73843c36..72a2819f8b6e 100644 --- a/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts +++ b/packages/pieces/community/monday/src/lib/actions/update-column-values-of-item.ts @@ -16,7 +16,7 @@ export const updateColumnValuesOfItemAction = createAction({ classification: 'WRITE', displayName: 'Update Column Values of Specific Item', description: 'Updates multiple columns values of specific item.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Updates one or more column values on an existing monday.com item identified by board and item id; provided values are auto-coerced to each column\'s type. Use to edit an item\'s fields. Idempotent: re-applying the same values leaves the item in the same state.', idempotent: true }, outputSchema: updateColumnValuesOfItemActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/update-item-name.ts b/packages/pieces/community/monday/src/lib/actions/update-item-name.ts index 19668a41fe1e..814458c62f8e 100644 --- a/packages/pieces/community/monday/src/lib/actions/update-item-name.ts +++ b/packages/pieces/community/monday/src/lib/actions/update-item-name.ts @@ -9,7 +9,7 @@ export const updateItemNameAction = createAction({ classification: 'WRITE', displayName: 'Update Item Name', description: 'Updates an item name.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Renames an existing monday.com item identified by board and item id. Use to change an item\'s title. Idempotent: re-applying the same name leaves the item unchanged.', idempotent: true }, outputSchema: updateItemNameActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts b/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts index 2f5402daf1e2..dc1a646f7c0f 100644 --- a/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts +++ b/packages/pieces/community/monday/src/lib/actions/upload-file-to-column.ts @@ -12,7 +12,7 @@ export const uploadFileToColumnAction = createAction({ classification: 'WRITE', displayName: 'Upload File to Column', description: 'Upload a file to a column in Monday.', - audience: 'both', + audience: 'human', aiMetadata: { description: 'Uploads a file (from URL or base64) and attaches it to a file-type column on a monday.com item, identified by board, item, and file column id. Use to add an attachment to an item. Not idempotent: each call uploads and attaches another copy.', idempotent: false }, outputSchema: uploadFileToColumnActionOutputSchema, props: { diff --git a/packages/pieces/community/monday/src/lib/common/ai-props.ts b/packages/pieces/community/monday/src/lib/common/ai-props.ts new file mode 100644 index 000000000000..4296710201bb --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/ai-props.ts @@ -0,0 +1,94 @@ +import { Property } from '@activepieces/pieces-framework'; + +function boardId(required?: R) { + return Property.ShortText({ + displayName: 'Board ID', + description: 'The numeric board ID. Resolve it with List Boards.', + required: required ?? true, + }); +} + +function itemId(required?: R) { + return Property.ShortText({ + displayName: 'Item ID', + description: 'The numeric item ID. Resolve it with List Board Items or Search Items by Column Values.', + required: required ?? true, + }); +} + +function groupId(required?: R) { + return Property.ShortText({ + displayName: 'Group ID', + description: 'The group ID (e.g. "topics"). Resolve it with List Groups.', + required: required ?? true, + }); +} + +function columnId(required?: R) { + return Property.ShortText({ + displayName: 'Column ID', + description: 'The column ID (e.g. "status", "date4"). Resolve it with List Columns.', + required: required ?? true, + }); +} + +function workspaceId(required?: R) { + return Property.ShortText({ + displayName: 'Workspace ID', + description: 'The numeric workspace ID. Resolve it with List Workspaces.', + required: required ?? true, + }); +} + +function updateId(required?: R) { + return Property.ShortText({ + displayName: 'Update ID', + description: 'The update ID. Resolve it with List Item Updates.', + required: required ?? true, + }); +} + +function docId(required?: R) { + return Property.ShortText({ + displayName: 'Doc ID', + description: 'The doc ID (not the doc object ID). Resolve it with List Docs.', + required: required ?? true, + }); +} + +function folderId(required?: R) { + return Property.ShortText({ + displayName: 'Folder ID', + description: 'The folder ID. Resolve it with List Folders.', + required: required ?? true, + }); +} + +function userIds(required?: R) { + return Property.Array({ + displayName: 'User IDs', + description: 'Numeric user IDs. Resolve them with List Users.', + required: required ?? true, + }); +} + +function teamIds(required?: R) { + return Property.Array({ + displayName: 'Team IDs', + description: 'Numeric team IDs. Resolve them with List Teams.', + required: required ?? true, + }); +} + +export const mondayAiProps = { + boardId, + itemId, + groupId, + columnId, + workspaceId, + updateId, + docId, + folderId, + userIds, + teamIds, +}; diff --git a/packages/pieces/community/monday/src/lib/common/client.ts b/packages/pieces/community/monday/src/lib/common/client.ts index 5078d97b2a44..7b7cd685347a 100644 --- a/packages/pieces/community/monday/src/lib/common/client.ts +++ b/packages/pieces/community/monday/src/lib/common/client.ts @@ -1,3 +1,4 @@ +import { isNil } from '@activepieces/pieces-framework'; import mondaySdk from 'monday-sdk-js'; import { MondayClientSdk } from 'monday-sdk-js/types/client-sdk.interface'; import { Board, MondayColumn, User } from './models'; @@ -98,4 +99,43 @@ export class mondayClient { { variables: variables } ); } + async query({ + query, + variables, + }: { + query: string; + variables?: Record; + }): Promise { + const response = await this.client.api(query, { + variables: variables ?? {}, + apiVersion: MONDAY_AI_API_VERSION, + }); + const errorMessage = extractErrorMessage(response); + if (!isNil(errorMessage)) { + throw new Error(`monday.com API error: ${errorMessage}`); + } + if (isNil(response.data)) { + throw new Error('monday.com API returned no data.'); + } + return response.data; + } +} + +function extractErrorMessage(response: unknown): string | null { + if (typeof response !== 'object' || response === null) { + return null; + } + if ('errors' in response && Array.isArray(response.errors) && response.errors.length > 0) { + return response.errors + .map((error: unknown) => + typeof error === 'object' && error !== null && 'message' in error ? String(error.message) : JSON.stringify(error) + ) + .join('; '); + } + if ('error_message' in response && !isNil(response.error_message)) { + return String(response.error_message); + } + return null; } + +export const MONDAY_AI_API_VERSION = '2026-07'; diff --git a/packages/pieces/community/monday/src/lib/common/monday-api.ts b/packages/pieces/community/monday/src/lib/common/monday-api.ts new file mode 100644 index 000000000000..4f03316d33d6 --- /dev/null +++ b/packages/pieces/community/monday/src/lib/common/monday-api.ts @@ -0,0 +1,118 @@ +import { httpClient, HttpError, HttpMethod } from '@activepieces/pieces-common'; +import { isNil } from '@activepieces/pieces-framework'; +import FormData from 'form-data'; +import { MONDAY_AI_API_VERSION } from './client'; + +async function uploadFile({ + apiKey, + query, + variables, + file, + fileName, +}: { + apiKey: string; + query: string; + variables: Record; + file: Buffer; + fileName: string; +}): Promise { + const formData = new FormData(); + formData.append('query', query); + formData.append('variables', JSON.stringify(variables)); + formData.append('map', JSON.stringify({ file: 'variables.file' })); + formData.append('file', file, fileName); + + const response = await sendOrThrow(() => + httpClient.sendRequest>({ + method: HttpMethod.POST, + url: MONDAY_FILE_API_URL, + headers: { + Authorization: apiKey, + 'API-Version': MONDAY_AI_API_VERSION, + ...formData.getHeaders(), + }, + body: formData, + }) + ); + return unwrap(response); +} + +function toJsonString(value: unknown): string { + return typeof value === 'string' ? value : JSON.stringify(value ?? {}); +} + +function joinValues(values: unknown): string | null { + if (!Array.isArray(values) || values.length === 0) { + return null; + } + return values + .map((v) => (typeof v === 'object' && v !== null ? JSON.stringify(v) : String(v))) + .join(', '); +} + +function toStringArray(values: unknown): string[] { + if (!Array.isArray(values)) { + return []; + } + return values + .filter((v) => !isNil(v) && String(v).trim() !== '') + .map((v) => String(v).trim()); +} + +export const mondayApi = { + uploadFile, + toJsonString, + joinValues, + toStringArray, +}; + +async function sendOrThrow( + send: () => Promise<{ body: MondayGraphQLResponse }> +): Promise> { + try { + const response = await send(); + return response.body; + } catch (error) { + if (error instanceof HttpError) { + throw new Error(describeHttpError({ status: error.response.status, body: error.response.body })); + } + throw error; + } +} + +function unwrap(body: MondayGraphQLResponse): T { + if (!isNil(body.errors) && body.errors.length > 0) { + throw new Error(`monday.com API error: ${body.errors.map((e) => e.message).join('; ')}`); + } + if (!isNil(body.error_message)) { + throw new Error(`monday.com API error: ${body.error_message}`); + } + if (isNil(body.data)) { + throw new Error('monday.com API returned no data.'); + } + return body.data; +} + +function describeHttpError({ status, body }: { status: number; body: unknown }): string { + const detail = typeof body === 'string' ? body : JSON.stringify(body); + switch (status) { + case 401: + return `monday.com rejected the API token (401). Reconnect the monday.com connection. ${detail}`; + case 403: + return `The connected monday.com user lacks permission for this operation (403). ${detail}`; + case 404: + return `monday.com resource not found (404). ${detail}`; + case 429: + return `monday.com rate or complexity limit reached (429). Retry after a short wait. ${detail}`; + default: + return `monday.com request failed with status ${status}. ${detail}`; + } +} + +const MONDAY_FILE_API_URL = 'https://api.monday.com/v2/file'; + +type MondayGraphQLResponse = { + data?: T; + errors?: { message: string }[]; + error_message?: string; +}; diff --git a/packages/pieces/community/monday/src/lib/output-schemas.ts b/packages/pieces/community/monday/src/lib/output-schemas.ts index ead24ef8c359..065fca070e44 100644 --- a/packages/pieces/community/monday/src/lib/output-schemas.ts +++ b/packages/pieces/community/monday/src/lib/output-schemas.ts @@ -60,16 +60,8 @@ const specificColumnUpdatedEventFields: OutputSchema['fields'] = [ ]; export const specificColumnUpdatedTriggerOutputSchema: OutputSchema = { - itemLabel: '{event.pulseName}', fields: [ - { - key: 'items', - label: 'Events', - value: '', - listItems: [ - { key: 'event', label: 'Event', children: specificColumnUpdatedEventFields }, - ], - }, + { key: 'event', label: 'Event', children: specificColumnUpdatedEventFields }, ], }; @@ -90,16 +82,1043 @@ const newItemInBoardEventFields: OutputSchema['fields'] = [ ]; export const newItemInBoardTriggerOutputSchema: OutputSchema = { - itemLabel: '{event.pulseName}', + fields: [ + { key: 'event', label: 'Event', children: newItemInBoardEventFields }, + { key: 'columnValues', label: 'Column Values', dynamicKey: true }, + ], +}; + +const aiItemSummaryFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Item ID' }, + { key: 'name', label: 'Item Name' }, + { key: 'state', label: 'State' }, + { key: 'url', label: 'Item URL', format: 'url' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'group_id', label: 'Group ID' }, +]; + +const aiItemColumnValueField: OutputSchema['fields'][number] = { + key: 'column_values', + label: 'Column Values', + labelKey: 'column_id', + listItems: [ + { key: 'column_id', label: 'Column ID' }, + { key: 'type', label: 'Column Type' }, + { key: 'text', label: 'Text' }, + { key: 'value', label: 'Raw Value (JSON)' }, + ], +}; + +const aiItemWithValuesFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Item ID' }, + { key: 'name', label: 'Item Name' }, + { key: 'state', label: 'State' }, + { key: 'url', label: 'Item URL', format: 'url' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'group_id', label: 'Group ID' }, + { key: 'group_title', label: 'Group Title' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, + aiItemColumnValueField, +]; + +const aiItemSummarySchema: OutputSchema = { fields: aiItemSummaryFields }; + +const aiItemPageSchema: OutputSchema = { + fields: [ + { key: 'items', label: 'Items', labelKey: 'name', listItems: aiItemWithValuesFields }, + { key: 'count', label: 'Count', format: 'number' }, + { key: 'cursor', label: 'Next Page Cursor' }, + ], +}; + +export const getItemsActionOutputSchema: OutputSchema = { fields: [ { key: 'items', label: 'Items', - value: '', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Item ID' }, + { key: 'name', label: 'Item Name' }, + { key: 'state', label: 'State' }, + { key: 'url', label: 'Item URL', format: 'url' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'board_name', label: 'Board Name' }, + { key: 'group_id', label: 'Group ID' }, + { key: 'group_title', label: 'Group Title' }, + { key: 'parent_item_id', label: 'Parent Item ID' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, + aiItemColumnValueField, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listBoardItemsActionOutputSchema: OutputSchema = aiItemPageSchema; + +export const searchItemsByColumnValuesActionOutputSchema: OutputSchema = aiItemPageSchema; + +export const listSubitemsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'subitems', + label: 'Subitems', + labelKey: 'name', + listItems: [...aiItemWithValuesFields, { key: 'parent_item_id', label: 'Parent Item ID' }], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createBoardItemActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const setItemColumnValuesActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const renameItemActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const changeSimpleColumnValueActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const changeColumnValueActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const duplicateItemActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const moveItemToGroupActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const moveItemToBoardActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const archiveItemActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const changeItemPositionActionOutputSchema: OutputSchema = aiItemSummarySchema; + +export const createSubitemActionOutputSchema: OutputSchema = { + fields: [...aiItemSummaryFields, { key: 'parent_item_id', label: 'Parent Item ID' }], +}; + +export const clearItemUpdatesActionOutputSchema: OutputSchema = { + fields: [{ key: 'success', label: 'Success', format: 'boolean' }, ...aiItemSummaryFields], +}; + +export const deleteItemActionOutputSchema: OutputSchema = { + fields: [ + { key: 'success', label: 'Success', format: 'boolean' }, + { key: 'id', label: 'Deleted Item ID' }, + ], +}; + +export const updateAssetsOnItemActionOutputSchema: OutputSchema = { + fields: [ + { key: 'item_id', label: 'Item ID' }, + { + key: 'assets', + label: 'Assets', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Asset ID' }, + { key: 'name', label: 'File Name' }, + { key: 'url', label: 'File URL', format: 'url' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +const aiUpdateFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Update ID' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'body', label: 'Body', format: 'html' }, + { key: 'text_body', label: 'Text Body' }, + { key: 'creator_id', label: 'Creator ID' }, + { key: 'creator_name', label: 'Creator Name' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, +]; + +const aiUpdateLikeFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Update ID' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'liked', label: 'Liked', format: 'boolean' }, +]; + +const aiUpdatePinFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Update ID' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'pinned', label: 'Pinned', format: 'boolean' }, +]; + +const aiColumnFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Column ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'title', label: 'Title' }, + { key: 'description', label: 'Description' }, +]; + +const aiGroupFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Group ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'title', label: 'Title' }, + { key: 'color', label: 'Color' }, +]; + +export const postItemUpdateActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Update ID' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'parent_id', label: 'Parent Update ID' }, + { key: 'body', label: 'Body', format: 'html' }, + { key: 'text_body', label: 'Text Body' }, + { key: 'creator_id', label: 'Creator ID' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + ], +}; + +export const listItemUpdatesActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'updates', + label: 'Updates', + labelKey: 'text_body', + listItems: [ + ...aiUpdateFields, + { key: 'reply_count', label: 'Reply Count', format: 'number' }, + { + key: 'replies', + label: 'Replies', + labelKey: 'text_body', + listItems: [ + { key: 'id', label: 'Reply ID' }, + { key: 'body', label: 'Body', format: 'html' }, + { key: 'text_body', label: 'Text Body' }, + { key: 'creator_id', label: 'Creator ID' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + ], + }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listBoardUpdatesActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'updates', + label: 'Updates', + labelKey: 'text_body', + listItems: [{ key: 'board_id', label: 'Board ID' }, ...aiUpdateFields], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const editUpdateActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Update ID' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'body', label: 'Body', format: 'html' }, + { key: 'text_body', label: 'Text Body' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, + ], +}; + +export const deleteUpdateActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Update ID' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const likeUpdateActionOutputSchema: OutputSchema = { + fields: aiUpdateLikeFields, +}; + +export const unlikeUpdateActionOutputSchema: OutputSchema = { + fields: aiUpdateLikeFields, +}; + +export const pinUpdateActionOutputSchema: OutputSchema = { + fields: aiUpdatePinFields, +}; + +export const unpinUpdateActionOutputSchema: OutputSchema = { + fields: aiUpdatePinFields, +}; + +export const uploadFileActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Asset ID' }, + { key: 'name', label: 'File Name' }, + { key: 'url', label: 'File URL', format: 'url' }, + { key: 'file_extension', label: 'File Extension' }, + { key: 'file_size', label: 'File Size', format: 'filesize' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'target', label: 'Target' }, + { key: 'target_id', label: 'Target ID' }, + ], +}; + +export const getAssetsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'assets', + label: 'Assets', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Asset ID' }, + { key: 'name', label: 'File Name' }, + { key: 'url', label: 'File URL', format: 'url' }, + { key: 'public_url', label: 'Public URL', format: 'url' }, + { key: 'thumbnail_url', label: 'Thumbnail URL', format: 'image' }, + { key: 'file_extension', label: 'File Extension' }, + { key: 'file_size', label: 'File Size', format: 'filesize' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'uploaded_by_id', label: 'Uploaded By ID' }, + { key: 'uploaded_by_name', label: 'Uploaded By Name' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listColumnsActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { + key: 'columns', + label: 'Columns', + labelKey: 'title', listItems: [ - { key: 'event', label: 'Event', children: newItemInBoardEventFields }, - { key: 'columnValues', label: 'Column Values', dynamicKey: true }, + { key: 'id', label: 'Column ID' }, + { key: 'title', label: 'Title' }, + { key: 'type', label: 'Type' }, + { key: 'description', label: 'Description' }, + { key: 'settings', label: 'Settings (JSON)' }, + { key: 'archived', label: 'Archived', format: 'boolean' }, + { key: 'width', label: 'Width', format: 'number' }, ], }, + { key: 'count', label: 'Count', format: 'number' }, ], }; + +export const addBoardColumnActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Column ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'title', label: 'Title' }, + { key: 'type', label: 'Type' }, + { key: 'description', label: 'Description' }, + ], +}; + +export const updateColumnActionOutputSchema: OutputSchema = { + fields: aiColumnFields, +}; + +export const deleteColumnActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Column ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const getColumnTypeSchemaActionOutputSchema: OutputSchema = { + fields: [ + { key: 'column_type', label: 'Column Type' }, + { key: 'schema', label: 'JSON Schema' }, + ], +}; + +export const listGroupsActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { + key: 'groups', + label: 'Groups', + labelKey: 'title', + listItems: [ + { key: 'id', label: 'Group ID' }, + { key: 'title', label: 'Title' }, + { key: 'color', label: 'Color' }, + { key: 'archived', label: 'Archived', format: 'boolean' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const addBoardGroupActionOutputSchema: OutputSchema = { + fields: aiGroupFields, +}; + +export const updateGroupActionOutputSchema: OutputSchema = { + fields: [...aiGroupFields, { key: 'updated_attributes', label: 'Updated Attributes' }], +}; + +export const deleteGroupActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Group ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const archiveGroupActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Group ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'title', label: 'Title' }, + { key: 'archived', label: 'Archived', format: 'boolean' }, + ], +}; + +export const duplicateGroupActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'New Group ID' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'source_group_id', label: 'Source Group ID' }, + { key: 'title', label: 'Title' }, + { key: 'color', label: 'Color' }, + ], +}; + +const aiBoardFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Board ID' }, + { key: 'name', label: 'Board Name' }, + { key: 'description', label: 'Description' }, + { key: 'state', label: 'State' }, + { key: 'board_kind', label: 'Board Kind' }, + { key: 'url', label: 'Board URL', format: 'url' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'folder_id', label: 'Folder ID' }, +]; + +const aiBoardStateFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Board ID' }, + { key: 'name', label: 'Board Name' }, + { key: 'state', label: 'State' }, +]; + +const aiBoardTeamFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Team ID' }, + { key: 'name', label: 'Team Name' }, +]; + +const aiBoardRemovedUserFields: OutputSchema['fields'] = [ + { key: 'id', label: 'User ID' }, + { key: 'name', label: 'User Name' }, +]; + +const aiWorkspaceFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Workspace ID' }, + { key: 'name', label: 'Workspace Name' }, + { key: 'kind', label: 'Kind' }, + { key: 'description', label: 'Description' }, + { key: 'state', label: 'State' }, +]; + +const aiFolderFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Folder ID' }, + { key: 'name', label: 'Folder Name' }, + { key: 'color', label: 'Color' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'parent_folder_id', label: 'Parent Folder ID' }, +]; + +export const listBoardsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'boards', + label: 'Boards', + labelKey: 'name', + listItems: [ + ...aiBoardFields, + { key: 'items_count', label: 'Items Count', format: 'number' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, + { key: 'owner_ids', label: 'Owner IDs' }, + { key: 'owner_names', label: 'Owner Names' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createBoardActionOutputSchema: OutputSchema = { + fields: aiBoardFields, +}; + +export const updateBoardActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'attribute', label: 'Updated Attribute' }, + { key: 'new_value', label: 'New Value' }, + { key: 'success', label: 'Success', format: 'boolean' }, + ], +}; + +export const duplicateBoardActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'New Board ID' }, + { key: 'name', label: 'New Board Name' }, + { key: 'url', label: 'New Board URL', format: 'url' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'source_board_id', label: 'Source Board ID' }, + ], +}; + +export const archiveBoardActionOutputSchema: OutputSchema = { + fields: aiBoardStateFields, +}; + +export const deleteBoardActionOutputSchema: OutputSchema = { + fields: aiBoardStateFields, +}; + +export const updateBoardHierarchyActionOutputSchema: OutputSchema = { + fields: [ + { key: 'success', label: 'Success', format: 'boolean' }, + { key: 'message', label: 'Message' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'folder_id', label: 'Folder ID' }, + ], +}; + +export const getBoardActivityLogsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'activity_logs', + label: 'Activity Logs', + labelKey: 'event', + listItems: [ + { key: 'id', label: 'Log ID' }, + { key: 'event', label: 'Event' }, + { key: 'entity', label: 'Entity' }, + { key: 'user_id', label: 'User ID' }, + { key: 'account_id', label: 'Account ID' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'data', label: 'Event Data' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listBoardViewsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'views', + label: 'Views', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'View ID' }, + { key: 'name', label: 'View Name' }, + { key: 'type', label: 'View Type' }, + { key: 'access_level', label: 'Access Level' }, + { key: 'source_view_id', label: 'Source View ID' }, + { key: 'board_id', label: 'Board ID' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const addUsersToBoardActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'role', label: 'Role' }, + { + key: 'users', + label: 'Users', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'User ID' }, + { key: 'name', label: 'User Name' }, + { key: 'email', label: 'Email', format: 'email' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const removeBoardSubscribersActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'removed_users', label: 'Removed Users', labelKey: 'name', listItems: aiBoardRemovedUserFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const addTeamsToBoardActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'teams', label: 'Teams', labelKey: 'name', listItems: aiBoardTeamFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const removeTeamsFromBoardActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'removed_teams', label: 'Removed Teams', labelKey: 'name', listItems: aiBoardTeamFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listWorkspacesActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'workspaces', + label: 'Workspaces', + labelKey: 'name', + listItems: [ + ...aiWorkspaceFields, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'is_default_workspace', label: 'Is Default Workspace', format: 'boolean' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createWorkspaceActionOutputSchema: OutputSchema = { + fields: [...aiWorkspaceFields, { key: 'created_at', label: 'Created At', format: 'datetime' }], +}; + +export const updateWorkspaceActionOutputSchema: OutputSchema = { + fields: aiWorkspaceFields, +}; + +export const deleteWorkspaceActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Workspace ID' }, + { key: 'name', label: 'Workspace Name' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const addUsersToWorkspaceActionOutputSchema: OutputSchema = { + fields: [ + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'role', label: 'Role' }, + { key: 'users', label: 'Users', labelKey: 'name', listItems: aiBoardRemovedUserFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const deleteUsersFromWorkspaceActionOutputSchema: OutputSchema = { + fields: [ + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'removed_users', label: 'Removed Users', labelKey: 'name', listItems: aiBoardRemovedUserFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const addTeamsToWorkspaceActionOutputSchema: OutputSchema = { + fields: [ + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'teams', label: 'Teams', labelKey: 'name', listItems: aiBoardTeamFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const deleteTeamsFromWorkspaceActionOutputSchema: OutputSchema = { + fields: [ + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'removed_teams', label: 'Removed Teams', labelKey: 'name', listItems: aiBoardTeamFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listFoldersActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'folders', + label: 'Folders', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Folder ID' }, + { key: 'name', label: 'Folder Name' }, + { key: 'color', label: 'Color' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'owner_id', label: 'Owner ID' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'workspace_name', label: 'Workspace Name' }, + { key: 'parent_folder_id', label: 'Parent Folder ID' }, + { key: 'parent_folder_name', label: 'Parent Folder Name' }, + { key: 'sub_folder_ids', label: 'Sub-folder IDs' }, + { key: 'board_ids', label: 'Board IDs' }, + { key: 'board_names', label: 'Board Names' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createFolderActionOutputSchema: OutputSchema = { + fields: [...aiFolderFields, { key: 'created_at', label: 'Created At', format: 'datetime' }], +}; + +export const updateFolderActionOutputSchema: OutputSchema = { + fields: aiFolderFields, +}; + +export const deleteFolderActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Folder ID' }, + { key: 'name', label: 'Folder Name' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +const aiDocBaseFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Doc ID' }, + { key: 'object_id', label: 'Doc Object ID' }, + { key: 'name', label: 'Name' }, + { key: 'url', label: 'URL', format: 'url' }, + { key: 'workspace_id', label: 'Workspace ID' }, +]; + +const aiDocBlockFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Block ID' }, + { key: 'type', label: 'Type' }, + { key: 'position', label: 'Position', format: 'number' }, + { key: 'parent_block_id', label: 'Parent Block ID' }, + { key: 'content', label: 'Content (JSON)' }, +]; + +const aiUserFields: OutputSchema['fields'] = [ + { key: 'id', label: 'User ID' }, + { key: 'name', label: 'Name' }, + { key: 'email', label: 'Email', format: 'email' }, + { key: 'title', label: 'Title' }, + { key: 'kind', label: 'Kind' }, + { key: 'status', label: 'Status' }, + { key: 'url', label: 'Profile URL', format: 'url' }, + { key: 'time_zone', label: 'Time Zone' }, + { key: 'country_code', label: 'Country Code' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, +]; + +const aiMiscTagFields: OutputSchema['fields'] = [ + { key: 'id', label: 'Tag ID' }, + { key: 'name', label: 'Name' }, + { key: 'color', label: 'Color' }, +]; + +const aiMiscMuteSettingFields: OutputSchema['fields'] = [ + { key: 'board_id', label: 'Board ID' }, + { key: 'mute_state', label: 'Mute State' }, +]; + +export const listDocsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'docs', + label: 'Docs', + labelKey: 'name', + listItems: [ + ...aiDocBaseFields, + { key: 'doc_kind', label: 'Doc Kind' }, + { key: 'folder_id', label: 'Folder ID' }, + { key: 'created_by_id', label: 'Created By ID' }, + { key: 'created_by_name', label: 'Created By' }, + { key: 'created_at', label: 'Created At', format: 'date' }, + { key: 'updated_at', label: 'Updated At', format: 'date' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createDocActionOutputSchema: OutputSchema = { + fields: aiDocBaseFields, +}; + +export const getDocBlocksActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { + key: 'blocks', + label: 'Blocks', + labelKey: 'type', + listItems: [ + ...aiDocBlockFields, + { key: 'created_by_id', label: 'Created By ID' }, + { key: 'created_at', label: 'Created At', format: 'date' }, + { key: 'updated_at', label: 'Updated At', format: 'date' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createDocBlockActionOutputSchema: OutputSchema = { + fields: [...aiDocBlockFields, { key: 'doc_id', label: 'Doc ID' }], +}; + +export const updateDocBlockActionOutputSchema: OutputSchema = { + fields: aiDocBlockFields, +}; + +export const deleteDocBlockActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Block ID' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const appendMarkdownToDocActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { key: 'success', label: 'Success', format: 'boolean' }, + { key: 'block_ids', label: 'New Block IDs' }, + { key: 'block_count', label: 'Block Count', format: 'number' }, + ], +}; + +export const importDocFromHtmlActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { key: 'success', label: 'Success', format: 'boolean' }, + ], +}; + +export const exportDocAsMarkdownActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { key: 'markdown', label: 'Markdown' }, + ], +}; + +export const renameDocActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { key: 'name', label: 'Name' }, + ], +}; + +export const duplicateDocActionOutputSchema: OutputSchema = { + fields: [ + { key: 'source_doc_id', label: 'Source Doc ID' }, + { key: 'new_doc_id', label: 'New Doc Object ID' }, + ], +}; + +export const deleteDocActionOutputSchema: OutputSchema = { + fields: [ + { key: 'doc_id', label: 'Doc ID' }, + { key: 'deleted', label: 'Deleted', format: 'boolean' }, + ], +}; + +export const getMeActionOutputSchema: OutputSchema = { + fields: [ + ...aiUserFields, + { key: 'photo_url', label: 'Photo', format: 'image' }, + { key: 'account_id', label: 'Account ID' }, + { key: 'account_name', label: 'Account Name' }, + { key: 'account_slug', label: 'Account Slug' }, + ], +}; + +export const getAccountActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Account ID' }, + { key: 'name', label: 'Name' }, + { key: 'slug', label: 'Slug' }, + { key: 'tier', label: 'Tier' }, + { key: 'country_code', label: 'Country Code' }, + { key: 'active_members_count', label: 'Active Members', format: 'number' }, + { key: 'first_day_of_the_week', label: 'First Day of the Week' }, + { key: 'is_during_trial', label: 'In Trial', format: 'boolean' }, + { key: 'is_trial_expired', label: 'Trial Expired', format: 'boolean' }, + { key: 'show_timeline_weekends', label: 'Show Timeline Weekends', format: 'boolean' }, + { key: 'plan_tier', label: 'Plan Tier' }, + { key: 'plan_period', label: 'Plan Period' }, + { key: 'plan_max_users', label: 'Plan Max Users', format: 'number' }, + { key: 'plan_version', label: 'Plan Version' }, + { key: 'products', label: 'Products' }, + ], +}; + +export const listUsersActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'users', + label: 'Users', + labelKey: 'name', + listItems: [ + ...aiUserFields, + { key: 'location', label: 'Location' }, + { key: 'phone', label: 'Phone' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listTeamsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'teams', + label: 'Teams', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'Team ID' }, + { key: 'name', label: 'Name' }, + { key: 'picture_url', label: 'Picture', format: 'image' }, + { key: 'member_count', label: 'Member Count', format: 'number' }, + { key: 'member_ids', label: 'Member IDs' }, + { key: 'member_names', label: 'Member Names' }, + { key: 'member_emails', label: 'Member Emails' }, + { key: 'owner_ids', label: 'Owner IDs' }, + { key: 'owner_names', label: 'Owner Names' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const listTagsActionOutputSchema: OutputSchema = { + fields: [ + { key: 'tags', label: 'Tags', labelKey: 'name', listItems: aiMiscTagFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const createOrGetTagActionOutputSchema: OutputSchema = { + fields: aiMiscTagFields, +}; + +export const sendNotificationActionOutputSchema: OutputSchema = { + fields: [ + { key: 'sent', label: 'Sent', format: 'boolean' }, + { key: 'user_id', label: 'User ID' }, + { key: 'target_type', label: 'Target Type' }, + { key: 'target_id', label: 'Target ID' }, + { key: 'text', label: 'Text' }, + ], +}; + +export const listNotificationsActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'notifications', + label: 'Notifications', + labelKey: 'text', + listItems: [ + { key: 'id', label: 'Notification ID' }, + { key: 'title', label: 'Title' }, + { key: 'text', label: 'Text' }, + { key: 'read', label: 'Read', format: 'boolean' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'board_name', label: 'Board Name' }, + { key: 'item_id', label: 'Item ID' }, + { key: 'item_name', label: 'Item Name' }, + { key: 'update_id', label: 'Update ID' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + { key: 'next_cursor', label: 'Next Cursor' }, + ], +}; + +export const searchActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'results', + label: 'Results', + labelKey: 'name', + listItems: [ + { key: 'id', label: 'ID' }, + { key: 'entity_type', label: 'Entity Type' }, + { key: 'name', label: 'Name' }, + { key: 'url', label: 'URL Path' }, + { key: 'board_id', label: 'Board ID' }, + { key: 'workspace_id', label: 'Workspace ID' }, + { key: 'description', label: 'Description' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const getFavoritesActionOutputSchema: OutputSchema = { + fields: [ + { + key: 'favorites', + label: 'Favorites', + labelKey: 'object_type', + listItems: [ + { key: 'id', label: 'Favorite ID' }, + { key: 'object_id', label: 'Object ID' }, + { key: 'object_type', label: 'Object Type' }, + { key: 'folder_id', label: 'Folder ID' }, + { key: 'position', label: 'Position', format: 'number' }, + { key: 'created_at', label: 'Created At', format: 'datetime' }, + { key: 'updated_at', label: 'Updated At', format: 'datetime' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const getFormActionOutputSchema: OutputSchema = { + fields: [ + { key: 'id', label: 'Form ID' }, + { key: 'title', label: 'Title' }, + { key: 'description', label: 'Description' }, + { key: 'active', label: 'Active', format: 'boolean' }, + { key: 'is_anonymous', label: 'Anonymous', format: 'boolean' }, + { key: 'owner_id', label: 'Owner ID' }, + { key: 'question_count', label: 'Question Count', format: 'number' }, + { + key: 'questions', + label: 'Questions', + labelKey: 'title', + listItems: [ + { key: 'id', label: 'Question ID' }, + { key: 'title', label: 'Title' }, + { key: 'type', label: 'Type' }, + { key: 'required', label: 'Required', format: 'boolean' }, + { key: 'visible', label: 'Visible', format: 'boolean' }, + ], + }, + ], +}; + +export const aggregateBoardDataActionOutputSchema: OutputSchema = { + fields: [ + { key: 'board_id', label: 'Board ID' }, + { key: 'function', label: 'Function' }, + { key: 'column_id', label: 'Column ID' }, + { key: 'group_by_column_id', label: 'Group By Column ID' }, + { + key: 'rows', + label: 'Rows', + labelKey: 'group_value', + listItems: [ + { key: 'group_value', label: 'Group Value' }, + { key: 'value', label: 'Value', format: 'number' }, + ], + }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const getBoardMuteSettingsActionOutputSchema: OutputSchema = { + fields: [ + { key: 'settings', label: 'Settings', labelKey: 'board_id', listItems: aiMiscMuteSettingFields }, + { key: 'count', label: 'Count', format: 'number' }, + ], +}; + +export const updateBoardMuteSettingsActionOutputSchema: OutputSchema = { + fields: [...aiMiscMuteSettingFields, { key: 'enabled', label: 'Enabled Notifications' }], +};