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
3b240ca
withQueryModels: Move types to QueryModel and utils to utils.ts
labkey-alan Apr 18, 2026
fc538eb
useQueryModels: stub implementation
labkey-alan Apr 18, 2026
d706d51
QueryModel/utils.ts - Add bindURL
labkey-alan Apr 18, 2026
371cc4d
QueryModelManager - implement addModel, maybeLoad, updateModelsFromUR…
labkey-alan Apr 18, 2026
a812f8c
QueryModelManager - implement pagination methods, setSorts, setMaxRow…
labkey-alan Apr 18, 2026
e50dc52
QueryModelLoader: add loadTotalCount
labkey-alan Apr 18, 2026
ae9dceb
QueryModelManager: implement loadAllModels, loadCharts, loadModel, lo…
labkey-alan Apr 18, 2026
245696a
QueryModelManager: implement loadTotalCount, loadSelections, loadRows
labkey-alan Apr 18, 2026
00da731
QueryModelManager: implement replaceSelections, resetTotalCountState,…
labkey-alan Apr 18, 2026
52f5682
QueryModelManager: implement clearSelections, onModelChange, selectRo…
labkey-alan Apr 18, 2026
a35f87c
Add useQueryModels.test.tsx
labkey-alan Apr 18, 2026
d5fe87e
Move RequestManager tests to utils.test.ts
labkey-alan Apr 18, 2026
bfc0efe
Cleanup
labkey-alan Apr 18, 2026
714651c
Only call saveSettings/syncURL if needed
labkey-alan Apr 20, 2026
416267c
useQueryModels: add docstring
labkey-alan Apr 20, 2026
37c3d91
useQueryModels.test.tsx - Remove usages of sleep
labkey-alan Apr 20, 2026
0e273ac
APIKeysPanel: use useQueryModels
labkey-alan Apr 20, 2026
9eff73f
Actions: remove setSchemaQuery
labkey-alan Apr 20, 2026
1be6ab6
withQueryModels: use useQueryModels (TEMPORARY CHANGE)
labkey-alan Apr 20, 2026
1c3803e
useQueryModels: default queryConfigs to {}
labkey-alan Apr 25, 2026
4ab6af6
Remove resetQueryInfoState - it is unused
labkey-alan Apr 27, 2026
31f5580
QueryModel: make selections default to empty Set
labkey-alan Apr 27, 2026
faf4d1c
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.33.4
*Released*: 3 May 2026
- Consolidate Dataclass data update methods - use DIB for update only
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 @@ -624,9 +624,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 @@ -1804,6 +1804,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 @@ -1932,18 +1937,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 @@ -18,7 +18,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
@@ -1,6 +1,6 @@
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 @@ -8,8 +8,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 @@ -4,7 +4,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 @@ -2,7 +2,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 @@ -8,8 +8,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 @@ -7,7 +7,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 @@ -13,7 +13,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 @@ -13,7 +13,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 @@ -22,8 +22,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