diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 88d5fa2172..41abb65a8c 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.13.1", + "version": "7.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.13.1", + "version": "7.14.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index ac1be00930..7b6e33ec3e 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.13.1", + "version": "7.14.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index f91fa3da4e..a30bafbb87 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,13 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 7.14.0 +*Released*: 30 January 2026 +- Update `withQueryModels` to track and cancel requests for `loadRows`, `loadSelections` and `loadTotalCount` +- Add `RequestHandler` handling for `selectRows` and `selectRowsDeprecated` +- Skip error logging when request is aborted +- Update a few endpoint wrappers to use `request()` + ### version 7.13.1 *Released*: 26 January 2026 - Merge from release26.1-SNAPSHOT to develop diff --git a/packages/components/src/internal/actions.ts b/packages/components/src/internal/actions.ts index 675d31656a..cf2cee4f33 100644 --- a/packages/components/src/internal/actions.ts +++ b/packages/components/src/internal/actions.ts @@ -16,7 +16,7 @@ import { fromJS, List } from 'immutable'; import { ActionURL, Ajax, Filter, getServerContext, Query, Utils } from '@labkey/api'; -import { resolveKey, SchemaQuery } from '../public/SchemaQuery'; +import { SchemaQuery } from '../public/SchemaQuery'; import { Actions } from '../public/QueryModel/withQueryModels'; @@ -38,9 +38,8 @@ import { } from './constants'; import { DataViewInfo } from './DataViewInfo'; -import { handleRequestFailure } from './request'; +import { request, RequestHandler } from './request'; import { resolveErrorMessage } from './util/messaging'; -import { buildURL } from './url/AppURL'; import { ViewInfo } from './ViewInfo'; import { createGridModelId } from './models'; @@ -56,52 +55,41 @@ export function selectAll( queryParameters?: Record, containerFilter?: Query.ContainerFilter ): Promise { - return new Promise((resolve, reject) => { - return Ajax.request({ - url: buildURL('query', 'selectAll.api', undefined, { - container: containerPath, - }), - method: 'POST', - params: buildQueryParams(key, schemaQuery, filterArray, queryParameters, containerPath, containerFilter), - success: Utils.getCallbackWrapper(response => { - resolve(response); - }), - failure: handleRequestFailure( - reject, - `Problem in selecting all items in the grid ${key} ${schemaQuery.schemaName} ${schemaQuery.queryName}` - ), - }); + return request({ + url: ActionURL.buildURL('query', 'selectAll.api', containerPath), + method: 'POST', + params: buildQueryParams(key, schemaQuery, filterArray, queryParameters, containerPath, containerFilter), + errorLogMsg: `Problem in selecting all items in the grid ${key} ${schemaQuery.schemaName} ${schemaQuery.queryName}`, }); } -export function getGridIdsFromTransactionId( +export async function getGridIdsFromTransactionId( transactionAuditId: number | string, dataType: string, containerPath?: string ): Promise { - if (!transactionAuditId) { - return; - } - const failureMsg = 'There was a problem retrieving the ' + dataType + ' from the last action.'; - return new Promise((resolve, reject) => { - Ajax.request({ - url: ActionURL.buildURL('audit', 'getTransactionRowIds.api'), - params: { transactionAuditId, dataType, containerFilter: getContainerFilterForFolder(containerPath) }, - success: Utils.getCallbackWrapper(response => { - if (response.success) { - // The server returns numbers, so we coerce to string; If we don't it can lead to bugs (and has). - resolve(response.rowIds.map((rowId: number) => rowId.toString())); - } else { - console.error(failureMsg + ' (transactionAuditId = ' + transactionAuditId + ')', response); - reject(failureMsg); - } - }), - failure: Utils.getCallbackWrapper(error => { - console.error(failureMsg + ' (transactionAuditId = ' + transactionAuditId + ')', error); - reject(failureMsg); - }), - }); + if (!transactionAuditId) return; + + const failureMsg = `There was a problem retrieving the ${dataType} from the last action.`; + const errorLogMsg = `${failureMsg} (transactionAuditId = ${transactionAuditId})`; + + const response = await request<{ rowIds: number[]; success: boolean }>({ + url: ActionURL.buildURL('audit', 'getTransactionRowIds.api'), + params: { + containerFilter: getContainerFilterForFolder(containerPath), + dataType, + transactionAuditId, + }, + errorLogMsg, }); + + if (!response.success) { + console.error(errorLogMsg, response); + throw new Error(failureMsg); + } + + // The server returns numbers, so we coerce to string; If we don't, it can lead to bugs (and has). + return response.rowIds.map(rowId => rowId.toString()); } export async function selectGridIdsFromTransactionId( @@ -274,7 +262,8 @@ export function exportRows(type: EXPORT_TYPES, exportParams: Record } }); - let controller, action; + let action: string; + let controller: string; if (type === EXPORT_TYPES.CSV || type === EXPORT_TYPES.TSV || type === EXPORT_TYPES.LABEL_TEMPLATE) { controller = 'query'; action = 'exportRowsTsv.post'; @@ -298,7 +287,7 @@ export function exportRows(type: EXPORT_TYPES, exportParams: Record form.append('formDataEncoded', 'true'); Ajax.request({ - url: buildURL(controller, action, undefined, { container: containerPath, returnUrl: false }), + url: ActionURL.buildURL(controller, action, containerPath), method: 'POST', form, downloadFile: true, @@ -380,29 +369,24 @@ export function getSelected( filterArray?: Filter.IFilter[], containerPath?: string, queryParameters?: Record, - containerFilter?: Query.ContainerFilter + containerFilter?: Query.ContainerFilter, + requestHandler?: RequestHandler ): Promise { - if (useSnapshotSelection) return getSnapshotSelections(key, containerPath); + if (useSnapshotSelection) return getSnapshotSelections(key, containerPath, requestHandler); - return new Promise((resolve, reject) => { - return Ajax.request({ - url: buildURL('query', 'getSelected.api', undefined, { - container: containerPath, - }), - method: 'POST', - jsonData: getFilteredQueryParams( - key, - schemaQuery, - filterArray, - queryParameters, - containerPath, - containerFilter - ), - success: Utils.getCallbackWrapper(response => { - resolve(response); - }), - failure: handleRequestFailure(reject, 'Failed to get selected.'), - }); + return request({ + url: ActionURL.buildURL('query', 'getSelected.api', containerPath), + method: 'POST', + jsonData: getFilteredQueryParams( + key, + schemaQuery, + filterArray, + queryParameters, + containerPath, + containerFilter + ), + errorLogMsg: 'Failed to get selected.', + requestHandler, }); } @@ -419,7 +403,7 @@ export async function getSelectedDataDeprecated( viewName?: string, keyColumn = 'RowId' ): Promise { - const { models, orderedModels } = await selectRowsDeprecated({ + const { key, models, orderedModels } = await selectRowsDeprecated({ schemaName, queryName, viewName, @@ -430,11 +414,9 @@ export async function getSelectedDataDeprecated( offset: 0, }); - const dataKey = resolveKey(schemaName, queryName); - return { - data: fromJS(models[dataKey]), - dataIds: List(orderedModels[dataKey]), + data: fromJS(models[key]), + dataIds: orderedModels[key], }; } @@ -452,26 +434,18 @@ export type ClearSelectedOptions = { }; export function clearSelected(options: ClearSelectedOptions): Promise { - return new Promise((resolve, reject) => { - return Ajax.request({ - url: ActionURL.buildURL('query', 'clearSelected.api', options.containerPath), - method: 'POST', - jsonData: getFilteredQueryParams( - options.selectionKey, - options.schemaQuery, - options.filters, - options.queryParameters, - options.containerPath, - options.containerFilter - ), - success: Utils.getCallbackWrapper(response => { - resolve(response); - }), - failure: handleRequestFailure( - reject, - `Problem clearing the selection ${options.selectionKey} ${options.schemaQuery?.schemaName} ${options.schemaQuery?.queryName}` - ), - }); + return request({ + url: ActionURL.buildURL('query', 'clearSelected.api', options.containerPath), + method: 'POST', + jsonData: getFilteredQueryParams( + options.selectionKey, + options.schemaQuery, + options.filters, + options.queryParameters, + options.containerPath, + options.containerFilter + ), + errorLogMsg: `Problem clearing the selection ${options.selectionKey} ${options.schemaQuery?.schemaName} ${options.schemaQuery?.queryName}`, }); } @@ -490,7 +464,7 @@ export function clearSelected(options: ClearSelectedOptions): Promise ): Promise { - return new Promise((resolve, reject) => { - return Ajax.request({ - url: buildURL('query', 'setSelected.api', undefined, { - container: containerPath, - }), - method: 'POST', - jsonData: { - id: ids, - key, - checked, - validateIds, - schemaName, - queryName, - filterList: filters, - queryParameters, - }, - success: Utils.getCallbackWrapper(response => { - resolve(response); - }), - failure: handleRequestFailure(reject, 'Failed to set selection.'), - }); + return request({ + url: ActionURL.buildURL('query', 'setSelected.api', containerPath), + method: 'POST', + jsonData: { + id: ids, + key, + checked, + validateIds, + schemaName, + queryName, + filterList: filters, + queryParameters, + }, + errorLogMsg: 'Failed to set selection.', }); } export type ReplaceSelectedOptions = { containerPath?: string; - id: string[] | string; + id: string | string[]; selectionKey: string; }; export function replaceSelected(options: ReplaceSelectedOptions): Promise { - return new Promise((resolve, reject) => { - return Ajax.request({ - url: ActionURL.buildURL('query', 'replaceSelected.api', options.containerPath), - method: 'POST', - jsonData: { key: options.selectionKey, id: options.id }, - success: Utils.getCallbackWrapper(response => { - resolve(response); - }), - failure: handleRequestFailure(reject, 'Failed to replace selection.'), - }); + return request({ + url: ActionURL.buildURL('query', 'replaceSelected.api', options.containerPath), + method: 'POST', + jsonData: { key: options.selectionKey, id: options.id }, + errorLogMsg: 'Failed to replace selection.', }); } @@ -551,24 +513,14 @@ export function replaceSelected(options: ReplaceSelectedOptions): Promise