{valueDisplay}
@@ -491,6 +492,7 @@ export class Cell extends React.PureComponent
{
forUpdate={forUpdate}
modifyCell={cellActions.modifyCell}
onKeyDown={this.handleFocusedDropdownKeys}
+ row={row}
rowIdx={rowIdx}
select={cellActions.selectCell}
values={values}
diff --git a/packages/components/src/internal/components/editable/EditableGridLoaderFromSelection.tsx b/packages/components/src/internal/components/editable/EditableGridLoaderFromSelection.tsx
index 62ec4c02c3..4b2698b407 100644
--- a/packages/components/src/internal/components/editable/EditableGridLoaderFromSelection.tsx
+++ b/packages/components/src/internal/components/editable/EditableGridLoaderFromSelection.tsx
@@ -61,14 +61,13 @@ export class EditableGridLoaderFromSelection implements EditableGridLoader {
fetch(gridModel: QueryModel): Promise {
return new Promise((resolve, reject) => {
- const { queryName, queryParameters, schemaName, selections, sortString, viewName } = gridModel;
-
- const selections_ = [...selections].filter(s => this.idsNotPermitted.indexOf(parseInt(s, 10)) === -1);
+ const { queryName, queryParameters, schemaName, sortString, viewName } = gridModel;
+ const selectedIds = gridModel.getSelectedIds(this.idsNotPermitted);
return getSelectedData(
schemaName,
queryName,
- selections_,
+ selectedIds,
gridModel.getRequestColumnsString(this.requiredColumns, this.omittedColumns, true),
sortString,
queryParameters,
diff --git a/packages/components/src/internal/components/editable/EditableGridPanelForUpdate.tsx b/packages/components/src/internal/components/editable/EditableGridPanelForUpdate.tsx
index fc54e5af83..c31cb03d4f 100644
--- a/packages/components/src/internal/components/editable/EditableGridPanelForUpdate.tsx
+++ b/packages/components/src/internal/components/editable/EditableGridPanelForUpdate.tsx
@@ -88,8 +88,8 @@ export const EditableGridPanelForUpdate: FC = p
const hasValidUserComment = comment?.trim()?.length > 0;
const notPermittedText = useMemo(
- () => getOperationNotPermittedMessage(editStatusData, pluralNoun),
- [editStatusData, pluralNoun]
+ () => getOperationNotPermittedMessage(editStatusData, singularNoun, pluralNoun),
+ [editStatusData, singularNoun, pluralNoun]
);
useEffect(() => {
diff --git a/packages/components/src/internal/components/editable/LookupCell.tsx b/packages/components/src/internal/components/editable/LookupCell.tsx
index f68bf5b0ea..8a8c66de4c 100644
--- a/packages/components/src/internal/components/editable/LookupCell.tsx
+++ b/packages/components/src/internal/components/editable/LookupCell.tsx
@@ -30,6 +30,8 @@ import { QuerySelect } from '../forms/QuerySelect';
import { getQueryColumnRenderers } from '../../global';
+import { getValueFromRow } from '../../util/utils';
+
import { MODIFICATION_TYPES, SELECTION_TYPES } from './constants';
import { ValueDescriptor } from './models';
@@ -48,9 +50,10 @@ export interface LookupCellProps {
lookupValueFilters?: Filter.IFilter[];
modifyCell: (colIdx: number, rowIdx: number, newValues: ValueDescriptor[], mod: MODIFICATION_TYPES) => void;
onKeyDown?: (event: KeyboardEvent) => void;
+ row: any;
rowIdx: number;
- values: List;
select: (colIdx: number, rowIdx: number, selection?: SELECTION_TYPES, resetValue?: boolean) => void;
+ values: List;
}
interface QueryLookupCellProps extends LookupCellProps {
@@ -126,7 +129,7 @@ const QueryLookupCell: FC = memo(props => {
QueryLookupCell.displayName = 'QueryLookupCell';
export const LookupCell: FC = memo(props => {
- const { col, colIdx, disabled, modifyCell, onKeyDown, rowIdx, select, values } = props;
+ const { col, colIdx, disabled, modifyCell, onKeyDown, row, rowIdx, select, values, containerPath } = props;
const onSelectChange = useCallback(
(fieldName, formValue, options, props_) => {
@@ -158,7 +161,20 @@ export const LookupCell: FC = memo(props => {
);
}
- return ;
+ // if the column is a lookup, we need to pass the containerPath to the QuerySelect
+ const containerPath_ =
+ containerPath ??
+ getValueFromRow(row?.toJS(), 'Folder')?.toString() ??
+ getValueFromRow(row?.toJS(), 'Container')?.toString();
+
+ return (
+
+ );
});
LookupCell.displayName = 'LookupCell';
diff --git a/packages/components/src/internal/components/entities/APIWrapper.ts b/packages/components/src/internal/components/entities/APIWrapper.ts
index c602a3de0f..c0d0388210 100644
--- a/packages/components/src/internal/components/entities/APIWrapper.ts
+++ b/packages/components/src/internal/components/entities/APIWrapper.ts
@@ -14,14 +14,17 @@ import {
GetDeleteConfirmationDataOptions,
getDeleteConfirmationData,
getMoveConfirmationData,
+ getOperationConfirmationData,
getOperationConfirmationDataForModel,
getEntityTypeData,
getOriginalParentsFromLineage,
+ getParentTypeDataForLineage,
handleEntityFileImport,
moveEntities,
initParentOptionsSelects,
MoveEntitiesOptions,
getCrossFolderSelectionResult,
+ GetParentTypeDataForLineage,
} from './actions';
import { DataOperation } from './constants';
import {
@@ -65,6 +68,14 @@ export interface EntityAPIWrapper {
selectionKey?: string,
useSnapshotSelection?: boolean
) => Promise;
+ getOperationConfirmationData: (
+ dataType: EntityDataType,
+ rowIds: string[] | number[],
+ selectionKey?: string,
+ useSnapshotSelection?: boolean,
+ extraParams?: Record,
+ containerPath?: string
+ ) => Promise;
getOperationConfirmationDataForModel: (
model: QueryModel,
dataType: EntityDataType,
@@ -78,6 +89,7 @@ export interface EntityAPIWrapper {
originalParents: Record>;
parentTypeOptions: Map>;
}>;
+ getParentTypeDataForLineage: GetParentTypeDataForLineage;
handleEntityFileImport: (
importAction: string,
queryInfo: QueryInfo,
@@ -113,8 +125,10 @@ export class EntityServerAPIWrapper implements EntityAPIWrapper {
getDeleteConfirmationData = getDeleteConfirmationData;
getMoveConfirmationData = getMoveConfirmationData;
getEntityTypeData = getEntityTypeData;
+ getOperationConfirmationData = getOperationConfirmationData;
getOperationConfirmationDataForModel = getOperationConfirmationDataForModel;
getOriginalParentsFromLineage = getOriginalParentsFromLineage;
+ getParentTypeDataForLineage = getParentTypeDataForLineage;
handleEntityFileImport = handleEntityFileImport;
loadNameExpressionOptions = loadNameExpressionOptions;
moveEntities = moveEntities;
@@ -134,8 +148,10 @@ export function getEntityTestAPIWrapper(
getDeleteConfirmationData: mockFn(),
getMoveConfirmationData: mockFn(),
getEntityTypeData: mockFn(),
+ getOperationConfirmationData: mockFn(),
getOperationConfirmationDataForModel: mockFn(),
getOriginalParentsFromLineage: mockFn(),
+ getParentTypeDataForLineage: mockFn(),
handleEntityFileImport: mockFn(),
loadNameExpressionOptions: mockFn(),
moveEntities: mockFn(),
diff --git a/packages/components/src/internal/components/entities/models.test.ts b/packages/components/src/internal/components/entities/models.test.ts
index c83f3f913a..3709099ecf 100644
--- a/packages/components/src/internal/components/entities/models.test.ts
+++ b/packages/components/src/internal/components/entities/models.test.ts
@@ -18,7 +18,7 @@ import { List } from 'immutable';
import { SCHEMAS } from '../../schemas';
import { QueryColumn } from '../../../public/QueryColumn';
-import { EntityIdCreationModel, EntityParentType, EntityTypeOption } from './models';
+import { EntityIdCreationModel, EntityParentType, EntityTypeOption, OperationConfirmationData } from './models';
import { SampleTypeDataType } from './constants';
describe('EntityParentType', () => {
@@ -212,3 +212,155 @@ describe('EntityIdCreationModel', () => {
expect(sq.getSchemaQuery().queryName).toBe('a');
});
});
+
+describe('OperationConfirmationData', () => {
+ const data = new OperationConfirmationData({
+ allowed: [{ rowId: 1 }, { rowId: 2 }],
+ notAllowed: [{ rowId: 3 }, { rowId: 4 }, { rowId: 5 }],
+ notPermitted: [{ rowId: 6 }],
+ });
+
+ test('isIdActionable', () => {
+ expect(data.isIdActionable('1')).toBe(true);
+ expect(data.isIdActionable(1)).toBe(true);
+ expect(data.isIdActionable('3')).toBe(false);
+ expect(data.isIdActionable(3)).toBe(false);
+ expect(data.isIdActionable('6')).toBe(false);
+ expect(data.isIdActionable(6)).toBe(false);
+ });
+
+ test('getActionableIds', () => {
+ expect(data.getActionableIds()).toEqual([1, 2]);
+ expect(data.getActionableIds('bogus')).toEqual([]);
+ });
+
+ test('allActionable', () => {
+ expect(data.allActionable).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [{ rowId: 1 }, { rowId: 2 }],
+ notAllowed: [],
+ notPermitted: [],
+ }).allActionable
+ ).toBe(true);
+ });
+
+ test('noneActionable', () => {
+ expect(data.noneActionable).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [],
+ }).noneActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [{ rowId: 1 }, { rowId: 2 }],
+ notAllowed: [],
+ notPermitted: [],
+ }).noneActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [{ rowId: 1 }, { rowId: 2 }],
+ notPermitted: [],
+ }).noneActionable
+ ).toBe(true);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [{ rowId: 1 }, { rowId: 2 }],
+ }).noneActionable
+ ).toBe(true);
+ });
+
+ test('anyActionable', () => {
+ expect(data.anyActionable).toBe(true);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [],
+ }).anyActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [{ rowId: 1 }, { rowId: 2 }],
+ notAllowed: [],
+ notPermitted: [],
+ }).anyActionable
+ ).toBe(true);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [{ rowId: 1 }, { rowId: 2 }],
+ notPermitted: [],
+ }).anyActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [{ rowId: 1 }, { rowId: 2 }],
+ }).anyActionable
+ ).toBe(false);
+ });
+
+ test('anyNotActionable', () => {
+ expect(data.anyNotActionable).toBe(true);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [],
+ }).anyNotActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [{ rowId: 1 }, { rowId: 2 }],
+ notAllowed: [],
+ notPermitted: [],
+ }).anyNotActionable
+ ).toBe(false);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [{ rowId: 1 }, { rowId: 2 }],
+ notPermitted: [],
+ }).anyNotActionable
+ ).toBe(true);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [{ rowId: 1 }, { rowId: 2 }],
+ }).anyNotActionable
+ ).toBe(true);
+ });
+
+ test('totalCount', () => {
+ expect(data.totalCount).toBe(5);
+ expect(
+ new OperationConfirmationData({
+ allowed: [],
+ notAllowed: [],
+ notPermitted: [],
+ }).totalCount
+ ).toBe(0);
+ });
+
+ test('getContainerPaths', () => {
+ expect(
+ new OperationConfirmationData({
+ containers: [
+ { id: 'a', permitted: true },
+ { id: 'b', permitted: false },
+ { id: 'c', permitted: true },
+ ],
+ }).getContainerPaths()
+ ).toEqual(['a', 'c']);
+ });
+});
diff --git a/packages/components/src/internal/components/entities/models.ts b/packages/components/src/internal/components/entities/models.ts
index aa6aa70335..cad76a7962 100644
--- a/packages/components/src/internal/components/entities/models.ts
+++ b/packages/components/src/internal/components/entities/models.ts
@@ -531,10 +531,17 @@ export interface EntityDataType {
uniqueFieldKey: string;
}
+interface OperationContainerInfo {
+ id: string;
+ path: string;
+ permitted: boolean;
+}
+
export class OperationConfirmationData {
[immerable]: true;
readonly allowed: any[];
+ readonly containers: OperationContainerInfo[];
readonly notAllowed: any[];
readonly notPermitted: any[]; // could intersect both allowed and notAllowed
readonly idMap: Record;
@@ -606,6 +613,10 @@ export class OperationConfirmationData {
get anyNotActionable(): boolean {
return this.totalNotActionable > 0;
}
+
+ getContainerPaths(permittedOnly = true): string[] {
+ return this.containers?.filter(c => !permittedOnly || c.permitted).map(c => c.id) ?? [];
+ }
}
export interface CrossFolderSelectionResult {
diff --git a/packages/components/src/internal/components/forms/BulkUpdateForm.tsx b/packages/components/src/internal/components/forms/BulkUpdateForm.tsx
index 8714cfc3d7..c837963466 100644
--- a/packages/components/src/internal/components/forms/BulkUpdateForm.tsx
+++ b/packages/components/src/internal/components/forms/BulkUpdateForm.tsx
@@ -9,7 +9,7 @@ import { SchemaQuery } from '../../../public/SchemaQuery';
import { getSelectedData } from '../../actions';
-import { capitalizeFirstChar, getCommonDataValues, getUpdatedData } from '../../util/utils';
+import { capitalizeFirstChar, caseInsensitive, getCommonDataValues, getUpdatedData } from '../../util/utils';
import { QueryInfoForm } from './QueryInfoForm';
@@ -35,7 +35,7 @@ interface Props {
queryInfo: QueryInfo;
readOnlyColumns?: string[];
requiredColumns?: string[];
- selectedIds: Set;
+ selectedIds: string[];
singularNoun?: string;
// sortString is used so we render editable grids with the proper sorts when using onSubmitForEdit
sortString?: string;
@@ -46,10 +46,10 @@ interface Props {
}
interface State {
+ containerPaths: string[];
dataForSelection: Map;
dataIdsForSelection: List;
displayFieldUpdates: any;
- errorMsg: string;
isLoadingDataForSelection: boolean;
originalDataForSelection: Map;
}
@@ -65,11 +65,11 @@ export class BulkUpdateForm extends PureComponent {
super(props);
this.state = {
+ containerPaths: undefined,
originalDataForSelection: undefined,
dataForSelection: undefined,
displayFieldUpdates: {},
dataIdsForSelection: undefined,
- errorMsg: undefined,
isLoadingDataForSelection: true,
};
}
@@ -93,14 +93,13 @@ export class BulkUpdateForm extends PureComponent {
: undefined;
let columnString = columns?.map(c => c.fieldKey).join(',');
if (requiredColumns) columnString = `${columnString ? columnString + ',' : ''}${requiredColumns.join(',')}`;
-
const { schemaName, name } = queryInfo;
try {
const { data, dataIds } = await getSelectedData(
schemaName,
name,
- Array.from(selectedIds),
+ selectedIds,
columnString,
sortString,
undefined,
@@ -108,6 +107,7 @@ export class BulkUpdateForm extends PureComponent {
);
const mappedData = this.mapDataForDisplayFields(data);
this.setState({
+ containerPaths: mappedData.containerPaths,
originalDataForSelection: data,
dataForSelection: mappedData.data,
displayFieldUpdates: mappedData.bulkUpdates,
@@ -121,46 +121,59 @@ export class BulkUpdateForm extends PureComponent {
}
};
- mapDataForDisplayFields(data: Map): { bulkUpdates: OrderedMap; data: Map } {
+ mapDataForDisplayFields(data: Map): {
+ bulkUpdates: OrderedMap;
+ containerPaths?: string[];
+ data: Map;
+ } {
const { displayValueFields } = this.props;
let updates = Map();
let bulkUpdates = OrderedMap();
-
- if (!displayValueFields) return { data, bulkUpdates };
+ const containerPaths = new Set();
let conflictKeys = new Set();
data.forEach((rowData, id) => {
if (rowData) {
- let updatedRow = Map();
- rowData.forEach((field, key) => {
- if (displayValueFields.includes(key)) {
- const valuesDiffer =
- field.has('displayValue') && field.get('value') !== field.get('displayValue');
- let comparisonValue = field.get('displayValue') ?? field.get('value');
- if (comparisonValue) comparisonValue += ''; // force to string
- if (!conflictKeys.has(key)) {
- if (!bulkUpdates.has(key)) {
- bulkUpdates = bulkUpdates.set(key, comparisonValue);
- } else if (bulkUpdates.get(key) !== comparisonValue) {
- bulkUpdates = bulkUpdates.remove(key);
- conflictKeys = conflictKeys.add(key);
+ const containerPath =
+ caseInsensitive(rowData.toJS(), 'Folder') ?? caseInsensitive(rowData.toJS(), 'Container');
+ if (containerPath?.value) containerPaths.add(containerPath.value);
+
+ if (displayValueFields) {
+ let updatedRow = Map();
+ rowData.forEach((field, key) => {
+ if (displayValueFields.includes(key)) {
+ const valuesDiffer =
+ field.has('displayValue') && field.get('value') !== field.get('displayValue');
+ let comparisonValue = field.get('displayValue') ?? field.get('value');
+ if (comparisonValue) comparisonValue += ''; // force to string
+ if (!conflictKeys.has(key)) {
+ if (!bulkUpdates.has(key)) {
+ bulkUpdates = bulkUpdates.set(key, comparisonValue);
+ } else if (bulkUpdates.get(key) !== comparisonValue) {
+ bulkUpdates = bulkUpdates.remove(key);
+ conflictKeys = conflictKeys.add(key);
+ }
+ }
+ if (valuesDiffer) {
+ field = field.set('value', comparisonValue);
}
}
- if (valuesDiffer) {
- field = field.set('value', comparisonValue);
- }
+ updatedRow = updatedRow.set(key, field);
+ });
+ if (!updatedRow.isEmpty()) {
+ updates = updates.set(id, updatedRow);
}
- updatedRow = updatedRow.set(key, field);
- });
- if (!updatedRow.isEmpty()) updates = updates.set(id, updatedRow);
+ }
}
});
- if (!updates.isEmpty()) return { data: data.merge(updates), bulkUpdates };
- return { data, bulkUpdates };
+ if (!updates.isEmpty()) {
+ return { data: data.merge(updates), bulkUpdates, containerPaths: Array.from(containerPaths) };
+ }
+ return { data, bulkUpdates, containerPaths: Array.from(containerPaths) };
}
getSelectionCount(): number {
- return this.props.selectedIds.size;
+ return this.props.selectedIds.length;
}
getSelectionNoun(): string {
@@ -216,7 +229,7 @@ export class BulkUpdateForm extends PureComponent {
}
render() {
- const { isLoadingDataForSelection, dataForSelection } = this.state;
+ const { isLoadingDataForSelection, dataForSelection, containerPaths } = this.state;
const {
containerFilter,
onCancel,
@@ -230,6 +243,11 @@ export class BulkUpdateForm extends PureComponent {
const fieldValues =
isLoadingDataForSelection || !dataForSelection ? undefined : getCommonDataValues(dataForSelection);
+ // if all selectedIds are from the same containerPath, use that for the lookups via QueryFormInputs > QuerySelect,
+ // if selections are from multiple containerPaths, disable the lookup and file field inputs
+ const containerPath = containerPaths?.length === 1 ? containerPaths[0] : undefined;
+ const preventCrossFolderEnable = containerPaths?.length > 1;
+
return (
{
checkRequiredFields={false}
columnFilter={this.columnFilter}
containerFilter={containerFilter}
+ containerPath={containerPath}
disabled={disabled}
+ preventCrossFolderEnable={preventCrossFolderEnable}
fieldValues={fieldValues}
header={this.renderBulkUpdateHeader()}
includeCommentField={true}
@@ -255,6 +275,7 @@ export class BulkUpdateForm extends PureComponent {
showLabelAsterisk
submitForEditText="Edit with Grid"
submitText={`Update ${capitalizeFirstChar(pluralNoun)}`}
+ pluralNoun={pluralNoun}
title={this.getTitle()}
onAdditionalFormDataChange={onAdditionalFormDataChange}
/>
diff --git a/packages/components/src/internal/components/forms/FieldLabel.spec.tsx b/packages/components/src/internal/components/forms/FieldLabel.spec.tsx
deleted file mode 100644
index 8f00a160c3..0000000000
--- a/packages/components/src/internal/components/forms/FieldLabel.spec.tsx
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (c) 2019 LabKey Corporation. All rights reserved. No portion of this work may be reproduced in
- * any form or by any electronic or mechanical means without written permission from LabKey Corporation.
- */
-import React from 'react';
-import renderer from 'react-test-renderer';
-import { mount, shallow } from 'enzyme';
-
-import { QueryColumn } from '../../../public/QueryColumn';
-
-import { ToggleIcon } from '../buttons/ToggleButtons';
-
-import { FieldLabel } from './FieldLabel';
-import { LabelOverlay } from './LabelOverlay';
-
-const queryColumn = new QueryColumn({
- name: 'testColumn',
- caption: 'test Column',
-});
-
-describe('FieldLabel', () => {
- test("don't show label", () => {
- const tree = renderer.create();
- expect(tree === null);
- });
-
- test('without overlay, with label', () => {
- const label = This is the label;
- const wrapper = shallow();
- expect(wrapper.find('span.label-span')).toHaveLength(1);
- expect(wrapper.find(LabelOverlay)).toHaveLength(0);
- });
-
- test('without overlay, with column', () => {
- const wrapper = shallow();
- expect(wrapper.text()).toContain(queryColumn.caption);
- expect(wrapper.find(LabelOverlay)).toHaveLength(0);
- });
-
- test('with overlay, with label', () => {
- const label = This is the label;
- const wrapper = shallow();
- expect(wrapper.find(LabelOverlay)).toHaveLength(1);
- });
-
- test('with overlay, with column', () => {
- const wrapper = mount();
- expect(wrapper.text()).toContain(queryColumn.caption);
- expect(wrapper.find(LabelOverlay)).toHaveLength(1);
- });
-
- function verifyToggle(wrapper, classNames?: string[]) {
- expect(wrapper.find(ToggleIcon)).toHaveLength(1);
- if (classNames?.length > 0) {
- classNames.forEach(className => expect(wrapper.find('.' + className)).toHaveLength(1));
- }
- }
-
- test('showToggle', () => {
- const wrapper = shallow();
- verifyToggle(wrapper);
- });
-
- test('showToggle, with labelOverlayProps, not formsy', () => {
- const label = 'This is the label';
- const props = {
- label,
- isFormsy: false,
- };
- const wrapper = shallow();
- verifyToggle(wrapper, ['control-label-toggle-input', 'control-label-toggle-input-size-fixed', 'col-xs-1']);
- });
-
- test('showToggle, with labelOverlayProps, formsy', () => {
- const label = 'This is the label';
- const props = {
- label,
- isFormsy: true,
- };
- const wrapper = shallow();
- verifyToggle(wrapper);
- });
-
- test('showToggle, with labelOverlayProps, formsy, with toggleClassName', () => {
- const label = 'This is the label';
- const props = {
- label,
- isFormsy: true,
- };
- const wrapper = shallow(
-
- );
- verifyToggle(wrapper, ['toggle-wrapper']);
- });
-
- test('showToggle, with labelOverlayProps, not formsy, with toggleClassName', () => {
- const label = 'This is the label';
- const props = {
- label,
- isFormsy: false,
- };
- const wrapper = shallow(
-
- );
- verifyToggle(wrapper, ['toggle-wrapper', 'col-xs-1']);
- });
-});
diff --git a/packages/components/src/internal/components/forms/FieldLabel.test.tsx b/packages/components/src/internal/components/forms/FieldLabel.test.tsx
new file mode 100644
index 0000000000..2edf9e4433
--- /dev/null
+++ b/packages/components/src/internal/components/forms/FieldLabel.test.tsx
@@ -0,0 +1,160 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Formsy from 'formsy-react';
+
+import { QueryColumn } from '../../../public/QueryColumn';
+
+import { FieldLabel } from './FieldLabel';
+
+const queryColumn = new QueryColumn({
+ name: 'testColumn',
+ caption: 'test Column',
+});
+
+describe('FieldLabel', () => {
+ beforeAll(() => {
+ console.warn = jest.fn();
+ });
+
+ test("don't show label", () => {
+ render();
+ expect(document.body.textContent).toBe('');
+ });
+
+ test('without overlay, with label', () => {
+ const label = This is the label;
+ render();
+ expect(document.querySelector('span.label-span').textContent).toBe('This is the label');
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(0);
+ });
+
+ test('without overlay, with column', () => {
+ render();
+ expect(document.body.textContent).toBe(queryColumn.caption);
+ expect(document.querySelectorAll('.span.label-span')).toHaveLength(0);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(0);
+ });
+
+ test('with overlay, with label', () => {
+ const label = This is the label;
+ render();
+ expect(document.body.textContent).toBe(' ');
+ expect(document.querySelectorAll('.span.label-span')).toHaveLength(0);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('with overlay, with column', () => {
+ render();
+ expect(document.body.textContent).toBe(queryColumn.caption + ' ');
+ expect(document.querySelectorAll('.span.label-span')).toHaveLength(0);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle', () => {
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle, with labelOverlayProps, not formsy', () => {
+ const label = 'This is the label';
+ const props = {
+ label,
+ isFormsy: false,
+ };
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.control-label-toggle-input')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle, with labelOverlayProps, formsy', () => {
+ const label = 'This is the label';
+ const props = {
+ label,
+ isFormsy: true,
+ };
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.control-label-toggle-input')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle, with labelOverlayProps, formsy, with toggleClassName', () => {
+ const label = 'This is the label';
+ const props = {
+ label,
+ isFormsy: true,
+ };
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.toggle-wrapper')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle, with labelOverlayProps, not formsy, with toggleClassName', () => {
+ const label = 'This is the label';
+ const props = {
+ label,
+ isFormsy: false,
+ };
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.toggle-wrapper')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+
+ test('showToggle, toggleProps disabled', () => {
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.disabled')).toHaveLength(1);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(2);
+ });
+
+ test('showToggle, toggleProps not disabled', () => {
+ render(
+
+
+
+ );
+ expect(document.querySelectorAll('.toggle')).toHaveLength(1);
+ expect(document.querySelectorAll('.disabled')).toHaveLength(0);
+ expect(document.querySelectorAll('.overlay-trigger')).toHaveLength(1);
+ });
+});
diff --git a/packages/components/src/internal/components/forms/FieldLabel.tsx b/packages/components/src/internal/components/forms/FieldLabel.tsx
index 42eebc29ef..f544647d04 100644
--- a/packages/components/src/internal/components/forms/FieldLabel.tsx
+++ b/packages/components/src/internal/components/forms/FieldLabel.tsx
@@ -15,6 +15,7 @@ import { LabelOverlay, LabelOverlayProps } from './LabelOverlay';
interface ToggleProps {
onClick: () => void;
+ toolTip?: string;
}
export interface FieldLabelProps {
@@ -93,6 +94,8 @@ export class FieldLabel extends Component {
inputFieldName={getFieldEnabledFieldName(column, fieldName)}
active={!isDisabled ? 'on' : 'off'}
onClick={toggleProps?.onClick}
+ disabled={!toggleProps?.onClick}
+ toolTip={toggleProps?.toolTip}
/>
diff --git a/packages/components/src/internal/components/forms/QueryFormInputs.tsx b/packages/components/src/internal/components/forms/QueryFormInputs.tsx
index bda7a9dffc..00e4bc91f3 100644
--- a/packages/components/src/internal/components/forms/QueryFormInputs.tsx
+++ b/packages/components/src/internal/components/forms/QueryFormInputs.tsx
@@ -25,6 +25,10 @@ import { QueryInfo } from '../../../public/QueryInfo';
import { caseInsensitive } from '../../util/utils';
+import { getContainerFilterForLookups } from '../../query/api';
+
+import { isAllProductFoldersFilteringEnabled } from '../../app/utils';
+
import { FormsyInput } from './input/FormsyReactComponents';
import { resolveInputRenderer } from './input/InputRenderFactory';
import { QuerySelect } from './QuerySelect';
@@ -46,9 +50,11 @@ export interface QueryFormInputsProps {
columnFilter?: (col?: QueryColumn) => boolean;
// this can be used when you want to keep certain columns always filtered out (e.g., aliquot- or sample-only columns)
isIncludedColumn?: (col: QueryColumn) => boolean;
- componentKey?: string; // unique key to add to QuerySelect to avoid duplication w/ transpose
+ componentKey?: string;
+ // unique key to add to QuerySelect to avoid duplication w/ transpose
/** A container filter that will be applied to all query-based inputs in this form */
containerFilter?: Query.ContainerFilter;
+ containerPath?: string;
disabledFields?: List