diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md
index 01a4c29d42..e837d16659 100644
--- a/packages/components/releaseNotes/components.md
+++ b/packages/components/releaseNotes/components.md
@@ -1,6 +1,12 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages
+### version 7.X.0
+*Released*: ? February 2026
+- Remove GridAliquotViewSelector
+- Remove SampleAliquotViewSelector
+- Remove isSampleAliquotSelectorEnabled
+
### version 7.16.0
*Released*: 5 February 2026
- File import warnings for cross type sample import case
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index b0064baa26..9a644e7356 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -340,8 +340,6 @@ import {
} from './internal/components/entities/models';
import { EntityMoveModal } from './internal/components/entities/EntityMoveModal';
import { EntityMoveConfirmationModal } from './internal/components/entities/EntityMoveConfirmationModal';
-import { SampleAliquotViewSelector } from './internal/components/entities/SampleAliquotViewSelector';
-import { GridAliquotViewSelector } from './internal/components/entities/GridAliquotViewSelector';
import {
FindDerivativesButton,
FindDerivativesMenuItem,
@@ -733,7 +731,6 @@ import {
isProjectContainer,
isProtectedDataEnabled,
isRegistryEnabled,
- isSampleAliquotSelectorEnabled,
isSampleStatusEnabled,
isSharedContainer,
isSourceTypeEnabled,
@@ -939,7 +936,6 @@ const App = {
isPlatesEnabled,
isBiologicsEnabled,
isPremiumApplication,
- isSampleAliquotSelectorEnabled,
isProjectContainer,
isProtectedDataEnabled,
isDataChangeCommentRequirementFeatureEnabled,
@@ -1429,7 +1425,6 @@ export {
GlobalStateContextProvider,
Grid,
GRID_CHECKBOX_OPTIONS,
- GridAliquotViewSelector,
getTextAlignClassName,
GridColumn,
GridPanel,
@@ -1645,7 +1640,6 @@ export {
SAMPLE_TYPE_AUDIT_QUERY,
SAMPLE_TYPE_CONCEPT_URI,
SAMPLE_TYPE_DESIGNER_ROLE,
- SampleAliquotViewSelector,
SampleAmountEditModal,
sampleDeleteDependencyText,
SampleOperation,
diff --git a/packages/components/src/internal/app/constants.ts b/packages/components/src/internal/app/constants.ts
index 07131d83fd..3260c60813 100644
--- a/packages/components/src/internal/app/constants.ts
+++ b/packages/components/src/internal/app/constants.ts
@@ -111,7 +111,6 @@ export const SERVER_NOTIFICATION_MAX_ROWS = 8;
export const EXPERIMENTAL_PRODUCT_ALL_FOLDER_LOOKUPS = 'queryProductAllFolderLookups';
export const EXPERIMENTAL_PRODUCT_FOLDER_DATA_LISTING_SCOPED = 'queryProductProjectDataListingScoped';
export const EXPERIMENTAL_REQUESTS_MENU = 'experimental-biologics-requests-menu';
-export const EXPERIMENTAL_SAMPLE_ALIQUOT_SELECTOR = 'experimental-sample-aliquot-selector';
export const FOLDER_DATA_TYPE_EXCLUSIONS = 'dataTypeExclusions';
export const ARCHIVED_FOLDERS = 'archivedContainers';
diff --git a/packages/components/src/internal/app/utils.ts b/packages/components/src/internal/app/utils.ts
index daefea1f7f..ab948a1f6f 100644
--- a/packages/components/src/internal/app/utils.ts
+++ b/packages/components/src/internal/app/utils.ts
@@ -27,7 +27,6 @@ import {
EXPERIMENTAL_PRODUCT_ALL_FOLDER_LOOKUPS,
EXPERIMENTAL_PRODUCT_FOLDER_DATA_LISTING_SCOPED,
EXPERIMENTAL_REQUESTS_MENU,
- EXPERIMENTAL_SAMPLE_ALIQUOT_SELECTOR,
FOLDER_DATA_TYPE_EXCLUSIONS,
FREEZER_MANAGER_APP_PROPERTIES,
FREEZERS_KEY,
@@ -419,10 +418,6 @@ export function isFeatureEnabled(flag: ProductFeature, moduleContext?: ModuleCon
return resolveModuleContext(moduleContext)?.core?.productFeatures?.indexOf(flag) >= 0;
}
-export function isSampleAliquotSelectorEnabled(moduleContext?: ModuleContext): boolean {
- return resolveModuleContext(moduleContext)?.samplemanagement?.[EXPERIMENTAL_SAMPLE_ALIQUOT_SELECTOR] === true;
-}
-
export function hasModule(moduleName: string, moduleContext?: ModuleContext): boolean {
return resolveModuleContext(moduleContext).api?.moduleNames?.indexOf(moduleName.toLowerCase()) >= 0;
}
diff --git a/packages/components/src/internal/components/entities/GridAliquotViewSelector.test.tsx b/packages/components/src/internal/components/entities/GridAliquotViewSelector.test.tsx
deleted file mode 100644
index 5fe9523703..0000000000
--- a/packages/components/src/internal/components/entities/GridAliquotViewSelector.test.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-import { Filter } from '@labkey/api';
-
-import { makeTestQueryModel } from '../../../public/QueryModel/testUtils';
-import { SchemaQuery } from '../../../public/SchemaQuery';
-import { IS_ALIQUOT_COL } from '../samples/constants';
-
-import { GridAliquotViewSelector } from './GridAliquotViewSelector';
-
-describe('', () => {
- beforeEach(() => {
- LABKEY.moduleContext = { samplemanagement: { 'experimental-sample-aliquot-selector': true } };
- });
-
- test('no queryModel', () => {
- render();
- expect(document.querySelector('.aliquot-view-selector')).not.toBeInTheDocument();
- });
-
- test('experimental flag disabled', () => {
- LABKEY.moduleContext = { samplemanagement: { 'experimental-sample-aliquot-selector': false } };
- const model = makeTestQueryModel(new SchemaQuery('a', 'b'));
- render();
- expect(document.querySelector('.aliquot-view-selector')).not.toBeInTheDocument();
- });
-
- // Note: this method is identical to the one for SampleAliquotViewSelector, so it feels like we're mostly just
- // testing that component again. Is there something more specific to GridAliquotViewSelector that we should be
- // testing?
- function verifyOptions(all?: boolean, samples?: boolean, aliquots?: boolean) {
- const items = document.querySelectorAll('.lk-menu-item');
- const buttonText = document.querySelector('.dropdown-toggle').textContent;
- expect(items).toHaveLength(3);
- expect(document.querySelector('.dropdown-header')).toHaveTextContent('Show Samples');
- expect(items[0]).toHaveTextContent('Samples and Aliquots');
- expect(items[1]).toHaveTextContent('Samples Only');
- expect(items[2]).toHaveTextContent('Aliquots Only');
-
- if (all) {
- expect(items[0].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('All Samples');
- } else {
- expect(items[0].getAttribute('class')).not.toContain('active');
- }
-
- if (samples) {
- expect(items[1].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('Samples Only');
- } else {
- expect(items[1].getAttribute('class')).not.toContain('active');
- }
-
- if (aliquots) {
- expect(items[2].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('Aliquots Only');
- } else {
- expect(items[2].getAttribute('class')).not.toContain('active');
- }
- }
-
- test('with queryModel, without filter', () => {
- const model = makeTestQueryModel(new SchemaQuery('a', 'b'));
- render();
- verifyOptions(true);
- });
-
- test('with queryModel, filtered to aliquots only', () => {
- let model = makeTestQueryModel(new SchemaQuery('a', 'b'));
- model = model.mutate({
- filterArray: [Filter.create(IS_ALIQUOT_COL, true)],
- });
- render();
-
- verifyOptions(false, false, true);
- });
-
- test('with queryModel, filtered to samples only', () => {
- let model = makeTestQueryModel(new SchemaQuery('a', 'b'));
- model = model.mutate({
- filterArray: [Filter.create(IS_ALIQUOT_COL, false)],
- });
- render();
-
- verifyOptions(false, true);
- });
-});
diff --git a/packages/components/src/internal/components/entities/GridAliquotViewSelector.tsx b/packages/components/src/internal/components/entities/GridAliquotViewSelector.tsx
deleted file mode 100644
index 84f866cbce..0000000000
--- a/packages/components/src/internal/components/entities/GridAliquotViewSelector.tsx
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (c) 2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { Component, ReactNode } from 'react';
-
-import { Filter } from '@labkey/api';
-
-import { isSampleAliquotSelectorEnabled } from '../../app/utils';
-import { ALIQUOT_FILTER_MODE, IS_ALIQUOT_COL } from '../samples/constants';
-import { Actions } from '../../../public/QueryModel/withQueryModels';
-import { QueryModel } from '../../../public/QueryModel/QueryModel';
-
-import { SampleAliquotViewSelector } from './SampleAliquotViewSelector';
-
-interface Props {
- actions?: Actions;
- initAliquotMode?: ALIQUOT_FILTER_MODE; // allow to set aliquot filter from a init value
- queryModel?: QueryModel;
- updateFilter?: (filter: Filter.IFilter, newModel: ALIQUOT_FILTER_MODE) => void;
-}
-
-interface State {
- initAliquotModeSynced: boolean;
-}
-
-export class GridAliquotViewSelector extends Component {
- state: Readonly = {
- initAliquotModeSynced: false,
- };
-
- componentDidMount(): void {
- this.syncInitMode();
- }
-
- componentDidUpdate = (): void => {
- this.syncInitMode();
- };
-
- syncInitMode = () => {
- const { initAliquotMode, queryModel } = this.props;
- const { initAliquotModeSynced } = this.state;
- // if bindURL = true, rely on url param to set aliquot filter and ignore the initAliquotMode prop
- if (!initAliquotModeSynced && initAliquotMode && !queryModel.bindURL && !queryModel?.isLoading) {
- const currentMode = this.getAliquotFilterMode();
- if (initAliquotMode != currentMode) this.updateAliquotFilter(initAliquotMode);
- this.setState(() => ({ initAliquotModeSynced: true }));
- }
- };
-
- getAliquotColName = (): string => {
- // account for the case where the aliquot column is in the queryModel via a lookup from the sample ID
- const { queryModel } = this.props;
- let aliquotColName;
- if (!queryModel.getColumnByFieldKey(IS_ALIQUOT_COL)) {
- aliquotColName = queryModel.allColumns?.find(
- c => c.fieldKey.toLowerCase().indexOf('/' + IS_ALIQUOT_COL.toLowerCase()) > -1
- )?.fieldKey;
- }
-
- return aliquotColName ?? IS_ALIQUOT_COL;
- };
-
- updateAliquotFilter = (newMode: ALIQUOT_FILTER_MODE): void => {
- const { queryModel, actions, updateFilter } = this.props;
- const aliquotColName = this.getAliquotColName();
-
- let newFilter: Filter.IFilter;
- if (newMode === ALIQUOT_FILTER_MODE.all || newMode === ALIQUOT_FILTER_MODE.none) {
- newFilter = null;
- } else {
- newFilter = Filter.create(aliquotColName, newMode === ALIQUOT_FILTER_MODE.aliquots);
- }
-
- if (updateFilter) {
- updateFilter(newFilter, newMode);
- return;
- }
-
- if (queryModel.queryInfo && actions) {
- // keep any existing filters that do not match the aliquot column name
- const updatedFilters = queryModel.filterArray.filter(filter => {
- return aliquotColName.toLowerCase() !== filter.getColumnName().toLowerCase();
- });
-
- if (newFilter) updatedFilters.push(newFilter);
-
- actions.setFilters(queryModel.id, updatedFilters, true);
- }
- };
-
- getAliquotFilterMode = (): ALIQUOT_FILTER_MODE => {
- const { queryModel } = this.props;
- let mode = ALIQUOT_FILTER_MODE.all;
- const filterArray = queryModel?.filterArray;
- if (filterArray) {
- const aliquotColName = this.getAliquotColName();
-
- filterArray.forEach(filter => {
- if (filter.getColumnName().toLowerCase() === aliquotColName?.toLowerCase()) {
- const filterType = filter.getFilterType();
- const value = filter.getValue();
-
- if (filterType == Filter.Types.ISBLANK || filterType == Filter.Types.MISSING) {
- mode = ALIQUOT_FILTER_MODE.none;
- } else if (filterType == Filter.Types.EQUAL || filterType.getURLSuffix() == 'eq') {
- if (value === '') return;
- mode =
- value === 'true' || value === true
- ? ALIQUOT_FILTER_MODE.aliquots
- : ALIQUOT_FILTER_MODE.samples;
- } else if (
- filterType == Filter.Types.NOT_EQUAL ||
- filterType == Filter.Types.NEQ ||
- filterType == Filter.Types.NEQ_OR_NULL
- ) {
- if (value === '') return;
- mode =
- value === 'true' || value === true
- ? ALIQUOT_FILTER_MODE.samples
- : ALIQUOT_FILTER_MODE.aliquots;
- }
- }
- });
- }
-
- return mode;
- };
-
- render(): ReactNode {
- const { queryModel } = this.props;
-
- if (!queryModel) return null;
-
- if (!isSampleAliquotSelectorEnabled()) return null;
-
- return (
-
- );
- }
-}
diff --git a/packages/components/src/internal/components/entities/SampleAliquotViewSelector.test.tsx b/packages/components/src/internal/components/entities/SampleAliquotViewSelector.test.tsx
deleted file mode 100644
index c2d9afa019..0000000000
--- a/packages/components/src/internal/components/entities/SampleAliquotViewSelector.test.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import { render } from '@testing-library/react';
-
-import { ALIQUOT_FILTER_MODE } from '../samples/constants';
-
-import { SampleAliquotViewSelector } from './SampleAliquotViewSelector';
-
-describe('', () => {
- function verifyOptions(all?: boolean, samples?: boolean, aliquots?: boolean) {
- const buttonText = document.querySelector('.dropdown-toggle').textContent;
- const items = document.querySelectorAll('.lk-menu-item');
- expect(items).toHaveLength(3);
- expect(document.querySelector('.dropdown-header')).toHaveTextContent('Show Samples');
- expect(items[0]).toHaveTextContent('Samples and Aliquots');
- expect(items[1]).toHaveTextContent('Samples Only');
- expect(items[2]).toHaveTextContent('Aliquots Only');
-
- if (all) {
- expect(items[0].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('All Samples');
- } else {
- expect(items[0].getAttribute('class')).not.toContain('active');
- }
-
- if (samples) {
- expect(items[1].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('Samples Only');
- } else {
- expect(items[1].getAttribute('class')).not.toContain('active');
- }
-
- if (aliquots) {
- expect(items[2].getAttribute('class')).toContain('active');
- expect(buttonText).toEqual('Aliquots Only');
- } else {
- expect(items[2].getAttribute('class')).not.toContain('active');
- }
- }
-
- test('aliquotFilterMode undefined', () => {
- render(
-
- );
-
- verifyOptions(true);
- });
-
- test('aliquotFilterMode: all', () => {
- render(
-
- );
-
- verifyOptions(true);
- });
-
- test('aliquotFilterMode: samples', () => {
- render(
-
- );
-
- verifyOptions(false, true);
- });
-
- test('aliquotFilterMode: aliquots', () => {
- render(
-
- );
- verifyOptions(false, false, true);
- });
-
- test('customized labels', () => {
- render(
-
- );
-
- expect(document.querySelector('.dropdown-header')).toHaveTextContent('Show Jobs with Samples');
-
- const items = document.querySelectorAll('.lk-menu-item');
- expect(items).toHaveLength(3);
- expect(items[0]).toHaveTextContent('Parent Sample and Aliquots');
- expect(items[1]).toHaveTextContent('Parent Sample Only');
- expect(items[2]).toHaveTextContent('Aliquots Only');
- });
-});
diff --git a/packages/components/src/internal/components/entities/SampleAliquotViewSelector.tsx b/packages/components/src/internal/components/entities/SampleAliquotViewSelector.tsx
deleted file mode 100644
index d5e0a4c49a..0000000000
--- a/packages/components/src/internal/components/entities/SampleAliquotViewSelector.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { FC, useCallback } from 'react';
-
-import { ALIQUOT_FILTER_MODE } from '../samples/constants';
-import { DropdownButton, MenuHeader, MenuItem } from '../../dropdowns';
-
-interface ViewMenuItemProps {
- currentFilterMode: ALIQUOT_FILTER_MODE;
- filterMode: ALIQUOT_FILTER_MODE;
- label: string;
- onClick: (filterMode: ALIQUOT_FILTER_MODE) => void;
-}
-
-const ViewMenuItem: FC = ({ currentFilterMode, filterMode, label, onClick }) => {
- const onClick_ = useCallback(() => onClick(filterMode), [filterMode, onClick]);
- return (
-
- );
-};
-ViewMenuItem.displayName = 'ViewMenuItem';
-
-interface Props {
- aliquotFilterMode: ALIQUOT_FILTER_MODE;
- aliquotsLabel?: string;
- allLabel?: string;
- headerLabel?: string;
- samplesLabel?: string;
- updateAliquotFilter: (newMode: ALIQUOT_FILTER_MODE) => void;
-}
-
-export const SampleAliquotViewSelector: FC = props => {
- const {
- aliquotsLabel = 'Aliquots Only',
- aliquotFilterMode = ALIQUOT_FILTER_MODE.all,
- allLabel = 'Samples and Aliquots',
- headerLabel = 'Show Samples',
- samplesLabel = 'Samples Only',
- updateAliquotFilter,
- } = props;
- let title = 'All Samples';
-
- if (aliquotFilterMode === ALIQUOT_FILTER_MODE.samples) {
- title = samplesLabel;
- } else if (aliquotFilterMode === ALIQUOT_FILTER_MODE.aliquots) {
- title = aliquotsLabel;
- } else if (aliquotFilterMode === ALIQUOT_FILTER_MODE.none) {
- title = 'None';
- }
-
- return (
-
-
-
-
-
-
- );
-};
-SampleAliquotViewSelector.displayName = 'SampleAliquotViewSelector';