Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cb2f977
withQueryModels: Move types to QueryModel and utils to utils.ts
labkey-alan Apr 18, 2026
a104898
useQueryModels: stub implementation
labkey-alan Apr 18, 2026
144eb58
QueryModel/utils.ts - Add bindURL
labkey-alan Apr 18, 2026
2b72c24
QueryModelManager - implement addModel, maybeLoad, updateModelsFromUR…
labkey-alan Apr 18, 2026
c8a8baa
QueryModelManager - implement pagination methods, setSorts, setMaxRow…
labkey-alan Apr 18, 2026
7056a47
QueryModelLoader: add loadTotalCount
labkey-alan Apr 18, 2026
f1ccc4f
QueryModelManager: implement loadAllModels, loadCharts, loadModel, lo…
labkey-alan Apr 18, 2026
0d27d20
QueryModelManager: implement loadTotalCount, loadSelections, loadRows
labkey-alan Apr 18, 2026
f5ad86d
QueryModelManager: implement replaceSelections, resetTotalCountState,…
labkey-alan Apr 18, 2026
ad15946
QueryModelManager: implement clearSelections, onModelChange, selectRo…
labkey-alan Apr 18, 2026
25ea2b3
Add useQueryModels.test.tsx
labkey-alan Apr 18, 2026
41bd1ee
Move RequestManager tests to utils.test.ts
labkey-alan Apr 18, 2026
db79426
Cleanup
labkey-alan Apr 18, 2026
1fe2569
Only call saveSettings/syncURL if needed
labkey-alan Apr 20, 2026
ae7f0f0
useQueryModels: add docstring
labkey-alan Apr 20, 2026
96a52a9
useQueryModels.test.tsx - Remove usages of sleep
labkey-alan Apr 20, 2026
9e0f1a4
APIKeysPanel: use useQueryModels
labkey-alan Apr 20, 2026
2d14d5d
Actions: remove setSchemaQuery
labkey-alan Apr 20, 2026
def4c66
withQueryModels: use useQueryModels (TEMPORARY CHANGE)
labkey-alan Apr 20, 2026
52dd6b0
useQueryModels: default queryConfigs to {}
labkey-alan Apr 25, 2026
bb5f99a
Remove resetQueryInfoState - it is unused
labkey-alan Apr 27, 2026
4277f1e
QueryModel: make selections default to empty Set
labkey-alan Apr 27, 2026
bc544d0
useQueryModels: syncURL on loadModel
labkey-alan May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version 8.0.0
*Released* ?? April 2026
- Add useQueryModels hook: functionally equivalent to withQueryModels
- Deprecate withQueryModels
- Actions: remove `setSchemaQuery`
- Backwards incompatible, but likely safe since there are no known usages
- APIKeysPanel: Use useQueryModels

### version 7.41.1
*Released*: 3 June 2026
- Merge from release26.6-SNAPSHOT to develop
Expand Down
23 changes: 12 additions & 11 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -614,9 +614,9 @@ import {
wrapDraggable,
} from './internal/test/testHelpers';
import { renderWithAppContext } from './internal/test/reactTestLibraryHelpers';
import { flattenValuesFromRow, QueryModel, SavedSettings } from './public/QueryModel/QueryModel';
import { ChangeType, flattenValuesFromRow, QueryModel, SavedSettings } from './public/QueryModel/QueryModel';
import { getExpandQueryInfo, includedColumnsForCustomizationFilter } from './public/QueryModel/CustomizeGridViewModal';
import { ChangeType, withQueryModels } from './public/QueryModel/withQueryModels';
import { withQueryModels } from './public/QueryModel/withQueryModels';
import { GridPanel, GridPanelWithModel } from './public/QueryModel/GridPanel';
import { TabbedGridPanel } from './public/QueryModel/TabbedGridPanel';
import { DetailPanel, DetailPanelWithModel } from './public/QueryModel/DetailPanel';
Expand Down Expand Up @@ -1798,6 +1798,11 @@ export {
wrapDraggable,
};

