Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions packages/components/package-lock.json

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

4 changes: 2 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@labkey/components",
"version": "6.43.1",
"version": "6.43.2",
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
"sideEffects": false,
"files": [
Expand Down Expand Up @@ -50,7 +50,7 @@
"homepage": "https://github.com/LabKey/labkey-ui-components#readme",
"dependencies": {
"@hello-pangea/dnd": "18.0.1",
"@labkey/api": "1.41.0",
"@labkey/api": "1.41.1",
"@testing-library/dom": "~10.4.0",
"@testing-library/jest-dom": "~6.6.3",
"@testing-library/react": "~16.3.0",
Expand Down
8 changes: 7 additions & 1 deletion packages/components/releaseNotes/components.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# @labkey/components
Components, models, actions, and utility functions for LabKey applications and pages

### version 6.43.2
*Released*: 23 May 2025
- Add detailed auditing of domain changes & comment ability
- include `CommentTextArea` in `BaseDomainDesigner` for supplying user provided comment for domain updates
- wire up `auditUserComment` for `SampleTypeDesigner`, `DataClassDesigner` and `AssayDesignerPanels`

### version 6.43.1
*Released*: 22 May 2025
*Released*: 23 May 2025
- Issue 53055: Check for multiple values in single value column

### version 6.43.0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import React, { PureComponent, ComponentType, FC, memo, PropsWithChildren } from 'react';
import React, {
PureComponent,
ComponentType,
FC,
memo,
PropsWithChildren,
useState,
useCallback,
useMemo,
} from 'react';
import { List } from 'immutable';

import { getSubmitButtonClass, isApp } from '../../app/utils';
import { FormButtons } from '../../FormButtons';

import { Alert } from '../base/Alert';

import { CommentTextArea } from '../forms/input/CommentTextArea';

import { useDataChangeCommentsRequired } from '../forms/input/useDataChangeCommentsRequired';

import { getDomainBottomErrorMessage, getDomainHeaderName, getUpdatedVisitedPanelsList } from './actions';
import { DOMAIN_ERROR_ID, SEVERITY_LEVEL_ERROR } from './constants';

Expand Down Expand Up @@ -119,8 +132,9 @@ interface BaseDomainDesignerProps extends PropsWithChildren {
hasValidProperties: boolean;
name: string;
onCancel: () => void;
onFinish: () => void;
onFinish: (reason?: string) => void;
saveBtnText?: string;
showUserComment?: boolean;
submitting: boolean;
visitedPanels: List<number>;
}
Expand All @@ -137,7 +151,11 @@ export const BaseDomainDesigner: FC<BaseDomainDesignerProps> = memo(props => {
onCancel,
hasValidProperties,
saveBtnText = 'Save',
showUserComment,
} = props;
const [userComment, setUserComment] = useState<string>(undefined);
// skip useDataChangeCommentsRequired hook for LKS pages with showUserComment=false
const requiresUserComment = showUserComment ? useDataChangeCommentsRequired().requiresUserComment : false;

// get a list of the domain names that have errors
const errorDomains = domains
Expand All @@ -147,6 +165,15 @@ export const BaseDomainDesigner: FC<BaseDomainDesignerProps> = memo(props => {
const bottomErrorMsg = getDomainBottomErrorMessage(exception, errorDomains, hasValidProperties, visitedPanels);
const submitClassname = `save-button btn btn-${getSubmitButtonClass()}`;

const onSave = useCallback(() => {
onFinish(userComment);
}, [userComment, onFinish]);

const canSubmit = useMemo(() => {
if (submitting) return false;
return !requiresUserComment || userComment?.trim()?.length > 0;
}, [requiresUserComment, userComment, submitting]);

return (
<div className="domain-designer">
{children}
Expand All @@ -159,7 +186,16 @@ export const BaseDomainDesigner: FC<BaseDomainDesignerProps> = memo(props => {
<button className="cancel-button btn btn-default" onClick={onCancel} type="button">
Cancel
</button>
<button className={submitClassname} disabled={submitting} onClick={onFinish} type="button">
{showUserComment && (
<CommentTextArea
actionName="Update"
containerClassName="inline-comment"
onChange={setUserComment}
requiresUserComment={requiresUserComment}
inline
/>
)}
<button className={submitClassname} disabled={!canSubmit} onClick={onSave} type="button">
{saveBtnText}
</button>
</FormButtons>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ export function _parseCalculatedColumn(
export interface SaveDomainOptions {
/** Boolean indicating if rowIndices should be added to the error message objects */
addRowIndexes?: boolean;
auditUserComment?: string;
/** Container path where requests are made. Defaults to domain.container for updates. */
containerPath?: string;
/** DomainDesign to save */
Expand Down Expand Up @@ -576,6 +577,7 @@ export function saveDomain(options: SaveDomainOptions): Promise<DomainDesign> {
domainDesign: DomainDesign.serialize(domain),
includeWarnings,
options: options.options,
auditUserComment: options.auditUserComment,
success: successHandler,
failure: failureHandler,
});
Expand Down Expand Up @@ -902,7 +904,7 @@ export function updateDataType(field: DomainField, value: any): DomainField {
}) as DomainField;
} else {
if (PropDescType.isUser(value)) {
field = field.merge({lookupValidator: LOOKUP_VALIDATOR}) as DomainField;
field = field.merge({ lookupValidator: LOOKUP_VALIDATOR }) as DomainField;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,14 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
);
}

onFinish = (): void => {
onFinish = (reasonForUpdate?: string): void => {
const { setSubmitting } = this.props;
const { protocolModel } = this.state;
const appIsValidMsg = this.getAppIsValidMsg();
const textChoiceValidMsg = this.getTextChoiceUpdatesValidMsg();
const isValid = protocolModel.isValid() && textChoiceValidMsg === undefined && appIsValidMsg === undefined;

this.props.onFinish(isValid, this.saveDomain);
this.props.onFinish(isValid, () => this.saveDomain(reasonForUpdate));

if (!isValid) {
const exception =
Expand All @@ -258,13 +258,13 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
}
};

saveDomain = (): void => {
saveDomain = (auditUserComment?: string): void => {
const { beforeFinish, setSubmitting } = this.props;
const { protocolModel } = this.state;

beforeFinish?.(protocolModel);

saveAssayDesign(protocolModel)
saveAssayDesign(protocolModel, auditUserComment)
.then(response => {
this.setState(() => ({ protocolModel }));
setSubmitting(false, () => {
Expand Down Expand Up @@ -418,6 +418,7 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
onCancel={onCancel}
onFinish={this.onFinish}
saveBtnText={saveBtnText}
showUserComment={!initModel.isNew() && appPropertiesOnly}
>
<FilterCriteriaContext.Provider value={filterCriteriaState}>
<AssayPropertiesPanel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ import { handleRequestFailure } from '../../../request';

import { AssayProtocolModel } from './models';

export function saveAssayDesign(model: AssayProtocolModel): Promise<AssayProtocolModel> {
export function saveAssayDesign(model: AssayProtocolModel, auditUserComment?: string): Promise<AssayProtocolModel> {
return new Promise((resolve, reject) => {
Ajax.request({
url: ActionURL.buildURL('assay', 'saveProtocol.api', model.container),
jsonData: AssayProtocolModel.serialize(model),
jsonData: AssayProtocolModel.serialize(model, auditUserComment),
success: Utils.getCallbackWrapper(response => {
resolve(AssayProtocolModel.create(response.data));
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,16 @@ export class AssayProtocolModel extends ImmutableRecord({
return new AssayProtocolModel({ ...raw, name, domains });
}

static serialize(model: AssayProtocolModel): any {
static serialize(model: AssayProtocolModel, auditUserComment?: string): any {
// need to serialize the DomainDesign objects to remove the unrecognized fields
const domains = model.domains.map(domain => {
return DomainDesign.serialize(domain);
});

const json = model.merge({ domains }).toJS();

if (auditUserComment) json.auditUserComment = auditUserComment;

// only need to serialize the id and not the autoCopyTargetContainer object
delete json.autoCopyTargetContainer;
delete json.exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ interface Props {
headerText?: string;
helpTopic?: string;
initModel?: DataClassModel;
isUpdate?: boolean;
isValidParentOptionsFn?: (row: any, isDataClass: boolean) => boolean;
// loadNameExpressionOptions is a prop for testing purposes only, see default implementation below
loadNameExpressionOptions?: (
Expand All @@ -61,11 +62,11 @@ interface Props {
onChange?: (model: DataClassModel) => void;
onComplete: (model: DataClassModel) => void;
saveBtnText?: string;
showGenIdBanner?: boolean;
validateNameExpressions?: boolean;
}

interface State {
auditUserComment?: string;
model: DataClassModel;
nameExpressionWarnings: string[];
namePreviews: string[];
Expand Down Expand Up @@ -156,12 +157,12 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
return name;
};

onFinish = (): void => {
onFinish = (auditUserComment?: string): void => {
const { defaultNameFieldConfig, setSubmitting, nounSingular } = this.props;
const { model } = this.state;
const isValid = model.isValid(defaultNameFieldConfig);

this.props.onFinish(isValid, this.saveDomain);
this.props.onFinish(isValid, () => this.saveDomain(false, auditUserComment));

if (!isValid) {
let exception: string;
Expand Down Expand Up @@ -207,7 +208,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
return aliases;
}

saveDomain = async (hasConfirmedNameExpression?: boolean): Promise<void> => {
saveDomain = async (hasConfirmedNameExpression?: boolean, auditUserComment?: string): Promise<void> => {
const { api, beforeFinish, onComplete, setSubmitting, validateNameExpressions } = this.props;
const { model } = this.state;
const { name, domain } = model;
Expand Down Expand Up @@ -239,6 +240,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
this.setState({
nameExpressionWarnings: response.warnings,
namePreviews: response.previews,
auditUserComment,
});
});
return;
Expand All @@ -261,6 +263,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
domain: domainDesign,
kind: Domain.KINDS.DATA_CLASS,
name: model.name,
auditUserComment,
options,
});

Expand Down Expand Up @@ -340,6 +343,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
setSubmitting(false, () => {
this.setState({
nameExpressionWarnings: undefined,
auditUserComment: undefined,
});
});
};
Expand All @@ -349,7 +353,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
() => ({
nameExpressionWarnings: undefined,
}),
() => this.saveDomain(true)
() => this.saveDomain(true, this.state.auditUserComment)
);
};

Expand Down Expand Up @@ -461,7 +465,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
firstState,
helpTopic,
domainFormDisplayOptions,
showGenIdBanner,
isUpdate,
allowParentAlias,
allowFolderExclusion,
} = this.props;
Expand All @@ -480,6 +484,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
onCancel={onCancel}
onFinish={this.onFinish}
saveBtnText={saveBtnText}
showUserComment={isUpdate && appPropertiesOnly}
>
<DataClassPropertiesPanel
nounSingular={nounSingular}
Expand All @@ -504,7 +509,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
previewName={namePreviews?.[0]}
onNameFieldHover={this.onNameFieldHover}
nameExpressionGenIdProps={
showGenIdBanner && hasGenIdInExpression
isUpdate && hasGenIdInExpression
? {
containerPath: model.containerPath,
dataTypeName: model.name,
Expand Down
Loading