From f91a1c6eb943dc569311e1e3d0af5ce2d8b41179 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Mon, 23 Feb 2026 17:19:01 +0000 Subject: [PATCH 01/12] feat: add support for update report definition endpoint --- CHANGELOG.md | 4 + lib/reports/index.ts | 10 + lib/reports/types.ts | 332 ++++++++++++++++++ lib/utils/httpRequestor.js | 5 +- .../reports/update_report_definition.spec.ts | 125 +++++++ 5 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 test/mock-api/reports/update_report_definition.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b8b294..020b78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased +- Added support for PATCH /2.0/reports{reportId}/definition endpoint +- WireMock integration tests for contract testing for PATCH /2.0/reports{reportId}/definition endpoint + ## [4.7.1] - 2026-02-12 ### Added - WiremMock integration tests for contract testing for GET /2.0/users/{userId}/plans and GET /2.0/users endpoints diff --git a/lib/reports/index.ts b/lib/reports/index.ts index b4fd0bb..ecdbc8b 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -16,7 +16,9 @@ import type { ReportPublish, SetReportPublishStatusOptions, SetReportPublishStatusResponse, + UpdateReportDefinitionOptions, } from './types'; +import type { BaseResponseStatus } from '../types/BaseResponseStatus'; import * as constants from '../utils/constants'; export function create(options: CreateOptions): ReportsApi { @@ -87,6 +89,13 @@ export function create(options: CreateOptions): ReportsApi { return requestor.put({ ...optionsToSend, ...urlOptions, ...putOptions }, callback); }; + const updateReportDefinition = ( + patchOptions: UpdateReportDefinitionOptions, + callback?: RequestCallback): Promise => { + const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition' }; + return requestor.patch({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); + } + return { listReports, getReport, @@ -95,6 +104,7 @@ export function create(options: CreateOptions): ReportsApi { getReportAsCSV, getReportPublishStatus, setReportPublishStatus, + updateReportDefinition, ...shares.create(options), }; } diff --git a/lib/reports/types.ts b/lib/reports/types.ts index 891cdb3..726cccc 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -175,6 +175,52 @@ export interface ReportsApi { options: SetReportPublishStatusOptions, callback?: RequestCallback ) => Promise; + + /** + * Update a Report's definition based on the specified ID + * + * **Note:** This endpoint supports partial updates **only on root level** properties of the report definition, + * such as `filters`, `groupingCriteria` and `aggregationCriteria`. For example, you can update the report's filters + * without affecting its grouping criteria. However, nested properties within these objects, + * such as a specific filter or grouping criterion, cannot be updated individually and + * require a full replacement of the respective section. + * + * @param options - {@link UpdateReportDefinitionOptions} - Configuration options for the request + * @param callback - {@link RequestCallback}\<{@link BaseResponseStatus}\> - Optional callback function + * @returns Promise\<{@link BaseResponseStatus}\> + * + * @remarks + * It mirrors to the following Smartsheet REST API method: `PATCH /reports/{reportId}/definition` + * + * @example + * ```typescript + * // Update report filters + * const result = await client.reports.updateReportDefinition({ + * reportId: 4583173393803140, + * body: { + * filters: { + * operator: 'AND', + * criteria: [ + * { + * column: { title: 'Status', type: 'PICKLIST' }, + * operator: 'EQUAL', + * values: ['Complete'] + * }, + * { + * column: { title: 'Priority', type: 'TEXT_NUMBER' }, + * operator: 'IS_ONE_OF', + * values: ['High', 'Critical'] + * } + * ] + * } + * } + * }); + * ``` + */ + updateReportDefinition: ( + options: UpdateReportDefinitionOptions, + callback?: RequestCallback + ) => Promise; } // ============================================================================ @@ -722,3 +768,289 @@ export interface SetReportPublishStatusResponse extends BaseResponseStatus { failedItems?: FailedItem[]; result: ReportPublish; } + +// ============================================================================ +// Update Report Definition +// ============================================================================ + +export interface UpdateReportDefinitionOptions extends RequestOptions { + /** + * reportID of the report being accessed. + */ + reportId: number; +} + +// ============================================================================ +// Report Definition Enums +// ============================================================================ + +/** + * Boolean operator for filter expressions. + */ +export enum ReportFilterOperator { + AND = 'AND', + OR = 'OR', +} + +/** + * Filter condition operators. + */ +export enum ReportFilterConditionOperator { + EQUAL = 'EQUAL', + NOT_EQUAL = 'NOT_EQUAL', + GREATER_THAN = 'GREATER_THAN', + LESS_THAN = 'LESS_THAN', + CONTAINS = 'CONTAINS', + BETWEEN = 'BETWEEN', + TODAY = 'TODAY', + PAST = 'PAST', + FUTURE = 'FUTURE', + LAST_N_DAYS = 'LAST_N_DAYS', + NEXT_N_DAYS = 'NEXT_N_DAYS', + IS_BLANK = 'IS_BLANK', + IS_NOT_BLANK = 'IS_NOT_BLANK', + IS_NUMBER = 'IS_NUMBER', + IS_NOT_NUMBER = 'IS_NOT_NUMBER', + IS_DATE = 'IS_DATE', + IS_NOT_DATE = 'IS_NOT_DATE', + IS_CHECKED = 'IS_CHECKED', + IS_UNCHECKED = 'IS_UNCHECKED', + IS_ONE_OF = 'IS_ONE_OF', + IS_NOT_ONE_OF = 'IS_NOT_ONE_OF', + LESS_THAN_OR_EQUAL = 'LESS_THAN_OR_EQUAL', + GREATER_THAN_OR_EQUAL = 'GREATER_THAN_OR_EQUAL', + DOES_NOT_CONTAIN = 'DOES_NOT_CONTAIN', + NOT_BETWEEN = 'NOT_BETWEEN', + NOT_TODAY = 'NOT_TODAY', + NOT_PAST = 'NOT_PAST', + NOT_FUTURE = 'NOT_FUTURE', + NOT_LAST_N_DAYS = 'NOT_LAST_N_DAYS', + NOT_NEXT_N_DAYS = 'NOT_NEXT_N_DAYS', + HAS_ANY_OF = 'HAS_ANY_OF', + HAS_NONE_OF = 'HAS_NONE_OF', + HAS_ALL_OF = 'HAS_ALL_OF', + NOT_ALL_OF = 'NOT_ALL_OF', + MULTI_IS_EQUAL = 'MULTI_IS_EQUAL', + MULTI_IS_NOT_EQUAL = 'MULTI_IS_NOT_EQUAL', +} + +/** + * Sorting direction for grouping and sorting criteria. + */ +export enum ReportSortingDirection { + ASCENDING = 'ASCENDING', + DESCENDING = 'DESCENDING', +} + +/** + * Aggregation types for report aggregation criteria. + */ +export enum ReportAggregationType { + SUM = 'SUM', + AVG = 'AVG', + MIN = 'MIN', + MAX = 'MAX', + COUNT = 'COUNT', + FIRST = 'FIRST', + LAST = 'LAST', +} + +/** + * Column types for report column identifiers. + */ +export enum ReportColumnType { + ABSTRACT_DATETIME = 'ABSTRACT_DATETIME', + CHECKBOX = 'CHECKBOX', + CONTACT_LIST = 'CONTACT_LIST', + DATE = 'DATE', + DATETIME = 'DATETIME', + DURATION = 'DURATION', + MULTI_CONTACT_LIST = 'MULTI_CONTACT_LIST', + MULTI_PICKLIST = 'MULTI_PICKLIST', + PICKLIST = 'PICKLIST', + PREDECESSOR = 'PREDECESSOR', + TEXT_NUMBER = 'TEXT_NUMBER', +} + +/** + * System column types for report column identifiers. + */ +export enum ReportSystemColumnType { + AUTO_NUMBER = 'AUTO_NUMBER', + CREATED_BY = 'CREATED_BY', + CREATED_DATE = 'CREATED_DATE', + MODIFIED_BY = 'MODIFIED_BY', + MODIFIED_DATE = 'MODIFIED_DATE', + SHEET_NAME = 'SHEET_NAME', +} + +// ============================================================================ +// Report Definition Types +// ============================================================================ + +/** + * The report definition contains filters, grouping and sorting properties of the report. + * + * Note: When groupingCriteria is defined the primary column of the report will move to the index 0 when it is first rendered by the app. + */ +export interface ReportDefinition { + /** + * Report filter expression. + */ + filters?: ReportFilterExpression; + /** + * List of report grouping criteria. + */ + groupingCriteria?: ReportGroupingCriterion[]; + /** + * List of report aggregation criteria. + */ + aggregationCriteria?: ReportAggregationCriterion[]; + /** + * List of report sorting criteria. + */ + sortingCriteria?: ReportSortingCriterion[]; +} + +/** + * Report filter expression. It is a recursive object that allows at most 3 levels. + * + * At least one of `criteria` or `nestedCriteria` has to be provided in addition to `operator`. + * + * Example: + * ```json + * { + * "operator": "OR", + * "nestedCriteria": [ + * { + * "operator": "AND", + * "nestedCriteria": [], + * "criteria": [ + * { + * "column": { "title": "Price", "type": "TEXT_NUMBER" }, + * "operator": "GREATER_THAN", + * "values": ["11"] + * } + * ] + * } + * ], + * "criteria": [] + * } + * ``` + */ +export interface ReportFilterExpression { + /** + * The boolean operator that will be applied to the list of `criteria` and `nestedCriteria`. + */ + operator: ReportFilterOperator | string; + /** + * A recursive list of report filter expressions. Each item will be joined to the filter expression with the AND/OR operator defined on this level. + */ + nestedCriteria?: ReportFilterExpression[]; + /** + * Criteria objects specifying custom criteria against which to match cell values. Each item will be joined to the filter expression with the AND/OR operator defined on this level. + */ + criteria?: ReportFilterCriterion[]; +} + +/** + * Represents a report filter criterion. + */ +export interface ReportFilterCriterion { + /** + * Object used to match a sheet column for a report. + */ + column: ReportColumnIdentifier; + /** + * Condition operator. + */ + operator: ReportFilterConditionOperator | string; + /** + * List of filter values. + */ + values?: string[]; +} + +/** + * Report grouping criterion. + */ +export interface ReportGroupingCriterion { + /** + * Object used to match a sheet column for a report. + */ + column: ReportColumnIdentifier; + /** + * Sorting direction within the group. + */ + sortingDirection: ReportSortingDirection | string; + /** + * Indicates whether the group is expanded in the UI. + */ + isExpanded?: boolean; +} + +/** + * Report aggregation criterion. + */ +export interface ReportAggregationCriterion { + /** + * Object used to match a sheet column for a report. + */ + column: ReportColumnIdentifier; + /** + * Type of aggregation. + */ + aggregationType: ReportAggregationType | string; + /** + * Indicates whether the group is expanded in the UI. + */ + isExpanded?: boolean; +} + +/** + * Report sorting criterion. + */ +export interface ReportSortingCriterion { + /** + * Object used to match a sheet column for a report. + */ + column: ReportColumnIdentifier; + /** + * Sorting direction. + */ + sortingDirection: ReportSortingDirection | string; + /** + * Force null values to the bottom of the sorted list. + */ + forceNullsToBottom?: boolean; +} + +/** + * Object used to match a sheet column for a report. One of [`type`, `systemColumnType`] or [`primary=true`] is required. + * + * `systemColumnType` should be specified if you want to match a system column. Use `primary=true` to match primary columns. When matching primary columns `title` can be used to customize primary column name in the rendered report. + * + * **Note:** Columns in the report are matched by the combination of `title` and `type` (and `systemColumnType` if specified). + * + * **Note:** `symbol` is not used for matching and as a result `CHECKBOX` or `PICKLIST` columns with different symbols (from different sheets) can be combined into the same column in the report. You cannot combine `CHECKBOX` with `PICKLIST` into the same column in the report because they are different types. + */ +export interface ReportColumnIdentifier { + /** + * Column title to be matched from the source sheets. + * + * Note: If `primary` is **true** then this property can be used to customize the primary column title. + */ + title?: string; + /** + * Column type to be matched from the source sheets. See Column Types. + */ + type?: ReportColumnType | string; + /** + * Column type to be matched from the source sheets. See System Columns. Additionally `SHEET_NAME` is available as an extra option for reports. + */ + systemColumnType?: ReportSystemColumnType | string; + /** + * Indicates if the matched column is primary. + */ + primary?: boolean; +} diff --git a/lib/utils/httpRequestor.js b/lib/utils/httpRequestor.js index 688f9ad..4c08e73 100644 --- a/lib/utils/httpRequestor.js +++ b/lib/utils/httpRequestor.js @@ -96,6 +96,8 @@ export function create(requestorConfig) { const put = (options, callback) => methodRequest(options, request.put, 'PUT', callback, options.body); + const patch = (options, callback) => methodRequest(options, request.patch, 'PATCH', callback, options.body); + const methodRequest = (options, method, methodName, callback, body) => { const baseRequestOptions = { url: buildUrl(options), @@ -146,7 +148,7 @@ export function create(requestorConfig) { }; const methodHandler = (url, method, methodName, requestOptions, body) => { - if (methodName === 'POST' || methodName === 'PUT') { + if (methodName === 'POST' || methodName === 'PUT' || methodName === 'PATCH') { return method(url, body, requestOptions); } @@ -188,6 +190,7 @@ export function create(requestorConfig) { put: put, post: post, postFile: postFile, + patch: patch, delete: deleteFunc, internal: { buildHeaders: buildHeaders, diff --git a/test/mock-api/reports/update_report_definition.spec.ts b/test/mock-api/reports/update_report_definition.spec.ts new file mode 100644 index 0000000..5ec5bfe --- /dev/null +++ b/test/mock-api/reports/update_report_definition.spec.ts @@ -0,0 +1,125 @@ +import crypto from 'crypto'; +import { createClient, findWireMockRequest } from '../utils/utils'; +import { expect } from '@jest/globals'; +import { + TEST_REPORT_ID, + TEST_SUCCESS_MESSAGE, + TEST_SUCCESS_RESULT_CODE, + ERROR_500_STATUS_CODE, + ERROR_500_MESSAGE, + ERROR_400_STATUS_CODE, + ERROR_400_MESSAGE +} from './common_test_constants'; + +describe('Reports - updateReportDefinition endpoint tests', () => { + const client = createClient(); + + const testFilterOnlyBody = { + filters: { + operator: 'AND', + criteria: [ + { + column: { title: 'Status', type: 'PICKLIST' }, + operator: 'EQUAL', + values: ['Complete'] + }, + { + column: { title: 'Priority', type: 'TEXT_NUMBER' }, + operator: 'IS_ONE_OF', + values: ['High', 'Critical'] + } + ] + } + }; + + it('updateReportDefinition generated url is correct', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/reports/update-report-definition/all-response-body-properties' + } + }; + await client.reports.updateReportDefinition(options); + const matchedRequest = await findWireMockRequest(requestId); + const parsedUrl = new URL(matchedRequest.absoluteUrl); + expect(parsedUrl.pathname).toEqual(`/2.0/reports/${TEST_REPORT_ID}/definition`); + }); + + it('updateReportDefinition uses PATCH method', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/reports/update-report-definition/all-response-body-properties' + } + }; + await client.reports.updateReportDefinition(options); + const matchedRequest = await findWireMockRequest(requestId); + expect(matchedRequest.method).toEqual('PATCH'); + }); + + it('updateReportDefinition request body', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/reports/update-report-definition/all-response-body-properties' + } + }; + const response = await client.reports.updateReportDefinition(options); + const matchedRequest = await findWireMockRequest(requestId); + + expect(response).toEqual({ + message: TEST_SUCCESS_MESSAGE, + resultCode: TEST_SUCCESS_RESULT_CODE + }); + + const body = JSON.parse(matchedRequest.body); + expect(body).toEqual(testFilterOnlyBody); + }); + + it('updateReportDefinition error 500 response', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/500-response' + } + }; + try { + await client.reports.updateReportDefinition(options); + expect(true).toBe(false); // Expected an error to be thrown + } catch (error) { + expect(error.statusCode).toBe(ERROR_500_STATUS_CODE); + expect(error.message).toBe(ERROR_500_MESSAGE); + } + }); + + it('updateReportDefinition error 400 response', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/errors/400-response' + } + }; + try { + await client.reports.updateReportDefinition(options); + expect(true).toBe(false); // Expected an error to be thrown + } catch (error) { + expect(error.statusCode).toBe(ERROR_400_STATUS_CODE); + expect(error.message).toBe(ERROR_400_MESSAGE); + } + }); +}); From 4d69e798fee43913ed8d52663d87f02cb15e788c Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Mon, 23 Feb 2026 17:22:34 +0000 Subject: [PATCH 02/12] feat: add support for update report definition endpoint fix failing test --- test/functional/client.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/client.spec.ts b/test/functional/client.spec.ts index 65375a8..59da546 100644 --- a/test/functional/client.spec.ts +++ b/test/functional/client.spec.ts @@ -180,7 +180,7 @@ describe('Client Unit Tests', () => { describe('#reports', () => { it('should have reports object', () => { expect(smartsheet).toHaveProperty('reports'); - expect(Object.keys(smartsheet.reports)).toHaveLength(12); + expect(Object.keys(smartsheet.reports)).toHaveLength(13); }); it('should have get methods', () => { From c7e7c14fe780a7525a268d21e85531c3eb62117c Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Tue, 24 Feb 2026 09:25:14 +0000 Subject: [PATCH 03/12] feat: add support for update report definition endpoint fix prettier issues --- lib/reports/index.ts | 5 +++-- lib/reports/types.ts | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/reports/index.ts b/lib/reports/index.ts index ecdbc8b..5144935 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -91,10 +91,11 @@ export function create(options: CreateOptions): ReportsApi { const updateReportDefinition = ( patchOptions: UpdateReportDefinitionOptions, - callback?: RequestCallback): Promise => { + callback?: RequestCallback + ): Promise => { const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition' }; return requestor.patch({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); - } + }; return { listReports, diff --git a/lib/reports/types.ts b/lib/reports/types.ts index 726cccc..e9032a2 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -176,13 +176,13 @@ export interface ReportsApi { callback?: RequestCallback ) => Promise; - /** + /** * Update a Report's definition based on the specified ID - * - * **Note:** This endpoint supports partial updates **only on root level** properties of the report definition, - * such as `filters`, `groupingCriteria` and `aggregationCriteria`. For example, you can update the report's filters - * without affecting its grouping criteria. However, nested properties within these objects, - * such as a specific filter or grouping criterion, cannot be updated individually and + * + * **Note:** This endpoint supports partial updates **only on root level** properties of the report definition, + * such as `filters`, `groupingCriteria` and `aggregationCriteria`. For example, you can update the report's filters + * without affecting its grouping criteria. However, nested properties within these objects, + * such as a specific filter or grouping criterion, cannot be updated individually and * require a full replacement of the respective section. * * @param options - {@link UpdateReportDefinitionOptions} - Configuration options for the request @@ -218,7 +218,7 @@ export interface ReportsApi { * ``` */ updateReportDefinition: ( - options: UpdateReportDefinitionOptions, + options: UpdateReportDefinitionOptions, callback?: RequestCallback ) => Promise; } From ed3951118b8d592de8758929f191b4cc5324ca48 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Tue, 24 Feb 2026 12:05:34 +0000 Subject: [PATCH 04/12] chore: minor updates from ai review --- CHANGELOG.md | 4 ++-- test/mock-api/reports/update_report_definition.spec.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 020b78e..34c3d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased -- Added support for PATCH /2.0/reports{reportId}/definition endpoint -- WireMock integration tests for contract testing for PATCH /2.0/reports{reportId}/definition endpoint +- Added support for PATCH /2.0/reports/{reportId}/definition endpoint +- WireMock integration tests for contract testing for PATCH /2.0/reports/{reportId}/definition endpoint ## [4.7.1] - 2026-02-12 ### Added diff --git a/test/mock-api/reports/update_report_definition.spec.ts b/test/mock-api/reports/update_report_definition.spec.ts index 5ec5bfe..d7c8973 100644 --- a/test/mock-api/reports/update_report_definition.spec.ts +++ b/test/mock-api/reports/update_report_definition.spec.ts @@ -63,7 +63,7 @@ describe('Reports - updateReportDefinition endpoint tests', () => { expect(matchedRequest.method).toEqual('PATCH'); }); - it('updateReportDefinition request body', async () => { + it('updateReportDefinition all response body properties', async () => { const requestId = crypto.randomUUID(); const options = { reportId: TEST_REPORT_ID, From 64013a12c4108292edeb1f3411ab091443252085 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Tue, 10 Mar 2026 14:15:13 +0000 Subject: [PATCH 05/12] feat: add support for add report columns remove invalid options --- lib/reports/types.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/reports/types.ts b/lib/reports/types.ts index e9032a2..b813a04 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -876,7 +876,6 @@ export enum ReportColumnType { * System column types for report column identifiers. */ export enum ReportSystemColumnType { - AUTO_NUMBER = 'AUTO_NUMBER', CREATED_BY = 'CREATED_BY', CREATED_DATE = 'CREATED_DATE', MODIFIED_BY = 'MODIFIED_BY', @@ -1019,10 +1018,6 @@ export interface ReportSortingCriterion { * Sorting direction. */ sortingDirection: ReportSortingDirection | string; - /** - * Force null values to the bottom of the sorted list. - */ - forceNullsToBottom?: boolean; } /** From 2136e4ba9caa7d7d47d546a6621e30a91a16fbd2 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Thu, 12 Mar 2026 11:36:13 +0000 Subject: [PATCH 06/12] feat: add support for add report columns add update filters query param support --- lib/reports/index.ts | 7 ++++++- lib/reports/types.ts | 4 ++++ .../reports/update_report_definition.spec.ts | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/reports/index.ts b/lib/reports/index.ts index 5144935..8e1f0e5 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -93,7 +93,12 @@ export function create(options: CreateOptions): ReportsApi { patchOptions: UpdateReportDefinitionOptions, callback?: RequestCallback ): Promise => { - const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition' }; + const urlOptions = { + url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition', + queryParameters: { + updateFilters: patchOptions.updateFilters, + } + }; return requestor.patch({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); }; diff --git a/lib/reports/types.ts b/lib/reports/types.ts index b813a04..8aa4a1f 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -778,6 +778,10 @@ export interface UpdateReportDefinitionOptions extends RequestOptions { expect(matchedRequest.method).toEqual('PATCH'); }); + it('updateReportDefinition sets query parameters correctly', async () => { + const requestId = crypto.randomUUID(); + const options = { + reportId: TEST_REPORT_ID, + body: testFilterOnlyBody, + updateFilters: true, + customProperties: { + 'x-request-id': requestId, + 'x-test-name': '/reports/update-report-definition/all-response-body-properties' + } + }; + await client.reports.updateReportDefinition(options); + const matchedRequest = await findWireMockRequest(requestId); + expect(matchedRequest.queryParams.updateFilters.values).toContain('true'); + }); + it('updateReportDefinition all response body properties', async () => { const requestId = crypto.randomUUID(); const options = { From b60f1bf2afe914041421d4b40befc819351bc6c6 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Thu, 12 Mar 2026 11:39:32 +0000 Subject: [PATCH 07/12] feat: add support for add report columns fix prettier linting issue --- lib/reports/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/reports/index.ts b/lib/reports/index.ts index 8e1f0e5..7e6ad89 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -93,11 +93,11 @@ export function create(options: CreateOptions): ReportsApi { patchOptions: UpdateReportDefinitionOptions, callback?: RequestCallback ): Promise => { - const urlOptions = { + const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition', queryParameters: { updateFilters: patchOptions.updateFilters, - } + }, }; return requestor.patch({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); }; From b4cf7af21b218dd60bcf8aaebef1f1e58712bc14 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Mon, 16 Mar 2026 11:33:06 +0000 Subject: [PATCH 08/12] feat: update report definition response body update change aggregationCriteria to summarizingCriteria to match spec add returns report definition --- lib/reports/types.ts | 12 ++--- .../reports/update_report_definition.spec.ts | 50 ++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/reports/types.ts b/lib/reports/types.ts index 8aa4a1f..ec508e3 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -180,7 +180,7 @@ export interface ReportsApi { * Update a Report's definition based on the specified ID * * **Note:** This endpoint supports partial updates **only on root level** properties of the report definition, - * such as `filters`, `groupingCriteria` and `aggregationCriteria`. For example, you can update the report's filters + * such as `filters`, `groupingCriteria` and `summarizingCriteria`. For example, you can update the report's filters * without affecting its grouping criteria. However, nested properties within these objects, * such as a specific filter or grouping criterion, cannot be updated individually and * require a full replacement of the respective section. @@ -847,7 +847,7 @@ export enum ReportSortingDirection { } /** - * Aggregation types for report aggregation criteria. + * Aggregation types for report summarizing criteria. */ export enum ReportAggregationType { SUM = 'SUM', @@ -906,9 +906,9 @@ export interface ReportDefinition { */ groupingCriteria?: ReportGroupingCriterion[]; /** - * List of report aggregation criteria. + * List of report summarizing criteria. */ - aggregationCriteria?: ReportAggregationCriterion[]; + summarizingCriteria?: ReportSummarizingCriterion[]; /** * List of report sorting criteria. */ @@ -993,9 +993,9 @@ export interface ReportGroupingCriterion { } /** - * Report aggregation criterion. + * Report summarizing criterion. */ -export interface ReportAggregationCriterion { +export interface ReportSummarizingCriterion { /** * Object used to match a sheet column for a report. */ diff --git a/test/mock-api/reports/update_report_definition.spec.ts b/test/mock-api/reports/update_report_definition.spec.ts index b8e5785..d49b6ea 100644 --- a/test/mock-api/reports/update_report_definition.spec.ts +++ b/test/mock-api/reports/update_report_definition.spec.ts @@ -94,7 +94,55 @@ describe('Reports - updateReportDefinition endpoint tests', () => { expect(response).toEqual({ message: TEST_SUCCESS_MESSAGE, - resultCode: TEST_SUCCESS_RESULT_CODE + resultCode: TEST_SUCCESS_RESULT_CODE, + result: { + filters: { + operator: "AND", + criteria: [ + { + column: { + title: "Primary Column", + type: "TEXT_NUMBER", + primary: true + }, + operator: "EQUAL", + values: ["Test"] + } + ] + }, + groupingCriteria: [ + { + column: { + title: "Primary Column", + type: "TEXT_NUMBER", + primary: true + }, + sortingDirection: "ASCENDING", + isExpanded: true + } + ], + sortingCriteria: [ + { + column: { + title: "Primary Column", + type: "TEXT_NUMBER", + primary: true + }, + sortingDirection: "ASCENDING" + } + ], + summarizingCriteria: [ + { + column: { + title: "Primary Column", + type: "TEXT_NUMBER", + primary: true + }, + aggregationType: "COUNT", + isExpanded: true + } + ] + } }); const body = JSON.parse(matchedRequest.body); From acd943cd7b480aa63935e001f0b1317b72d7eff9 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Mon, 16 Mar 2026 11:56:06 +0000 Subject: [PATCH 09/12] feat: update report definition response body update --- lib/reports/index.ts | 5 +++-- lib/reports/types.ts | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/reports/index.ts b/lib/reports/index.ts index 7e6ad89..5ef61fb 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -17,6 +17,7 @@ import type { SetReportPublishStatusOptions, SetReportPublishStatusResponse, UpdateReportDefinitionOptions, + UpdateReportDefinitionResponse, } from './types'; import type { BaseResponseStatus } from '../types/BaseResponseStatus'; import * as constants from '../utils/constants'; @@ -91,8 +92,8 @@ export function create(options: CreateOptions): ReportsApi { const updateReportDefinition = ( patchOptions: UpdateReportDefinitionOptions, - callback?: RequestCallback - ): Promise => { + callback?: RequestCallback + ): Promise => { const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition', queryParameters: { diff --git a/lib/reports/types.ts b/lib/reports/types.ts index ec508e3..b98ca91 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -784,6 +784,10 @@ export interface UpdateReportDefinitionOptions extends RequestOptions Date: Thu, 19 Mar 2026 16:21:28 +0000 Subject: [PATCH 10/12] feat: update report definition response changed to PUT from PATCH no longer returning changed definition no longer needs query param --- lib/reports/index.ts | 7 ++- lib/reports/types.ts | 4 -- .../reports/update_report_definition.spec.ts | 50 +------------------ 3 files changed, 4 insertions(+), 57 deletions(-) diff --git a/lib/reports/index.ts b/lib/reports/index.ts index 5ef61fb..b85f0e2 100644 --- a/lib/reports/index.ts +++ b/lib/reports/index.ts @@ -17,7 +17,6 @@ import type { SetReportPublishStatusOptions, SetReportPublishStatusResponse, UpdateReportDefinitionOptions, - UpdateReportDefinitionResponse, } from './types'; import type { BaseResponseStatus } from '../types/BaseResponseStatus'; import * as constants from '../utils/constants'; @@ -92,15 +91,15 @@ export function create(options: CreateOptions): ReportsApi { const updateReportDefinition = ( patchOptions: UpdateReportDefinitionOptions, - callback?: RequestCallback - ): Promise => { + callback?: RequestCallback + ): Promise => { const urlOptions = { url: options.apiUrls.reports + '/' + patchOptions.reportId + '/definition', queryParameters: { updateFilters: patchOptions.updateFilters, }, }; - return requestor.patch({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); + return requestor.put({ ...optionsToSend, ...urlOptions, ...patchOptions }, callback); }; return { diff --git a/lib/reports/types.ts b/lib/reports/types.ts index b98ca91..ec508e3 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -784,10 +784,6 @@ export interface UpdateReportDefinitionOptions extends RequestOptions { }; await client.reports.updateReportDefinition(options); const matchedRequest = await findWireMockRequest(requestId); - expect(matchedRequest.method).toEqual('PATCH'); + expect(matchedRequest.method).toEqual('PUT'); }); it('updateReportDefinition sets query parameters correctly', async () => { @@ -95,54 +95,6 @@ describe('Reports - updateReportDefinition endpoint tests', () => { expect(response).toEqual({ message: TEST_SUCCESS_MESSAGE, resultCode: TEST_SUCCESS_RESULT_CODE, - result: { - filters: { - operator: "AND", - criteria: [ - { - column: { - title: "Primary Column", - type: "TEXT_NUMBER", - primary: true - }, - operator: "EQUAL", - values: ["Test"] - } - ] - }, - groupingCriteria: [ - { - column: { - title: "Primary Column", - type: "TEXT_NUMBER", - primary: true - }, - sortingDirection: "ASCENDING", - isExpanded: true - } - ], - sortingCriteria: [ - { - column: { - title: "Primary Column", - type: "TEXT_NUMBER", - primary: true - }, - sortingDirection: "ASCENDING" - } - ], - summarizingCriteria: [ - { - column: { - title: "Primary Column", - type: "TEXT_NUMBER", - primary: true - }, - aggregationType: "COUNT", - isExpanded: true - } - ] - } }); const body = JSON.parse(matchedRequest.body); From cab78eff92f112585a098e6832660a7d03c3a78a Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Tue, 31 Mar 2026 14:35:43 +0100 Subject: [PATCH 11/12] feat: update report definition update objects based on latest spec --- lib/reports/types.ts | 80 ++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/lib/reports/types.ts b/lib/reports/types.ts index ec508e3..dfa8928 100644 --- a/lib/reports/types.ts +++ b/lib/reports/types.ts @@ -863,15 +863,14 @@ export enum ReportAggregationType { * Column types for report column identifiers. */ export enum ReportColumnType { - ABSTRACT_DATETIME = 'ABSTRACT_DATETIME', CHECKBOX = 'CHECKBOX', - CONTACT_LIST = 'CONTACT_LIST', DATE = 'DATE', DATETIME = 'DATETIME', DURATION = 'DURATION', + CONTACT_LIST = 'CONTACT_LIST', MULTI_CONTACT_LIST = 'MULTI_CONTACT_LIST', - MULTI_PICKLIST = 'MULTI_PICKLIST', PICKLIST = 'PICKLIST', + MULTI_PICKLIST = 'MULTI_PICKLIST', PREDECESSOR = 'PREDECESSOR', TEXT_NUMBER = 'TEXT_NUMBER', } @@ -884,7 +883,7 @@ export enum ReportSystemColumnType { CREATED_DATE = 'CREATED_DATE', MODIFIED_BY = 'MODIFIED_BY', MODIFIED_DATE = 'MODIFIED_DATE', - SHEET_NAME = 'SHEET_NAME', + AUTO_NUMBER = 'AUTO_NUMBER', } // ============================================================================ @@ -916,42 +915,68 @@ export interface ReportDefinition { } /** - * Report filter expression. It is a recursive object that allows at most 3 levels. + * An expression to filter on report columns. It is a recursive object that allows at most three levels. + * + * It must include `operator` and at least one of the following: `criteria` or `nestedCriteria` * - * At least one of `criteria` or `nestedCriteria` has to be provided in addition to `operator`. + * Here is a two-level example: * - * Example: * ```json * { * "operator": "OR", * "nestedCriteria": [ * { * "operator": "AND", - * "nestedCriteria": [], * "criteria": [ * { * "column": { "title": "Price", "type": "TEXT_NUMBER" }, * "operator": "GREATER_THAN", * "values": ["11"] + * }, + * { + * "column": { "primary": true }, + * "operator": "CONTAINS", + * "values": ["PROJ-1"] + * } + * ] + * }, + * { + * "operator": "AND", + * "criteria": [ + * { + * "column": { "title": "Quantity", "type": "TEXT_NUMBER" }, + * "operator": "LESS_THAN", + * "values": ["12"] + * }, + * { + * "column": { "title": "Sold Out", "type": "CHECKBOX" }, + * "operator": "IS_CHECKED" * } * ] * } - * ], - * "criteria": [] + * ] * } * ``` + * + * It's equivalent to the following pseudo logic: + * + * ``` + * ("Price" > 11 AND "Primary" CONTAINS "PROJ-1") + * OR + * ("Quantity" < 12 AND "Sold Out" IS_CHECKED) + * ``` */ export interface ReportFilterExpression { /** - * The boolean operator that will be applied to the list of `criteria` and `nestedCriteria`. + * The boolean operator to apply to the list of `criteria` and `nestedCriteria`. */ operator: ReportFilterOperator | string; /** - * A recursive list of report filter expressions. Each item will be joined to the filter expression with the AND/OR operator defined on this level. + * A recursive list of report filter expressions. Each item is joined to the filter expression with the AND/OR operator defined on this level. */ nestedCriteria?: ReportFilterExpression[]; /** - * Criteria objects specifying custom criteria against which to match cell values. Each item will be joined to the filter expression with the AND/OR operator defined on this level. + * Criteria objects specifying custom criteria against which to match cell values. Each item is joined to the filter expression with the AND/OR operator defined on this level. */ criteria?: ReportFilterCriterion[]; } @@ -1004,10 +1029,6 @@ export interface ReportSummarizingCriterion { * Type of aggregation. */ aggregationType: ReportAggregationType | string; - /** - * Indicates whether the group is expanded in the UI. - */ - isExpanded?: boolean; } /** @@ -1025,31 +1046,38 @@ export interface ReportSortingCriterion { } /** - * Object used to match a sheet column for a report. One of [`type`, `systemColumnType`] or [`primary=true`] is required. + * An object for matching a source sheet column for a report. It requires one of: * - * `systemColumnType` should be specified if you want to match a system column. Use `primary=true` to match primary columns. When matching primary columns `title` can be used to customize primary column name in the rendered report. + * - [`type`, `title`] for **regular columns** + * - [`type`, `systemColumnType`] for **system columns** + * - [`type=TEXT_NUMBER`, `primary=true`] for the **primary column** + * - [`type=TEXT_NUMBER`, `sheetNameColumn=true`] for the special **sheet name report column** * - * **Note:** Columns in the report are matched by the combination of `title` and `type` (and `systemColumnType` if specified). + * **Note:** You can combine multiple `CHECKBOX` columns or multiple `PICKLIST` columns from different sheets into a single report column, even if their underlying symbols differ. However, you can't combine a `CHECKBOX` column with a `PICKLIST` column, because they're different types. * - * **Note:** `symbol` is not used for matching and as a result `CHECKBOX` or `PICKLIST` columns with different symbols (from different sheets) can be combined into the same column in the report. You cannot combine `CHECKBOX` with `PICKLIST` into the same column in the report because they are different types. + * **Note:** The system column type `AUTO_NUMBER` is matched together with columns having the same `title` and `type=TEXT_NUMBER`. Therefore, `title` is a required property in this case. */ export interface ReportColumnIdentifier { /** - * Column title to be matched from the source sheets. + * Title of a column to match. * - * Note: If `primary` is **true** then this property can be used to customize the primary column title. + * **Note:** If you specified `primary=true` to match primary columns, you can set the resulting report column title to this value. */ title?: string; /** - * Column type to be matched from the source sheets. See Column Types. + * Type of column to match. See [Column Types](/api/smartsheet/openapi/columns). */ type?: ReportColumnType | string; /** - * Column type to be matched from the source sheets. See System Columns. Additionally `SHEET_NAME` is available as an extra option for reports. + * System column type to match. See [System Columns](/api/smartsheet/openapi/columns). */ systemColumnType?: ReportSystemColumnType | string; /** - * Indicates if the matched column is primary. + * Set this to `true` to match the primary column. */ primary?: boolean; + /** + * Set this to `true` to match the special "Sheet Name" report column. + */ + sheetNameColumn?: boolean; } From 2ae455a91f50036dedddbe476ddbbdda7073ebe9 Mon Sep 17 00:00:00 2001 From: Scott McGowan Date: Mon, 6 Apr 2026 13:34:02 +0100 Subject: [PATCH 12/12] fix: test fail after merge --- test/functional/client.spec.ts | 2 +- test/mock-api/reports/update_report_definition.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/client.spec.ts b/test/functional/client.spec.ts index e2e9fb6..91a03f1 100644 --- a/test/functional/client.spec.ts +++ b/test/functional/client.spec.ts @@ -180,7 +180,7 @@ describe('Client Unit Tests', () => { describe('#reports', () => { it('should have reports object', () => { expect(smartsheet).toHaveProperty('reports'); - expect(Object.keys(smartsheet.reports)).toHaveLength(15); + expect(Object.keys(smartsheet.reports)).toHaveLength(16); }); it('should have get methods', () => { diff --git a/test/mock-api/reports/update_report_definition.spec.ts b/test/mock-api/reports/update_report_definition.spec.ts index fccdb48..de6ef6a 100644 --- a/test/mock-api/reports/update_report_definition.spec.ts +++ b/test/mock-api/reports/update_report_definition.spec.ts @@ -48,7 +48,7 @@ describe('Reports - updateReportDefinition endpoint tests', () => { expect(parsedUrl.pathname).toEqual(`/2.0/reports/${TEST_REPORT_ID}/definition`); }); - it('updateReportDefinition uses PATCH method', async () => { + it('updateReportDefinition uses PUT method', async () => { const requestId = crypto.randomUUID(); const options = { reportId: TEST_REPORT_ID,