// Due to babel-loader & typescript babel plugins we need to export/import types separately. The babel plugins require
// the typescript compiler option "isolatedModules", which do not export types from modules, so types must be exported
// separately.
// https://github.com/babel/babel-loader/issues/603

export type { ComponentsAPIWrapper } from './internal/APIWrapper';
export type { AppReducerState, ProductMenuState, ServerNotificationState } from './internal/app/reducers';
export type {
Expand Down Expand Up @@ -1926,18 +1931,14 @@ export type { ImportTemplate } from './public/QueryInfo';
export type { EditableDetailPanelProps } from './public/QueryModel/EditableDetailPanel';
export type { Action, ActionValue } from './public/QueryModel/grid/actions/Action';
export type { QueryConfig } from './public/QueryModel/QueryModel';
export type { QueryModelLoader } from './public/QueryModel/QueryModelLoader';
export type { TabbedGridPanelProps } from './public/QueryModel/TabbedGridPanel';

// Due to babel-loader & typescript babel plugins we need to export/import types separately. The babel plugins require
// the typescript compiler option "isolatedModules", which do not export types from modules, so types must be exported
// separately.
// https://github.com/babel/babel-loader/issues/603
export type {
Actions,
InjectedQueryModels,
MakeQueryModels,
QueryConfigMap,
QueryModelMap,
RequiresModelAndActions,
} from './public/QueryModel/withQueryModels';
} from './public/QueryModel/QueryModel';
export type { QueryModelLoader } from './public/QueryModel/QueryModelLoader';

export type { TabbedGridPanelProps } from './public/QueryModel/TabbedGridPanel';
export type { MakeQueryModels } from './public/QueryModel/withQueryModels';
2 changes: 1 addition & 1 deletion packages/components/src/internal/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ActionURL, Ajax, Filter, getServerContext, Query, Utils } from '@labkey

import { SchemaQuery } from '../public/SchemaQuery';

import { Actions } from '../public/QueryModel/withQueryModels';
import { Actions } from '../public/QueryModel/QueryModel';

import { GridResponse } from './components/editable/models';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/
import React, { FC, memo, useCallback, useState } from 'react';

import { RequiresModelAndActions } from '../../../public/QueryModel/withQueryModels';
import { RequiresModelAndActions } from '../../../public/QueryModel/QueryModel';

import { useNotificationsContext } from '../notifications/NotificationsContext';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { Modal } from '../../Modal';

import { LoadingSpinner } from '../base/LoadingSpinner';

import { QueryModel } from '../../../public/QueryModel/QueryModel';
import { RequiresModelAndActions } from '../../../public/QueryModel/withQueryModels';
import { QueryModel, RequiresModelAndActions } from '../../../public/QueryModel/QueryModel';

import { useServerContext } from '../base/ServerContext';
import { hasPermissions } from '../base/models/User';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useAppContext } from '../../AppContext';

import { DetailDisplaySharedProps } from '../forms/detail/DetailDisplay';

import { RequiresModelAndActions } from '../../../public/QueryModel/withQueryModels';
import { RequiresModelAndActions } from '../../../public/QueryModel/QueryModel';
import { SchemaQuery } from '../../../public/SchemaQuery';
import { DetailPanel } from '../../../public/QueryModel/DetailPanel';
import { QueryColumn } from '../../../public/QueryColumn';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, { FC, memo, useCallback, useState } from 'react';

import { Modal } from '../../Modal';

import { QueryModelMap } from '../../../public/QueryModel/withQueryModels';
import { QueryModelMap } from '../../../public/QueryModel/QueryModel';
import { CheckboxLK } from '../../Checkbox';

