Skip to content

Commit 98253ea

Browse files
authored
Add detailed auditing of domain changes & comment ability (#1792)
1 parent f04f3ef commit 98253ea

10 files changed

Lines changed: 112 additions & 41 deletions

File tree

packages/components/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.43.1",
3+
"version": "6.43.2",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [
@@ -50,7 +50,7 @@
5050
"homepage": "https://github.com/LabKey/labkey-ui-components#readme",
5151
"dependencies": {
5252
"@hello-pangea/dnd": "18.0.1",
53-
"@labkey/api": "1.41.0",
53+
"@labkey/api": "1.41.1",
5454
"@testing-library/dom": "~10.4.0",
5555
"@testing-library/jest-dom": "~6.6.3",
5656
"@testing-library/react": "~16.3.0",

packages/components/releaseNotes/components.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 6.43.2
5+
*Released*: 23 May 2025
6+
- Add detailed auditing of domain changes & comment ability
7+
- include `CommentTextArea` in `BaseDomainDesigner` for supplying user provided comment for domain updates
8+
- wire up `auditUserComment` for `SampleTypeDesigner`, `DataClassDesigner` and `AssayDesignerPanels`
9+
410
### version 6.43.1
5-
*Released*: 22 May 2025
11+
*Released*: 23 May 2025
612
- Issue 53055: Check for multiple values in single value column
713

814
### version 6.43.0

packages/components/src/internal/components/domainproperties/BaseDomainDesigner.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
import React, { PureComponent, ComponentType, FC, memo, PropsWithChildren } from 'react';
1+
import React, {
2+
PureComponent,
3+
ComponentType,
4+
FC,
5+
memo,
6+
PropsWithChildren,
7+
useState,
8+
useCallback,
9+
useMemo,
10+
} from 'react';
211
import { List } from 'immutable';
312

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

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

18+
import { CommentTextArea } from '../forms/input/CommentTextArea';
19+
20+
import { useDataChangeCommentsRequired } from '../forms/input/useDataChangeCommentsRequired';
21+
922
import { getDomainBottomErrorMessage, getDomainHeaderName, getUpdatedVisitedPanelsList } from './actions';
1023
import { DOMAIN_ERROR_ID, SEVERITY_LEVEL_ERROR } from './constants';
1124

@@ -119,8 +132,9 @@ interface BaseDomainDesignerProps extends PropsWithChildren {
119132
hasValidProperties: boolean;
120133
name: string;
121134
onCancel: () => void;
122-
onFinish: () => void;
135+
onFinish: (reason?: string) => void;
123136
saveBtnText?: string;
137+
showUserComment?: boolean;
124138
submitting: boolean;
125139
visitedPanels: List<number>;
126140
}
@@ -137,7 +151,11 @@ export const BaseDomainDesigner: FC<BaseDomainDesignerProps> = memo(props => {
137151
onCancel,
138152
hasValidProperties,
139153
saveBtnText = 'Save',
154+
showUserComment,
140155
} = props;
156+
const [userComment, setUserComment] = useState<string>(undefined);
157+
// skip useDataChangeCommentsRequired hook for LKS pages with showUserComment=false
158+
const requiresUserComment = showUserComment ? useDataChangeCommentsRequired().requiresUserComment : false;
141159

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

168+
const onSave = useCallback(() => {
169+
onFinish(userComment);
170+
}, [userComment, onFinish]);
171+
172+
const canSubmit = useMemo(() => {
173+
if (submitting) return false;
174+
return !requiresUserComment || userComment?.trim()?.length > 0;
175+
}, [requiresUserComment, userComment, submitting]);
176+
150177
return (
151178
<div className="domain-designer">
152179
{children}
@@ -159,7 +186,16 @@ export const BaseDomainDesigner: FC<BaseDomainDesignerProps> = memo(props => {
159186
<button className="cancel-button btn btn-default" onClick={onCancel} type="button">
160187
Cancel
161188
</button>
162-
<button className={submitClassname} disabled={submitting} onClick={onFinish} type="button">
189+
{showUserComment && (
190+
<CommentTextArea
191+
actionName="Update"
192+
containerClassName="inline-comment"
193+
onChange={setUserComment}
194+
requiresUserComment={requiresUserComment}
195+
inline
196+
/>
197+
)}
198+
<button className={submitClassname} disabled={!canSubmit} onClick={onSave} type="button">
163199
{saveBtnText}
164200
</button>
165201
</FormButtons>

packages/components/src/internal/components/domainproperties/actions.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ export function _parseCalculatedColumn(
530530
export interface SaveDomainOptions {
531531
/** Boolean indicating if rowIndices should be added to the error message objects */
532532
addRowIndexes?: boolean;
533+
auditUserComment?: string;
533534
/** Container path where requests are made. Defaults to domain.container for updates. */
534535
containerPath?: string;
535536
/** DomainDesign to save */
@@ -576,6 +577,7 @@ export function saveDomain(options: SaveDomainOptions): Promise<DomainDesign> {
576577
domainDesign: DomainDesign.serialize(domain),
577578
includeWarnings,
578579
options: options.options,
580+
auditUserComment: options.auditUserComment,
579581
success: successHandler,
580582
failure: failureHandler,
581583
});
@@ -902,7 +904,7 @@ export function updateDataType(field: DomainField, value: any): DomainField {
902904
}) as DomainField;
903905
} else {
904906
if (PropDescType.isUser(value)) {
905-
field = field.merge({lookupValidator: LOOKUP_VALIDATOR}) as DomainField;
907+
field = field.merge({ lookupValidator: LOOKUP_VALIDATOR }) as DomainField;
906908
}
907909
}
908910
}

packages/components/src/internal/components/domainproperties/assay/AssayDesignerPanels.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -232,14 +232,14 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
232232
);
233233
}
234234

235-
onFinish = (): void => {
235+
onFinish = (reasonForUpdate?: string): void => {
236236
const { setSubmitting } = this.props;
237237
const { protocolModel } = this.state;
238238
const appIsValidMsg = this.getAppIsValidMsg();
239239
const textChoiceValidMsg = this.getTextChoiceUpdatesValidMsg();
240240
const isValid = protocolModel.isValid() && textChoiceValidMsg === undefined && appIsValidMsg === undefined;
241241

242-
this.props.onFinish(isValid, this.saveDomain);
242+
this.props.onFinish(isValid, () => this.saveDomain(reasonForUpdate));
243243

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

261-
saveDomain = (): void => {
261+
saveDomain = (auditUserComment?: string): void => {
262262
const { beforeFinish, setSubmitting } = this.props;
263263
const { protocolModel } = this.state;
264264

265265
beforeFinish?.(protocolModel);
266266

267-
saveAssayDesign(protocolModel)
267+
saveAssayDesign(protocolModel, auditUserComment)
268268
.then(response => {
269269
this.setState(() => ({ protocolModel }));
270270
setSubmitting(false, () => {
@@ -418,6 +418,7 @@ export class AssayDesignerPanelsImpl extends React.PureComponent<Props, State> {
418418
onCancel={onCancel}
419419
onFinish={this.onFinish}
420420
saveBtnText={saveBtnText}
421+
showUserComment={!initModel.isNew() && appPropertiesOnly}
421422
>
422423
<FilterCriteriaContext.Provider value={filterCriteriaState}>
423424
<AssayPropertiesPanel

packages/components/src/internal/components/domainproperties/assay/actions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import { handleRequestFailure } from '../../../request';
2727

2828
import { AssayProtocolModel } from './models';
2929

30-
export function saveAssayDesign(model: AssayProtocolModel): Promise<AssayProtocolModel> {
30+
export function saveAssayDesign(model: AssayProtocolModel, auditUserComment?: string): Promise<AssayProtocolModel> {
3131
return new Promise((resolve, reject) => {
3232
Ajax.request({
3333
url: ActionURL.buildURL('assay', 'saveProtocol.api', model.container),
34-
jsonData: AssayProtocolModel.serialize(model),
34+
jsonData: AssayProtocolModel.serialize(model, auditUserComment),
3535
success: Utils.getCallbackWrapper(response => {
3636
resolve(AssayProtocolModel.create(response.data));
3737
}),

packages/components/src/internal/components/domainproperties/assay/models.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,16 @@ export class AssayProtocolModel extends ImmutableRecord({
135135
return new AssayProtocolModel({ ...raw, name, domains });
136136
}
137137

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

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

146+
if (auditUserComment) json.auditUserComment = auditUserComment;
147+
146148
// only need to serialize the id and not the autoCopyTargetContainer object
147149
delete json.autoCopyTargetContainer;
148150
delete json.exception;

packages/components/src/internal/components/domainproperties/dataclasses/DataClassDesigner.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ interface Props {
4848
headerText?: string;
4949
helpTopic?: string;
5050
initModel?: DataClassModel;
51+
isUpdate?: boolean;
5152
isValidParentOptionsFn?: (row: any, isDataClass: boolean) => boolean;
5253
// loadNameExpressionOptions is a prop for testing purposes only, see default implementation below
5354
loadNameExpressionOptions?: (
@@ -61,11 +62,11 @@ interface Props {
6162
onChange?: (model: DataClassModel) => void;
6263
onComplete: (model: DataClassModel) => void;
6364
saveBtnText?: string;
64-
showGenIdBanner?: boolean;
6565
validateNameExpressions?: boolean;
6666
}
6767

6868
interface State {
69+
auditUserComment?: string;
6970
model: DataClassModel;
7071
nameExpressionWarnings: string[];
7172
namePreviews: string[];
@@ -156,12 +157,12 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
156157
return name;
157158
};
158159

159-
onFinish = (): void => {
160+
onFinish = (auditUserComment?: string): void => {
160161
const { defaultNameFieldConfig, setSubmitting, nounSingular } = this.props;
161162
const { model } = this.state;
162163
const isValid = model.isValid(defaultNameFieldConfig);
163164

164-
this.props.onFinish(isValid, this.saveDomain);
165+
this.props.onFinish(isValid, () => this.saveDomain(false, auditUserComment));
165166

166167
if (!isValid) {
167168
let exception: string;
@@ -207,7 +208,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
207208
return aliases;
208209
}
209210

210-
saveDomain = async (hasConfirmedNameExpression?: boolean): Promise<void> => {
211+
saveDomain = async (hasConfirmedNameExpression?: boolean, auditUserComment?: string): Promise<void> => {
211212
const { api, beforeFinish, onComplete, setSubmitting, validateNameExpressions } = this.props;
212213
const { model } = this.state;
213214
const { name, domain } = model;
@@ -239,6 +240,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
239240
this.setState({
240241
nameExpressionWarnings: response.warnings,
241242
namePreviews: response.previews,
243+
auditUserComment,
242244
});
243245
});
244246
return;
@@ -261,6 +263,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
261263
domain: domainDesign,
262264
kind: Domain.KINDS.DATA_CLASS,
263265
name: model.name,
266+
auditUserComment,
264267
options,
265268
});
266269

@@ -340,6 +343,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
340343
setSubmitting(false, () => {
341344
this.setState({
342345
nameExpressionWarnings: undefined,
346+
auditUserComment: undefined,
343347
});
344348
});
345349
};
@@ -349,7 +353,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
349353
() => ({
350354
nameExpressionWarnings: undefined,
351355
}),
352-
() => this.saveDomain(true)
356+
() => this.saveDomain(true, this.state.auditUserComment)
353357
);
354358
};
355359

@@ -461,7 +465,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
461465
firstState,
462466
helpTopic,
463467
domainFormDisplayOptions,
464-
showGenIdBanner,
468+
isUpdate,
465469
allowParentAlias,
466470
allowFolderExclusion,
467471
} = this.props;
@@ -480,6 +484,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
480484
onCancel={onCancel}
481485
onFinish={this.onFinish}
482486
saveBtnText={saveBtnText}
487+
showUserComment={isUpdate && appPropertiesOnly}
483488
>
484489
<DataClassPropertiesPanel
485490
nounSingular={nounSingular}
@@ -504,7 +509,7 @@ export class DataClassDesignerImpl extends PureComponent<DataClassDesignerProps,
504509
previewName={namePreviews?.[0]}
505510
onNameFieldHover={this.onNameFieldHover}
506511
nameExpressionGenIdProps={
507-
showGenIdBanner && hasGenIdInExpression
512+
isUpdate && hasGenIdInExpression
508513
? {
509514
containerPath: model.containerPath,
510515
dataTypeName: model.name,

0 commit comments

Comments
 (0)