From 9a01937a250da4a6580cf50523d399f556dc10d7 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 21 Jan 2026 16:26:19 -0800 Subject: [PATCH 1/9] Update `FilterStatus` to optionally include an "Add Filter" button --- .../components/releaseNotes/components.md | 4 + .../src/public/QueryModel/FilterStatus.tsx | 91 +++++++++++-------- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 881e48c847..60100fd574 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,10 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version TBD +*Released*: TBD +- Update `FilterStatus` to optionally include an "Add Filter" button + ### version 7.13.0 *Released*: 20 January 2026 - Multi value text choices diff --git a/packages/components/src/public/QueryModel/FilterStatus.tsx b/packages/components/src/public/QueryModel/FilterStatus.tsx index b4cec32b46..859e4faf43 100644 --- a/packages/components/src/public/QueryModel/FilterStatus.tsx +++ b/packages/components/src/public/QueryModel/FilterStatus.tsx @@ -3,58 +3,76 @@ import React, { FC, memo } from 'react'; import { ActionValue } from './grid/actions/Action'; import { Value } from './grid/Value'; import { filterActionValuesByType } from './grid/utils'; +import classNames from 'classnames'; interface Props { actionValues: ActionValue[]; lockReadOnlyForDelete?: boolean; + onAddFilterClick?: () => void; onClick: (actionValue: ActionValue, event: any) => void; onRemove: (actionValueIndex: number, event: any) => void; onRemoveAll?: () => void; + showAdd?: boolean; } export const FilterStatus: FC = memo(props => { - const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete } = props; - const showRemoveAll = filterActionValuesByType(actionValues, 'filter', lockReadOnlyForDelete).length > 1; + const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete, showAdd, onAddFilterClick } = props; + const filterCount = actionValues?.filter(a => a.action.keyword === 'filter').length; + const showRemoveAll = actionValues + ? filterActionValuesByType(actionValues, 'filter', lockReadOnlyForDelete).length > 1 + : false; return (
- {actionValues - .sort((a, b) => { - // sort the view actions to the front - if (a.action.keyword !== b.action.keyword) { - return a.action.keyword === 'view' ? -1 : b.action.keyword === 'view' ? 1 : 0; - } + {actionValues && + actionValues + .sort((a, b) => { + // sort the view actions to the front + if (a.action.keyword !== b.action.keyword) { + return a.action.keyword === 'view' ? -1 : b.action.keyword === 'view' ? 1 : 0; + } - // then sort by filter display value - const aDisplayValue = a.displayValue ?? a.value; - const bDisplayValue = b.displayValue ?? b.value; - return aDisplayValue > bDisplayValue ? 1 : aDisplayValue < bDisplayValue ? -1 : 0; - }) - .map((actionValue, index) => { - // loop over all actionValues so that the index remains consistent, but don't show sort actions - if (actionValue.action.keyword === 'sort') { - return null; - } + // then sort by filter display value + const aDisplayValue = a.displayValue ?? a.value; + const bDisplayValue = b.displayValue ?? b.value; + return aDisplayValue > bDisplayValue ? 1 : aDisplayValue < bDisplayValue ? -1 : 0; + }) + .map((actionValue, index) => { + // loop over all actionValues so that the index remains consistent, but don't show sort actions + if (actionValue.action.keyword === 'sort') { + return null; + } - // only FilterActions can be edited via click - const _onClick = actionValue.action.keyword === 'filter' ? onClick : undefined; - // search and filter actions can be removed via click - const _onRemove = - actionValue.action.keyword === 'filter' || actionValue.action.keyword === 'search' - ? onRemove - : undefined; + // only FilterActions can be edited via click + const _onClick = actionValue.action.keyword === 'filter' ? onClick : undefined; + // search and filter actions can be removed via click + const _onRemove = + actionValue.action.keyword === 'filter' || actionValue.action.keyword === 'search' + ? onRemove + : undefined; - return ( - - ); - })} + return ( + + ); + })} + + {showAdd && onAddFilterClick && ( + + )} {onRemoveAll && showRemoveAll && ( Remove all @@ -63,3 +81,4 @@ export const FilterStatus: FC = memo(props => {
); }); +FilterStatus.displayName = 'FilterStatus'; From b2cbb472b602a4cf64ee589da5ca4f372da3bf95 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 21 Jan 2026 16:26:41 -0800 Subject: [PATCH 2/9] NPE avoidance in AppURL --- packages/components/src/internal/url/AppURL.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/components/src/internal/url/AppURL.ts b/packages/components/src/internal/url/AppURL.ts index bf643529d5..6c65cb14eb 100644 --- a/packages/components/src/internal/url/AppURL.ts +++ b/packages/components/src/internal/url/AppURL.ts @@ -99,17 +99,19 @@ export class AppURL { } } - const stringPart = parts[i].toString(); - const newPart = encodeURIComponent(stringPart); - - if (i === 0) { - if (stringPart.indexOf('/') === 0) { - basePath += newPart; + if (parts[i]) { + const stringPart = parts[i].toString(); + const newPart = encodeURIComponent(stringPart); + + if (i === 0) { + if (stringPart.indexOf('/') === 0) { + basePath += newPart; + } else { + basePath += '/' + newPart; + } } else { basePath += '/' + newPart; } - } else { - basePath += '/' + newPart; } } From 0f1971f28500e97f75cf0e9390aa4dfa79dc51a3 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Wed, 21 Jan 2026 16:30:07 -0800 Subject: [PATCH 3/9] @labkey/components v7.13.1-jobActionsUiUpdate.0 --- packages/components/package-lock.json | 4 ++-- packages/components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index a0066cbefd..9f6bec8c60 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.13.0", + "version": "7.13.1-jobActionsUiUpdate.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.13.0", + "version": "7.13.1-jobActionsUiUpdate.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 e58f02190d..1b304b4c30 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.13.0", + "version": "7.13.1-jobActionsUiUpdate.0", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ From 05e429e46cd2fe5dc91bd2a4cce9c131e06a1309 Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 27 Jan 2026 08:08:58 -0800 Subject: [PATCH 4/9] merge from develop --- packages/components/package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 9f6bec8c60..79dbe89521 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.13.1-jobActionsUiUpdate.0", + "version": "7.13.2-jobActionsUiUpdate.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.13.1-jobActionsUiUpdate.0", + "version": "7.13.2-jobActionsUiUpdate.0", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", From 4e3f4f652c566d3b0573e7a7bdc85b276f28c72f Mon Sep 17 00:00:00 2001 From: labkey-susanh Date: Tue, 27 Jan 2026 11:27:26 -0800 Subject: [PATCH 5/9] Remove redundant showAdd prop --- packages/components/src/public/QueryModel/FilterStatus.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/components/src/public/QueryModel/FilterStatus.tsx b/packages/components/src/public/QueryModel/FilterStatus.tsx index 859e4faf43..f855a1efa4 100644 --- a/packages/components/src/public/QueryModel/FilterStatus.tsx +++ b/packages/components/src/public/QueryModel/FilterStatus.tsx @@ -12,11 +12,10 @@ interface Props { onClick: (actionValue: ActionValue, event: any) => void; onRemove: (actionValueIndex: number, event: any) => void; onRemoveAll?: () => void; - showAdd?: boolean; } export const FilterStatus: FC = memo(props => { - const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete, showAdd, onAddFilterClick } = props; + const { actionValues, onClick, onRemove, onRemoveAll, lockReadOnlyForDelete, onAddFilterClick } = props; const filterCount = actionValues?.filter(a => a.action.keyword === 'filter').length; const showRemoveAll = actionValues ? filterActionValuesByType(actionValues, 'filter', lockReadOnlyForDelete).length > 1 @@ -63,7 +62,7 @@ export const FilterStatus: FC = memo(props => { ); })} - {showAdd && onAddFilterClick && ( + {onAddFilterClick && (