interface ExportModalProperties {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { QuerySelect } from '../forms/QuerySelect';
import { Alert } from '../base/Alert';
import { LoadingSpinner } from '../base/LoadingSpinner';

import { InjectedQueryModels, withQueryModels } from '../../../public/QueryModel/withQueryModels';
import { QueryModel } from '../../../public/QueryModel/QueryModel';
import { withQueryModels } from '../../../public/QueryModel/withQueryModels';
import { InjectedQueryModels, QueryModel } from '../../../public/QueryModel/QueryModel';

import { FormButtons } from '../../FormButtons';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ import { hasModule } from '../../../app/utils';
import { LineageDetail } from './LineageDetail';
import { DetailHeader, NodeDetailHeader } from './NodeDetailHeader';
import { DetailsListLineageIO, DetailsListNodes, DetailsListSteps } from './DetailsList';
import { InjectedQueryModels, QueryConfigMap, withQueryModels } from '../../../../public/QueryModel/withQueryModels';
import { withQueryModels } from '../../../../public/QueryModel/withQueryModels';
import { Filter } from '@labkey/api';
import { SchemaQuery } from '../../../../public/SchemaQuery';
import { ViewInfo } from '../../../ViewInfo';
import { QueryModel } from '../../../../public/QueryModel/QueryModel';
import { InjectedQueryModels, QueryConfigMap, QueryModel } from '../../../../public/QueryModel/QueryModel';

import { LINEAGE_DETAIL_REQUIRED_COLS } from '../constants';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { LoadingSpinner } from '../base/LoadingSpinner';
import { caseInsensitive } from '../../util/utils';
import { SCHEMAS } from '../../schemas';

import { InjectedQueryModels, withQueryModels } from '../../../public/QueryModel/withQueryModels';
import { withQueryModels } from '../../../public/QueryModel/withQueryModels';
import { InjectedQueryModels } from '../../../public/QueryModel/QueryModel';

import { SampleStatusTag } from './SampleStatusTag';
import { getSampleStatus, getSampleStatusContainerFilter } from './utils';
Expand Down
89 changes: 42 additions & 47 deletions packages/components/src/internal/components/user/APIKeysPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,13 @@ import { AppContext, useAppContext } from '../../AppContext';
import { setCopyValue } from '../../events';
import { isApp, isFeatureEnabled } from '../../app/utils';
import { ProductFeature } from '../../app/constants';
import {
ChangeType,
InjectedQueryModels,
QueryConfigMap,
RequiresModelAndActions,
withQueryModels,
} from '../../../public/QueryModel/withQueryModels';
import { ChangeType, QueryConfigMap, RequiresModelAndActions } from '../../../public/QueryModel/QueryModel';
import { SCHEMAS } from '../../schemas';
import { GridPanel } from '../../../public/QueryModel/GridPanel';
import { Modal } from '../../Modal';
import { getHelpLink, HelpLink } from '../../util/helpLinks';
import { biologicsIsPrimaryApp } from '../../app/products';
import { useQueryModels } from '../../../public/QueryModel/useQueryModels';

const API_KEYS_QUERY_HREF = ActionURL.buildURL('query', 'executeQuery.view', '/', {
schemaName: 'core',
Expand Down Expand Up @@ -68,15 +63,15 @@ const APIKeysButtonsComponent: FC<ButtonsComponentProps> = props => {
const noun = model.selections?.size > 1 ? 'Keys' : 'Key';
return (
<div className="btn-group">
<button type="button" className="btn btn-default" disabled={!model.hasSelections} onClick={onDeleteClicked}>
<button className="btn btn-default" disabled={!model.hasSelections} onClick={onDeleteClicked} type="button">
<span className="fa fa-trash" /> Delete
</button>
{showConfirmDelete && (
<Modal
confirmClass="btn-danger"
confirmText="Yes, Delete"
onCancel={closeDeleteModal}
onConfirm={onConfirmDelete}
confirmText="Yes, Delete"
title={`Delete ${model.selections?.size} API ${noun}`}
>
<strong>Deletion cannot be undone.</strong> Do you want to proceed?
Expand All @@ -90,7 +85,7 @@ APIKeysButtonsComponent.displayName = 'APIKeysButtonsComponent';
interface KeyGeneratorProps {
afterCreate?: () => void;
noun: string;
type: 'session' | 'apikey';
type: 'apikey' | 'session';
}

interface ModalProps extends KeyGeneratorProps {
Expand Down Expand Up @@ -139,23 +134,23 @@ export const KeyGeneratorModal: FC<ModalProps> = props => {

return (
<Modal
title={noun}
cancelText={keyValue ? 'Done' : undefined}
onCancel={onClose}
canConfirm={!keyValue}
confirmText={type === 'apikey' ? 'Generate ' + noun : undefined}
onCancel={onClose}
onConfirm={!keyValue && type === 'apikey' ? onGenerateKey : undefined}
title={noun}
>
{type === 'apikey' && !keyValue && (
<div>
<label htmlFor="keyDescription">Description</label>
<input
autoFocus
className="form-control api-key__input"
id="keyDescription"
type="text"
onChange={changeDescription}
autoFocus
placeholder="Enter description of key usage (optional)"
type="text"
/>
</div>
)}
Expand All @@ -173,10 +168,10 @@ export const KeyGeneratorModal: FC<ModalProps> = props => {
/>
<button
className="btn btn-default api-key__button"
title="Copy to clipboard"
disabled={!keyValue}
name={'copy_' + type + '_token'}
onClick={onCopyKey}
disabled={!keyValue}
title="Copy to clipboard"
type="button"
>
<i className="fa fa-clipboard"></i>
Expand Down Expand Up @@ -217,14 +212,14 @@ export const KeyGenerator: FC<KeyGeneratorProps> = props => {
<div className="top-padding form-group">
<button
className="btn btn-success api-key__button"
onClick={openModal}
disabled={showModal}
onClick={openModal}
type="button"
>
Generate {noun}
</button>
</div>
{showModal && <KeyGeneratorModal afterCreate={afterCreate} noun={noun} type={type} onClose={closeModal} />}
{showModal && <KeyGeneratorModal afterCreate={afterCreate} noun={noun} onClose={closeModal} type={type} />}
</div>
);
};
Expand All @@ -240,7 +235,7 @@ const SessionKeysSection: FC = memo(() => (
times out your session. Since they expire quickly, session keys are most appropriate for deployments with
regulatory compliance requirements.
</p>
<KeyGenerator type="session" noun="Session Key" />
<KeyGenerator noun="Session Key" type="session" />
</div>
));
SessionKeysSection.displayName = 'SessionKeysSection';
Expand All @@ -249,8 +244,22 @@ interface APIKeysGridProps {
includeSessionKeys?: boolean;
}

const APIKeysPanelGrid: FC<APIKeysGridProps & InjectedQueryModels> = props => {
const { actions, includeSessionKeys, queryModels } = props;
const APIKeysGrid: FC<APIKeysGridProps> = props => {
const { includeSessionKeys } = props;
const { homeContainer } = useServerContext();
const configs: QueryConfigMap = useMemo(
() => ({
model: {
id: 'model',
title: 'Current API Keys',
schemaQuery: SCHEMAS.CORE_TABLES.USER_API_KEYS,
includeTotalCount: true,
containerPath: homeContainer,
},
}),
[homeContainer]
);
const { actions, queryModels } = useQueryModels(configs, { autoLoad: true });
const { model } = queryModels;
const { moduleContext } = useServerContext();
const [error, setError] = useState<string>();
Expand All @@ -275,48 +284,34 @@ const APIKeysPanelGrid: FC<APIKeysGridProps & InjectedQueryModels> = props => {
<div className="api-keys-panel__grid">
<GridPanel
actions={actions}
model={model}
asPanel={false}
showSearchInput={false}
showFiltersButton={false}
showExport={false}
showChartMenu={false}
showViewMenu={false}
allowViewCustomization={false}
buttonsComponentProps={buttonsProps}
asPanel={false}
ButtonsComponent={APIKeysButtonsComponent}
buttonsComponentProps={buttonsProps}
emptyText="You currently do not have any API keys."
model={model}
showChartMenu={false}
showExport={false}
showFiltersButton={false}
showSearchInput={false}
showViewMenu={false}
/>
<Alert>{error}</Alert>

{apiEnabled && <KeyGenerator type="apikey" afterCreate={onApiKeyCreate} noun="API Key" />}
{apiEnabled && <KeyGenerator afterCreate={onApiKeyCreate} noun="API Key" type="apikey" />}

{sessionEnabled && includeSessionKeys && <SessionKeysSection />}
</div>
);
};
APIKeysPanelGrid.displayName = 'APIKeysPanelGrid';

const APIKeysPanelWithQueryModels = withQueryModels(APIKeysPanelGrid);
APIKeysGrid.displayName = 'APIKeysGrid';

export const APIKeysPanel: FC<APIKeysGridProps> = props => {
const { includeSessionKeys } = props;
const { homeContainer, impersonatingUser, moduleContext, user } = useServerContext();
const { impersonatingUser, moduleContext, user } = useServerContext();
const isImpersonating = !!impersonatingUser;
const apiEnabled = isApiKeyGenerationEnabled(moduleContext);
const sessionEnabled = isSessionKeyGenerationEnabled(moduleContext);
const configs: QueryConfigMap = useMemo(
() => ({
model: {
id: 'model',
title: 'Current API Keys',
schemaQuery: SCHEMAS.CORE_TABLES.USER_API_KEYS,
includeTotalCount: true,
containerPath: homeContainer,
},
}),
[homeContainer]
);

// We are meant to not show this panel for LKSM Starter, but show it in LKS and LKSM Prof+
if (isApp() && !isFeatureEnabled(ProductFeature.ApiKeys, moduleContext)) return null;
Expand Down Expand Up @@ -378,7 +373,7 @@ export const APIKeysPanel: FC<APIKeysGridProps> = props => {
{disabledMessage}
</Alert>

{!impersonatingUser && <APIKeysPanelWithQueryModels autoLoad queryConfigs={configs} {...props} />}
{!impersonatingUser && <APIKeysGrid includeSessionKeys={includeSessionKeys} />}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { SetURLSearchParams, useSearchParams } from 'react-router-dom';

import { getSelected } from '../../actions';

import { QueryModel, SavedSettings } from '../../../public/QueryModel/QueryModel';
import { ChangeType, InjectedQueryModels, QueryModel, SavedSettings } from '../../../public/QueryModel/QueryModel';
import { removeParameters } from '../../util/URL';

import { UserLimitSettings } from '../permissions/actions';
Expand All @@ -26,7 +26,7 @@ import { GridPanel } from '../../../public/QueryModel/GridPanel';
import { LoadingSpinner } from '../base/LoadingSpinner';
import { capitalizeFirstChar } from '../../util/utils';

import { ChangeType, InjectedQueryModels, withQueryModels } from '../../../public/QueryModel/withQueryModels';
import { withQueryModels } from '../../../public/QueryModel/withQueryModels';

import { MenuDivider, MenuItem } from '../../dropdowns';

Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/public/QueryModel/ChartMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { isChartBuilderEnabled } from '../../internal/app/utils';
import { ChartBuilderMenuItem } from '../../internal/components/chart/ChartBuilderMenuItem';
import { hasPermissions } from '../../internal/components/base/models/User';

import { RequiresModelAndActions } from './withQueryModels';
import { RequiresModelAndActions } from './QueryModel';
import { DisableableMenuItem } from '../../internal/components/samples/DisableableMenuItem';

const MAX_CHARTS = 5;
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/public/QueryModel/ChartPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { useServerContext } from '../../internal/components/base/ServerContext';

import { DropdownButton, MenuHeader, MenuItem } from '../../internal/dropdowns';

import { RequiresModelAndActions } from './withQueryModels';
import { RequiresModelAndActions } from './QueryModel';

interface Props extends RequiresModelAndActions {
api?: ChartAPIWrapper;
Expand Down
4 changes: 2 additions & 2 deletions packages/components/src/public/QueryModel/DetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { QueryColumn } from '../QueryColumn';
import { Alert } from '../../internal/components/base/Alert';
import { LoadingSpinner } from '../../internal/components/base/LoadingSpinner';

import { InjectedQueryModels, RequiresModelAndActions, withQueryModels } from './withQueryModels';
import { QueryConfig } from './QueryModel';
import { InjectedQueryModels, QueryConfig, RequiresModelAndActions } from './QueryModel';
import { withQueryModels } from './withQueryModels';

interface DetailPanelProps extends DetailDisplaySharedProps {
editColumns?: QueryColumn[];
Expand Down
Loading