Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve-cross project actions - Edit in Bulk #1479

Merged
merged 34 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1f9301e
Update getOperationNotPermittedMessage to work for both Edit in Grid …
cnathe Apr 18, 2024
cbe6338
BulkUpdateForm getUpdatedData() to include Folder in updated rows, if…
cnathe Apr 18, 2024
8cbeda1
3.39.1-fb-crossFolderEditInBulk.0
cnathe Apr 18, 2024
e7dc321
Add getSelectedIds(filterIds) to QueryModel
cnathe Apr 19, 2024
cfdcd8b
3.39.1-fb-crossFolderEditInBulk.1
cnathe Apr 19, 2024
aefb117
saveRowsByContainer prop for containerField to be optional since it h…
cnathe Apr 19, 2024
b31b80a
3.39.1-fb-crossFolderEditInBulk.2
cnathe Apr 19, 2024
dfbbe53
AppendUnitsInput fixes for grid cell rendering and enable/disable in …
cnathe Apr 19, 2024
0cb7686
3.39.1-fb-crossFolderEditInBulk.3
cnathe Apr 19, 2024
bb572d3
Merge branch 'develop' into fb_crossFolderEditInBulk
cnathe Apr 22, 2024
37ab44c
3.39.4-fb-crossFolderEditInBulk.0
cnathe Apr 22, 2024
996da53
Edit in Grid and Bulk lookup fields to use containerPath based on sel…
cnathe Apr 22, 2024
6d80e16
null check in getValueFromRow
cnathe Apr 22, 2024
c2627de
3.39.4-fb-crossFolderEditInBulk.1
cnathe Apr 22, 2024
69a41ef
Add getOperationConfirmationData to ApiWrapper
cnathe Apr 23, 2024
df507b6
3.39.4-fb-crossFolderEditInBulk.2
cnathe Apr 23, 2024
b8247ba
add getParentTypeDataForLineage to APIWrapper
cnathe Apr 23, 2024
a94ef1d
3.39.4-fb-crossFolderEditInBulk.3
cnathe Apr 23, 2024
d487fb8
add updateRowsByContainer to APIWrapper to conditionally use updateRo…
cnathe Apr 24, 2024
f5931a7
3.39.4-fb-crossFolderEditInBulk.4
cnathe Apr 24, 2024
be021e7
BulkUpdateForm to disable file files toggle when more than one contai…
cnathe Apr 25, 2024
475cccb
Merge branch 'develop' into fb_crossFolderEditInBulk
cnathe Apr 25, 2024
836a0c3
3.39.5-fb-crossFolderEditInBulk.0
cnathe Apr 25, 2024
329111c
pluralNoun.toLowerCase()
cnathe Apr 25, 2024
30d17e6
jest test updates and convert to RTL
cnathe Apr 25, 2024
adc5f0e
CR feedback - check for "Container" if "Folder" not found, don't disa…
cnathe Apr 26, 2024
ddf76cc
Merge branch 'develop' into fb_crossFolderEditInBulk
cnathe Apr 26, 2024
2da0d19
3.39.6-fb-crossFolderEditInBulk.0
cnathe Apr 26, 2024
330357c
jest test updates
cnathe Apr 26, 2024
1f92457
QueryFormInputs lookup update to add check for isAllProductFoldersFil…
cnathe Apr 26, 2024
34aff4a
3.39.6-fb-crossFolderEditInBulk.1
cnathe Apr 26, 2024
05b04f1
Update release notes with version number and release date
cnathe Apr 29, 2024
5ca037d
npm run lint-branch-fix
cnathe Apr 29, 2024
e770d3c
3.40.0
cnathe Apr 29, 2024
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
4 changes: 2 additions & 2 deletions packages/components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@labkey/components",
"version": "3.39.6",
"version": "3.40.0",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down
11 changes: 11 additions & 0 deletions packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages.

### version 3.40.0
*Released*: 29 April 2024
- Support cross-folder "Edit in Bulk"
- Update getOperationNotPermittedMessage to work for both Edit in Grid and Edit in Bulk scenarios
- BulkUpdateForm getUpdatedData() to include Folder in updated rows, if it exists in originalData
- Add getSelectedIds(filterIds) to QueryModel
- saveRowsByContainer prop for containerField to be optional since it has a default
- AppendUnitsInput fixes for grid cell rendering and enable/disable in bulk form
- Edit in Grid and Bulk lookup fields to use containerPath based on selected row(s) (for BulkUpdateForm, disable lookup fields and file files toggle when more than one containerPath in selection)
- Add getOperationConfirmationData and getParentTypeDataForLineage to ApiWrapper

