diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 161ab60f06..f8213951c2 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "6.64.1", + "version": "6.64.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "6.64.1", + "version": "6.64.2", "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 5a994324eb..01ba679eb6 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "6.64.1", + "version": "6.64.2", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 2df555e130..f3fa59c2b2 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,11 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 6.64.2 +*Released*: 10 October 2025 +- Issue 52878: Attachment thumbnails from ancestor columns not rendered in Sample Type grid + - Modify `AncestorRenderer` and `MultiValueRenderer` to handle file/attachment columns + ### version 6.64.1 *Released*: 9 October 2025 - Issue 53997: Establish a maximum size for query selections diff --git a/packages/components/src/internal/components/samples/actions.ts b/packages/components/src/internal/components/samples/actions.ts index 8006b6538b..e802a77613 100644 --- a/packages/components/src/internal/components/samples/actions.ts +++ b/packages/components/src/internal/components/samples/actions.ts @@ -216,6 +216,7 @@ export function getSelectionLineageData( export interface GroupedSampleDisplayColumns { aliquotHeaderDisplayColumns: QueryColumn[]; + aliquotOnlyColumns: string[]; // should hide from parent panel displayColumns: QueryColumn[]; editColumns: QueryColumn[]; } @@ -239,6 +240,7 @@ export function getGroupedSampleDisplayColumns( const editColumns = []; const displayColumns = []; const aliquotHeaderDisplayColumns = []; + const aliquotOnlyColumns = []; allDisplayColumns.forEach(col => { const lcFieldKey = col.fieldKey.toLowerCase(); @@ -259,6 +261,8 @@ export function getGroupedSampleDisplayColumns( sampleTypeDomainFields.independentFields.indexOf(lcFieldKey) > -1 ) { aliquotHeaderDisplayColumns.push(col); + if (sampleTypeDomainFields.aliquotFields.indexOf(lcFieldKey) > -1) + aliquotOnlyColumns.push(col.fieldKey); } } else { if (sampleTypeDomainFields.aliquotFields.indexOf(lcFieldKey) === -1) { @@ -294,6 +298,7 @@ export function getGroupedSampleDisplayColumns( return { aliquotHeaderDisplayColumns, + aliquotOnlyColumns, displayColumns, editColumns, }; diff --git a/packages/components/src/internal/renderers/AncestorRenderer.test.tsx b/packages/components/src/internal/renderers/AncestorRenderer.test.tsx index 1a18d5387e..6d3c12baff 100644 --- a/packages/components/src/internal/renderers/AncestorRenderer.test.tsx +++ b/packages/components/src/internal/renderers/AncestorRenderer.test.tsx @@ -4,6 +4,7 @@ import { Map } from 'immutable'; import { render } from '@testing-library/react'; import { AncestorRenderer } from './AncestorRenderer'; +import { QueryColumn } from '../../public/QueryColumn'; describe('AncestorRenderer', () => { test('No data', () => { @@ -28,6 +29,18 @@ describe('AncestorRenderer', () => { expect(links).toHaveLength(1); expect(links[0].getAttribute('href')).toBe(data.url); expect(links[0].textContent).toBe(data.displayValue); + expect(document.querySelectorAll('.attachment-card')).toHaveLength(0); + }); + + test('positive value, and file type column', () => { + const data = { + value: 'a.txt', + displayValue: 'a.txt', + url: 'http://samples.org/Sample-123', + }; + render(); + expect(document.querySelectorAll('span.text-muted')).toHaveLength(0); + expect(document.querySelectorAll('.attachment-card')).toHaveLength(1); }); test('negative value', () => { diff --git a/packages/components/src/internal/renderers/AncestorRenderer.tsx b/packages/components/src/internal/renderers/AncestorRenderer.tsx index a1e495cabf..bf6af3cd88 100644 --- a/packages/components/src/internal/renderers/AncestorRenderer.tsx +++ b/packages/components/src/internal/renderers/AncestorRenderer.tsx @@ -21,10 +21,11 @@ import { DefaultRenderer } from './DefaultRenderer'; export const ANCESTOR_LOOKUP_CONCEPT_URI = 'http://www.labkey.org/types#ancestorLookup'; interface Props { + col?: any; data: Map; } -export const AncestorRenderer: FC = memo(({ data }) => { +export const AncestorRenderer: FC = memo(({ data, col }) => { if (Map.isMap(data) && data.size > 0) { const { displayValue, value } = data.toJS(); if (value < 0 && displayValue) { @@ -35,7 +36,7 @@ export const AncestorRenderer: FC = memo(({ data }) => { ); } - return ; + return ; } return null; diff --git a/packages/components/src/internal/renderers/DefaultRenderer.tsx b/packages/components/src/internal/renderers/DefaultRenderer.tsx index 76dbbb12e1..4c34ad9057 100644 --- a/packages/components/src/internal/renderers/DefaultRenderer.tsx +++ b/packages/components/src/internal/renderers/DefaultRenderer.tsx @@ -24,6 +24,7 @@ import { isConditionalFormattingEnabled } from '../app/utils'; import { AppLink } from '../url/AppLink'; import { MultiValueRenderer } from './MultiValueRenderer'; +import { FileColumnRenderer } from './FileColumnRenderer'; interface Props { col?: QueryColumn; @@ -54,8 +55,11 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { display = data ? 'true' : 'false'; } else if (List.isList(data)) { // defensively return a MultiValueRenderer, this column likely wasn't declared properly as "multiValue" - return ; - } else { + return ; + } else if (col?.isFileInput) { + return ; + } + else { if (isConditionalFormattingEnabled()) { style = getDataStyling(data); if (style?.backgroundColor) { @@ -74,7 +78,7 @@ export const DefaultRenderer: FC = memo(({ col, data, noLink }) => { if (url && !noLink) { const targetBlank = data.get('urlTarget') === TARGET_BLANK; return ( - + {display} ); diff --git a/packages/components/src/internal/renderers/FileColumnRenderer.tsx b/packages/components/src/internal/renderers/FileColumnRenderer.tsx index 8cb79aca74..88778d82e6 100644 --- a/packages/components/src/internal/renderers/FileColumnRenderer.tsx +++ b/packages/components/src/internal/renderers/FileColumnRenderer.tsx @@ -56,11 +56,11 @@ export const getAttachmentCardProp = ( let url, value, display; if (Iterable.isIterable(data)) { url = data.get('url'); - value = data.get('value'); + value = data.get('value')?.toString(); display = getFileDisplayValue(data.get('displayValue') ?? value); } else { url = caseInsensitive(data, 'url'); - value = caseInsensitive(data, 'value'); + value = caseInsensitive(data, 'value')?.toString(); display = getFileDisplayValue(caseInsensitive(data, 'displayValue') ?? value); } const titleStyle = isConditionalFormattingEnabled() ? getDataStyling(data) : undefined; diff --git a/packages/components/src/internal/renderers/MultiValueRenderer.test.tsx b/packages/components/src/internal/renderers/MultiValueRenderer.test.tsx index 3e3a112be0..c73dc6a20d 100644 --- a/packages/components/src/internal/renderers/MultiValueRenderer.test.tsx +++ b/packages/components/src/internal/renderers/MultiValueRenderer.test.tsx @@ -4,6 +4,7 @@ import { fromJS, Map } from 'immutable'; import { render } from '@testing-library/react'; import { MultiValueRenderer } from './MultiValueRenderer'; +import { QueryColumn } from '../../public/QueryColumn'; describe('MultiValueRenderer', () => { test('empty data', () => { @@ -23,6 +24,20 @@ describe('MultiValueRenderer', () => { expect(document.body.textContent).toBe('24'); }); + test('data shapes, value, file column', () => { + const data = fromJS({ 24: { value: 'a.txt', url: 'a.txt' } }); + render(); + expect(document.body.textContent).toBe('a.txt'); + expect(document.querySelectorAll('.attachment-card')).toHaveLength(0); + }); + + test('data list, value, file column', () => { + const data = fromJS([{ value: 'a.txt', url: 'a.txt' }]); + render(); + expect(document.body.textContent).toBe('a.txtDownload'); + expect(document.querySelectorAll('.attachment-card')).toHaveLength(1); + }); + test('data shapes, displayValue', () => { const data = fromJS({ 24: { displayValue: 'Griffey', value: 24 } }); render(); @@ -59,6 +74,26 @@ describe('MultiValueRenderer', () => { expect(link[0].getAttribute('href')).toEqual('https://www.mariners.com/ichiro'); }); + test('multiple values, file column', () => { + const data = fromJS({ + 11: { value: 'a.txt', url: 'a.txt' }, + 24: { value: 'b.txt', url: 'b.txt' }, + 51: { value: 'c.txt', url: 'c.txt' }, + }); + render(); + const spans = document.querySelectorAll('span'); + expect(spans.length).toBe(3); + expect(spans[0].textContent).toEqual('a.txt'); + expect(spans[1].textContent).toEqual(', b.txt'); + expect(spans[2].textContent).toEqual(', c.txt'); + + const link = spans[2].querySelectorAll('a'); + expect(link).toHaveLength(1); + expect(link[0].getAttribute('href')).toEqual('c.txt'); + + expect(document.querySelectorAll('.attachment-card')).toHaveLength(0); + }); + test('non-Map values', () => { const data = Map({ 11: [], diff --git a/packages/components/src/internal/renderers/MultiValueRenderer.tsx b/packages/components/src/internal/renderers/MultiValueRenderer.tsx index 0c528f4a62..58382d7b57 100644 --- a/packages/components/src/internal/renderers/MultiValueRenderer.tsx +++ b/packages/components/src/internal/renderers/MultiValueRenderer.tsx @@ -14,17 +14,28 @@ * limitations under the License. */ import React, { FC, Fragment, memo, ReactNode } from 'react'; -import { Map } from 'immutable'; +import { List, Map } from 'immutable'; +import { QueryColumn } from '../../public/QueryColumn'; +import { FileColumnRenderer } from './FileColumnRenderer'; export interface MultiValueRendererProps { + col?: QueryColumn; data: Map; } -export const MultiValueRenderer: FC = memo(({ data }) => { +export const MultiValueRenderer: FC = memo(({ data, col }) => { if (!data || data.size === 0) { return null; } + if ( + List.isList(data) && + data.size === 1 && + (col?.isFileInput) + ) { + return ; + } + let i = -1; return (
@@ -58,7 +69,7 @@ export const MultiValueRenderer: FC = memo(({ data }) = return ( // IntelliJ mistakenly presumes that key is an index. // In fact, it is the key of the map which is unique. - // eslint-disable-next-line react/no-array-index-key + {++i > 0 ? ', ' : ''} {url ? {text} : text}