diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md
index 3babdfdd16..0aea45adb4 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.x.x
+*Released*: ? April 2026
+- Add EMPTY_COMPOUND_WARNING
+- Add COMPOUND to SCHEMAS.DATA_CLASSES
+- hasIdentifiedCol: check for Compound schema
+- UnidentifiedPill: handle schemas that don't have defined messages, add support for Compound schema
+
### version 7.30.0
*Released*: 17 April 2026
- GitHub Issue 848: Add the ability to reset TOTP settings in the apps
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 4c01381ea3..0f0f549394 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -250,6 +250,7 @@ import {
AssayUploadTabs,
DataViewInfoTypes,
EDIT_METHOD,
+ EMPTY_COMPOUND_WARNING,
EMPTY_NS_SEQUENCE_WARNING,
EXPORT_TYPES,
GRID_CHECKBOX_OPTIONS,
@@ -1271,6 +1272,7 @@ export {
EditInlineField,
EditorMode,
EditorModel,
+ EMPTY_COMPOUND_WARNING,
EMPTY_NS_SEQUENCE_WARNING,
encodePart,
ensureAllFieldsInAllRows,
@@ -1354,8 +1356,8 @@ export {
getEntityTypeOptions,
getEventDataValueDisplay,
getExcludedDataTypeNames,
- getFieldDisplayValue,
getExpandQueryInfo,
+ getFieldDisplayValue,
getFieldFiltersValidationResult,
getFilterForSampleOperation,
getFilterLabKeySql,
diff --git a/packages/components/src/internal/UnidentifiedPill.test.tsx b/packages/components/src/internal/UnidentifiedPill.test.tsx
new file mode 100644
index 0000000000..24b1cd60b3
--- /dev/null
+++ b/packages/components/src/internal/UnidentifiedPill.test.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { SchemaQuery } from '../public/SchemaQuery';
+import { EMPTY_COMPOUND_WARNING, EMPTY_NS_SEQUENCE_WARNING, EMPTY_PS_SEQUENCE_WARNING } from './constants';
+import { SCHEMAS } from './schemas';
+import { UnidentifiedPill } from './UnidentifiedPill';
+
+describe('UnidentifiedPill', () => {
+ test('renders with correct CSS classes', () => {
+ const { container } = render();
+ const pill = container.querySelector('.unidentified-sequence-pill');
+ expect(pill).toBeInTheDocument();
+ expect(pill).toHaveClass('status-pill', 'info');
+ });
+
+ test('always renders Unidentified text', () => {
+ const { container } = render();
+ expect(container.querySelector('.unidentified-sequence-pill')).toHaveTextContent('Unidentified');
+ });
+
+ describe('question mark icon', () => {
+ test('renders for PROTEIN_SEQUENCE schema', () => {
+ const { container } = render();
+ expect(container.querySelector('.fa-question-circle')).toBeInTheDocument();
+ });
+
+ test('renders for NUC_SEQUENCE schema', () => {
+ const { container } = render();
+ expect(container.querySelector('.fa-question-circle')).toBeInTheDocument();
+ });
+
+ test('renders for COMPOUND schema', () => {
+ const { container } = render();
+ expect(container.querySelector('.fa-question-circle')).toBeInTheDocument();
+ });
+
+ test('does not render for unrecognized schema', () => {
+ const { container } = render();
+ expect(container.querySelector('.fa-question-circle')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('popover on hover', () => {
+ test('shows EMPTY_PS_SEQUENCE_WARNING for PROTEIN_SEQUENCE', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ await user.hover(container.querySelector('.unidentified-sequence-pill'));
+ expect(screen.getByText(EMPTY_PS_SEQUENCE_WARNING)).toBeInTheDocument();
+ });
+
+ test('shows EMPTY_NS_SEQUENCE_WARNING for NUC_SEQUENCE', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ await user.hover(container.querySelector('.unidentified-sequence-pill'));
+ expect(screen.getByText(EMPTY_NS_SEQUENCE_WARNING)).toBeInTheDocument();
+ });
+
+ test('shows EMPTY_COMPOUND_WARNING for COMPOUND', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ await user.hover(container.querySelector('.unidentified-sequence-pill'));
+ expect(screen.getByText(EMPTY_COMPOUND_WARNING)).toBeInTheDocument();
+ });
+
+ test('does not show popover for unrecognized schema', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ await user.hover(container.querySelector('.unidentified-sequence-pill'));
+ expect(document.querySelector('.unidentified-sequence-popover')).not.toBeInTheDocument();
+ });
+
+ test('hides popover when mouse leaves', async () => {
+ const user = userEvent.setup();
+ const { container } = render();
+ const pill = container.querySelector('.unidentified-sequence-pill');
+ await user.hover(pill);
+ expect(screen.getByText(EMPTY_PS_SEQUENCE_WARNING)).toBeInTheDocument();
+ await user.unhover(pill);
+ expect(screen.queryByText(EMPTY_PS_SEQUENCE_WARNING)).not.toBeInTheDocument();
+ });
+ });
+});
diff --git a/packages/components/src/internal/UnidentifiedPill.tsx b/packages/components/src/internal/UnidentifiedPill.tsx
index a65b37bd99..7e28f57625 100644
--- a/packages/components/src/internal/UnidentifiedPill.tsx
+++ b/packages/components/src/internal/UnidentifiedPill.tsx
@@ -3,10 +3,22 @@ import { createPortal } from 'react-dom';
import { generateId } from './util/utils';
import { useOverlayTriggerState } from './OverlayTrigger';
import { Popover } from './Popover';
-import { EMPTY_NS_SEQUENCE_WARNING, EMPTY_PS_SEQUENCE_WARNING } from './constants';
+import { EMPTY_COMPOUND_WARNING, EMPTY_NS_SEQUENCE_WARNING, EMPTY_PS_SEQUENCE_WARNING } from './constants';
import { SchemaQuery } from '../public/SchemaQuery';
import { SCHEMAS } from './schemas';
+function getPopoverMessage(schemaQuery: SchemaQuery): string | undefined {
+ if (schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.PROTEIN_SEQUENCE, false)) {
+ return EMPTY_PS_SEQUENCE_WARNING;
+ } else if (schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.NUC_SEQUENCE, false)) {
+ return EMPTY_NS_SEQUENCE_WARNING;
+ } else if (schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.COMPOUND, false)) {
+ return EMPTY_COMPOUND_WARNING;
+ }
+
+ return undefined;
+}
+
interface Props {
schemaQuery: SchemaQuery;
}
@@ -16,9 +28,7 @@ export const UnidentifiedPill: FC = ({ schemaQuery }) => {
// Note: we use useOverlayTriggerState instead of OverlayTrigger because the wrapping div from OverlayTrigger
// causes layout problems.
const { onMouseEnter, onMouseLeave, portalEl, show, targetRef } = useOverlayTriggerState(id, true, false);
- const message = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.PROTEIN_SEQUENCE, false)
- ? EMPTY_PS_SEQUENCE_WARNING
- : EMPTY_NS_SEQUENCE_WARNING;
+ const message = getPopoverMessage(schemaQuery);
const popover = useMemo(
() => (
@@ -37,8 +47,8 @@ export const UnidentifiedPill: FC = ({ schemaQuery }) => {
ref={targetRef}
>
Unidentified
-
- {show && createPortal(popover, portalEl)}
+ {message && }
+ {show && message && createPortal(popover, portalEl)}
);
};
diff --git a/packages/components/src/internal/constants.ts b/packages/components/src/internal/constants.ts
index 7b9684f115..c6802553e3 100644
--- a/packages/components/src/internal/constants.ts
+++ b/packages/components/src/internal/constants.ts
@@ -265,3 +265,5 @@ export const EMPTY_NS_SEQUENCE_WARNING =
'Without a sequence, Protein sequence translations cannot be done automatically, and the system cannot prevent duplicates.';
export const EMPTY_PS_SEQUENCE_WARNING =
'No sequence added. The structure format and physical properties of molecules using this sequence cannot be calculated.';
+export const EMPTY_COMPOUND_WARNING =
+ 'Without SMILES, Molecule component translation cannot be done automatically, and the system cannot prevent duplicates.';
diff --git a/packages/components/src/internal/schemas.ts b/packages/components/src/internal/schemas.ts
index 3250fcab20..62fa907eef 100644
--- a/packages/components/src/internal/schemas.ts
+++ b/packages/components/src/internal/schemas.ts
@@ -66,6 +66,7 @@ export const DATA_CLASSES = {
SCHEMA: DATA_CLASS_SCHEMA,
CELL_LINE: new SchemaQuery(DATA_CLASS_SCHEMA, 'CellLine'),
CONSTRUCT: new SchemaQuery(DATA_CLASS_SCHEMA, 'Construct'),
+ COMPOUND: new SchemaQuery(DATA_CLASS_SCHEMA, 'Compound'),
EXPRESSION_SYSTEM: new SchemaQuery(DATA_CLASS_SCHEMA, 'ExpressionSystem'),
MOLECULE: new SchemaQuery(DATA_CLASS_SCHEMA, 'Molecule'),
MOLECULE_SET: new SchemaQuery(DATA_CLASS_SCHEMA, 'MoleculeSet'),
diff --git a/packages/components/src/internal/util/utils.ts b/packages/components/src/internal/util/utils.ts
index 9885a16695..070d80500e 100644
--- a/packages/components/src/internal/util/utils.ts
+++ b/packages/components/src/internal/util/utils.ts
@@ -919,5 +919,6 @@ export function hasIdentifiedCol(schemaQuery: SchemaQuery): boolean {
const isNucSeq = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.NUC_SEQUENCE, false);
const isProtSeq = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.PROTEIN_SEQUENCE, false);
const isMolecule = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.MOLECULE, false);
- return isNucSeq || isProtSeq || isMolecule;
+ const isCompound = schemaQuery.isEqual(SCHEMAS.DATA_CLASSES.COMPOUND, false);
+ return isNucSeq || isProtSeq || isMolecule || isCompound;
}