### version 3.39.6
*Released*: 25 April 2024
- Add support for exporting a storage map from terminal storage grids
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import {
uncapitalizeFirstChar,
valueIsEmpty,
withTransformedKeys,
getValueFromRow,
} from './internal/util/utils';
import { AutoForm } from './internal/components/AutoForm';
import { HelpIcon } from './internal/components/HelpIcon';
Expand Down Expand Up @@ -1577,6 +1578,7 @@ export {
generateId,
debounce,
valueIsEmpty,
getValueFromRow,
getActionErrorMessage,
getConfirmDeleteMessage,
resolveErrorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ describe('ToggleIcon', () => {

userEvent.click(document.getElementsByTagName('i')[0]);
expect(onClickFn).toHaveBeenCalledTimes(1);
expect(onClickFn).toHaveBeenCalledWith('on');
});

test('active first item', () => {
Expand All @@ -125,4 +126,23 @@ describe('ToggleIcon', () => {
expect(document.getElementsByClassName('toggle').length).toBe(1);
expect(document.getElementsByClassName('test-class').length).toBe(1);
});

test('disabled', () => {
const onClickFn = jest.fn();
render(<ToggleIcon active="off" disabled onClick={onClickFn} />);

userEvent.click(document.getElementsByTagName('i')[0]);
expect(onClickFn).toHaveBeenCalledTimes(0);
});

test('tooltip', () => {
const onClickFn = jest.fn();
render(<ToggleIcon active="off" toolTip="test tooltip" onClick={onClickFn} />);

expect(document.getElementsByClassName('overlay-trigger').length).toBe(1);

userEvent.click(document.getElementsByTagName('i')[0]);
expect(onClickFn).toHaveBeenCalledTimes(1);
expect(onClickFn).toHaveBeenCalledWith('on');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { FC, memo, useCallback } from 'react';
import classNames from 'classnames';

import { FormsyInput } from '../forms/input/FormsyReactComponents';
import { LabelHelpTip } from '../base/LabelHelpTip';

interface Props {
active: string;
Expand All @@ -16,6 +17,7 @@ interface Props {
inputFieldName?: string;
onClick: (selected: string) => void;
second?: string;
toolTip?: string;
}

export const ToggleButtons: FC<Props> = memo(props => {
Expand Down Expand Up @@ -71,17 +73,34 @@ export const ToggleButtons: FC<Props> = memo(props => {
});

export const ToggleIcon: FC<Props> = memo(props => {
const { first = 'on', second = 'off', onClick, active = 'off', className, inputFieldName, id } = props;
const {
first = 'on',
second = 'off',
onClick,
active = 'off',
className,
inputFieldName,
id,
disabled = false,
toolTip,
} = props;
const firstActive = active === first;
const secondActive = active === second;

const firstBtnClick = useCallback(() => {
if (secondActive) onClick(first);
}, [first, secondActive, onClick]);
if (secondActive && !disabled) onClick(first);
}, [first, secondActive, onClick, disabled]);

const secondBtnClick = useCallback(() => {
if (firstActive) onClick(second);
}, [second, firstActive, onClick]);
if (firstActive && !disabled) onClick(second);
}, [second, firstActive, onClick, disabled]);

const body = (
<>
{firstActive && <i className="fa fa-toggle-on" onClick={secondBtnClick} />}
{secondActive && <i className="fa fa-toggle-off" onClick={firstBtnClick} />}
</>
);

return (
<>
Expand All @@ -92,12 +111,13 @@ export const ToggleIcon: FC<Props> = memo(props => {
className={classNames('toggle', 'toggle-group-icon', 'btn-group', {
'toggle-on': firstActive,
'toggle-off': secondActive,
disabled,
[className]: !!className,
})}
id={id}
>
{firstActive && <i className="fa fa-toggle-on" onClick={secondBtnClick} />}
{secondActive && <i className="fa fa-toggle-off" onClick={firstBtnClick} />}
{toolTip && <LabelHelpTip iconComponent={body}>{toolTip}</LabelHelpTip>}
{!toolTip && body}
</div>
</>
);
Expand Down
6 changes: 4 additions & 2 deletions packages/components/src/internal/components/editable/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ export class Cell extends React.PureComponent<CellProps, State> {
const { filteredLookupKeys } = this.state;
const isDateTimeField = this.isDateTimeField;
const showLookup = this.isLookup;
const showMenu = showLookup || (col.inputRenderer && col.inputRenderer !== 'AppendUnitsInput');

if (!focused) {
let valueDisplay = values
Expand All @@ -403,7 +404,7 @@ export class Cell extends React.PureComponent<CellProps, State> {
'cell-warning': message !== undefined,
'cell-read-only': this.isReadOnly,
'cell-locked': locked,
'cell-menu': showLookup || col.inputRenderer,
'cell-menu': showMenu,
'cell-placeholder': valueDisplay.length === 0 && placeholder !== undefined,
}),
onDoubleClick: this.handleDblClick,
Expand All @@ -418,7 +419,7 @@ export class Cell extends React.PureComponent<CellProps, State> {
if (valueDisplay.length === 0 && placeholder) valueDisplay = placeholder;
let cell: ReactNode;

if (showLookup || col.inputRenderer) {
if (showMenu) {
cell = (
<div {...displayProps}>
<div className="cell-menu-value">{valueDisplay}</div>
Expand Down Expand Up @@ -491,6 +492,7 @@ export class Cell extends React.PureComponent<CellProps, State> {
forUpdate={forUpdate}
modifyCell={cellActions.modifyCell}
onKeyDown={this.handleFocusedDropdownKeys}
row={row}
rowIdx={rowIdx}
select={cellActions.selectCell}
values={values}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,13 @@ export class EditableGridLoaderFromSelection implements EditableGridLoader {

fetch(gridModel: QueryModel): Promise<GridResponse> {
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export const EditableGridPanelForUpdate: FC<EditableGridPanelForUpdateProps> = p

const hasValidUserComment = comment?.trim()?.length > 0;
const notPermittedText = useMemo(
() => getOperationNotPermittedMessage(editStatusData, pluralNoun),
[editStatusData, pluralNoun]
() => getOperationNotPermittedMessage(editStatusData, singularNoun, pluralNoun),
[editStatusData, singularNoun, pluralNoun]
);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -48,9 +50,10 @@ export interface LookupCellProps {
lookupValueFilters?: Filter.IFilter[];
modifyCell: (colIdx: number, rowIdx: number, newValues: ValueDescriptor[], mod: MODIFICATION_TYPES) => void;
onKeyDown?: (event: KeyboardEvent<HTMLElement>) => void;
row: any;
rowIdx: number;
values: List<ValueDescriptor>;
select: (colIdx: number, rowIdx: number, selection?: SELECTION_TYPES, resetValue?: boolean) => void;
values: List<ValueDescriptor>;
}

interface QueryLookupCellProps extends LookupCellProps {
Expand Down Expand Up @@ -126,7 +129,7 @@ const QueryLookupCell: FC<QueryLookupCellProps> = memo(props => {
QueryLookupCell.displayName = 'QueryLookupCell';

export const LookupCell: FC<LookupCellProps> = 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<SelectInputChange>(
(fieldName, formValue, options, props_) => {
Expand Down Expand Up @@ -158,7 +161,20 @@ export const LookupCell: FC<LookupCellProps> = memo(props => {
);
}

return <QueryLookupCell {...props} onSelectChange={onSelectChange} rawValues={rawValues} />;
// 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 (
<QueryLookupCell
{...props}
onSelectChange={onSelectChange}
rawValues={rawValues}
containerPath={containerPath_}
/>
);
});

LookupCell.displayName = 'LookupCell';
16 changes: 16 additions & 0 deletions packages/components/src/internal/components/entities/APIWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -65,6 +68,14 @@ export interface EntityAPIWrapper {
selectionKey?: string,
useSnapshotSelection?: boolean
) => Promise<OperationConfirmationData>;
getOperationConfirmationData: (
dataType: EntityDataType,
rowIds: string[] | number[],
selectionKey?: string,
useSnapshotSelection?: boolean,
extraParams?: Record<string, any>,
containerPath?: string
) => Promise<OperationConfirmationData>;
getOperationConfirmationDataForModel: (
model: QueryModel,
dataType: EntityDataType,
Expand All @@ -78,6 +89,7 @@ export interface EntityAPIWrapper {
originalParents: Record<string, List<EntityChoice>>;
parentTypeOptions: Map<string, List<IEntityTypeOption>>;
}>;
getParentTypeDataForLineage: GetParentTypeDataForLineage;
handleEntityFileImport: (
importAction: string,
queryInfo: QueryInfo,
Expand Down Expand Up @@ -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;
Expand All @@ -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(),
Expand Down
Loading