Skip to content

Commit ead1ad1

Browse files
authored
Plate Set Auditing (#1855)
1 parent e873a40 commit ead1ad1

6 files changed

Lines changed: 91 additions & 61 deletions

File tree

packages/components/package-lock.json

Lines changed: 2 additions & 2 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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "6.62.6",
3+
"version": "6.62.7",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

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

4+
### version 6.62.7
5+
*Released*: 29 September 2025
6+
- Enumerate plate, plate set auditing events
7+
- Introduce `isQueryUpdateEvent` to flag which events are backed by query audit events
8+
49
### version 6.62.6
510
*Released*: 29 September 2025
611
- Issue 53979: TextInput to handle non-finite numeric values

packages/components/src/internal/components/auditlog/AuditDetails.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2016-2018 LabKey Corporation. All rights reserved. No portion of this work may be reproduced in
33
* any form or by any electronic or mechanical means without written permission from LabKey Corporation.
44
*/
5-
import React, { Component, PropsWithChildren } from 'react';
5+
import React, { Component, PropsWithChildren, ReactNode } from 'react';
66
import { List, Map } from 'immutable';
77

88
import { User } from '../base/models/User';
@@ -17,11 +17,17 @@ import { AuditDetailsModel } from './models';
1717
import { LabelHelpTip } from '../base/LabelHelpTip';
1818
import { AUDIT_DETAIL_FIELD_VALUE_INHERITED } from './constants';
1919

20+
const USER_FIELDS = ['created by', 'createdby', 'modified by', 'modifiedby'];
21+
22+
function isUserFieldLabel(field: string): boolean {
23+
return USER_FIELDS.indexOf(field.toLowerCase()) > -1;
24+
}
25+
2026
interface Props extends PropsWithChildren {
2127
changeDetails?: AuditDetailsModel;
2228
emptyMsg?: string;
23-
fieldValueRenderer?: (label, value, displayValue, hasProvidedValue?: boolean) => any;
24-
gridColumnRenderer?: (data: any, row: any, displayValue: any) => any;
29+
fieldValueRenderer?: (label: string, value: any, displayValue: any, hasProvidedValue?: boolean) => ReactNode;
30+
gridColumnRenderer?: (data: any, row: any, displayValue: any) => ReactNode;
2531
gridData?: List<Map<string, any>>;
2632
inheritedFieldMsg?: string;
2733
rowId?: number;
@@ -36,10 +42,6 @@ export class AuditDetails extends Component<Props> {
3642
emptyMsg: 'No audit event selected.',
3743
};
3844

39-
static isUserFieldLabel(field: string): boolean {
40-
return ['created by', 'createdby', 'modified by', 'modifiedby'].indexOf(field.toLowerCase()) > -1;
41-
}
42-
4345
getValueDisplay = (field: string, value: string, hasProvidedValues: boolean, isInherited?: boolean): any => {
4446
const { fieldValueRenderer } = this.props;
4547

@@ -48,7 +50,7 @@ export class AuditDetails extends Component<Props> {
4850
let displayVal: any = value;
4951
if (value == null || value === '') displayVal = 'NA';
5052

51-
if (AuditDetails.isUserFieldLabel(field) && value !== undefined) {
53+
if (isUserFieldLabel(field) && value !== undefined) {
5254
displayVal = <UserLink userId={parseInt(value, 10)} />;
5355
}
5456

@@ -68,7 +70,7 @@ export class AuditDetails extends Component<Props> {
6870
): React.ReactNode {
6971
const { user, inheritedFieldMsg } = this.props;
7072

71-
if (!user.isSignedIn && AuditDetails.isUserFieldLabel(field)) return null;
73+
if (!user.isSignedIn && isUserFieldLabel(field)) return null;
7274

7375
const oldValue = this.getValueDisplay(
7476
field,
@@ -88,6 +90,7 @@ export class AuditDetails extends Component<Props> {
8890
if (providedVal) {
8991
providedVals.push('Provided value: ' + providedVal);
9092
}
93+
9194
return (
9295
<div className="row margin-bottom" key={field}>
9396
<div className="left-padding right-padding">
@@ -131,10 +134,9 @@ export class AuditDetails extends Component<Props> {
131134

132135
renderChanges() {
133136
const { changeDetails } = this.props;
134-
135137
const isUpdate = changeDetails.isUpdate();
136138
const isInsert = changeDetails.isInsert();
137-
const usedFields = [];
139+
const usedFields: string[] = [];
138140

139141
let newFields, oldFields;
140142
if (changeDetails.oldData) {

packages/components/src/internal/components/auditlog/constants.ts

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,121 +4,139 @@ export type AuditQuery = {
44
containerFilter?: Query.ContainerFilter;
55
hasDetail?: boolean;
66
hasTransactionId?: boolean;
7+
/** Indicates that the audit event is backed by a query update event. */
8+
isQueryUpdateEvent?: boolean;
79
label: string;
810
value: string;
911
};
1012

11-
export const ATTACHMENT_AUDIT_QUERY: AuditQuery = { label: 'Attachment Events', value: 'attachmentauditevent' };
12-
export const DOMAIN_AUDIT_QUERY: AuditQuery = { label: 'Domain Events', value: 'domainauditevent' };
13+
export const AUDIT_EVENT_TYPE_PARAM = 'eventType';
14+
15+
export const ATTACHMENT_AUDIT_QUERY: AuditQuery = { label: 'Attachment Events', value: 'AttachmentAuditEvent' };
16+
export const DOMAIN_AUDIT_QUERY: AuditQuery = { label: 'Domain Events', value: 'DomainAuditEvent' };
1317
export const DOMAIN_PROPERTY_AUDIT_QUERY: AuditQuery = {
1418
label: 'Domain Property Events',
15-
value: 'domainpropertyauditevent',
19+
value: 'DomainPropertyAuditEvent',
1620
};
1721
export const QUERY_UPDATE_AUDIT_QUERY: AuditQuery = {
1822
hasDetail: true,
1923
label: 'Query Update Events',
20-
value: 'queryupdateauditevent',
24+
value: 'QueryUpdateAuditEvent',
2125
};
2226

2327
export const DATACLASS_DATA_UPDATE_AUDIT_QUERY: AuditQuery = {
2428
hasDetail: true,
2529
hasTransactionId: true,
30+
isQueryUpdateEvent: true,
2631
label: 'Data Update Events',
27-
value: 'dataclassdataauditevent',
32+
value: 'DataClassDataAuditEvent',
2833
};
2934

3035
export const INVENTORY_AUDIT_QUERY: AuditQuery = {
3136
hasDetail: true,
3237
hasTransactionId: true,
3338
label: 'Storage Management Events',
34-
value: 'inventoryauditevent',
39+
value: 'InventoryAuditEvent',
3540
};
36-
export const LIST_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'List Events', value: 'listauditevent' };
41+
export const LIST_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'List Events', value: 'ListAuditEvent' };
3742
export const GROUP_AUDIT_QUERY: AuditQuery = {
3843
containerFilter: Query.ContainerFilter.allFolders,
3944
label: 'Roles and Assignment Events',
40-
value: 'groupauditevent',
45+
value: 'GroupAuditEvent',
4146
};
4247
export const CONTAINER_AUDIT_QUERY: AuditQuery = {
4348
containerFilter: Query.ContainerFilter.allFolders,
4449
label: 'Folder Events',
45-
value: 'containerauditevent',
50+
value: 'ContainerAuditEvent',
4651
};
4752
export const SAMPLE_TYPE_AUDIT_QUERY: AuditQuery = {
4853
hasTransactionId: true,
4954
label: 'Sample Type Events',
50-
value: 'samplesetauditevent',
55+
value: 'SampleSetAuditEvent',
5156
};
5257
export const SAMPLE_TIMELINE_AUDIT_QUERY: AuditQuery = {
5358
hasDetail: true,
5459
hasTransactionId: true,
5560
label: 'Sample Timeline Events',
56-
value: 'sampletimelineevent',
61+
value: 'SampleTimelineEvent',
5762
};
5863
export const USER_AUDIT_QUERY: AuditQuery = {
5964
containerFilter: Query.ContainerFilter.allFolders,
6065
label: 'User Events',
61-
value: 'userauditevent',
66+
value: 'UserAuditEvent',
6267
};
6368
export const ASSAY_AUDIT_QUERY: AuditQuery = {
6469
hasTransactionId: true,
65-
value: 'assayauditevent',
6670
label: 'Assay Events',
71+
value: 'AssayAuditEvent',
6772
};
6873
export const ASSAY_RESULT_AUDIT_QUERY: AuditQuery = {
6974
hasDetail: true,
7075
hasTransactionId: true,
76+
isQueryUpdateEvent: true,
7177
label: 'Assay Result Events',
72-
value: 'assayresultauditevent',
78+
value: 'AssayResultAuditEvent',
7379
};
7480
export const WORKFLOW_AUDIT_QUERY: AuditQuery = {
7581
hasDetail: true,
7682
label: 'Sample Workflow Events',
77-
value: 'samplesworkflowauditevent',
83+
value: 'SamplesWorkflowAuditEvent',
7884
};
7985
export const SOURCE_AUDIT_QUERY: AuditQuery = {
8086
hasDetail: true,
8187
hasTransactionId: true,
88+
isQueryUpdateEvent: true,
8289
label: 'Sources Events',
83-
value: 'sourcesauditevent',
84-
};
85-
86-
export const NOTEBOOK_AUDIT_QUERY: AuditQuery = {
87-
label: 'Notebook Events',
88-
value: 'LabBookEvent',
89-
};
90-
91-
export const NOTEBOOK_REVIEW_AUDIT_QUERY: AuditQuery = {
92-
label: 'Notebook Review Events',
93-
value: 'NotebookEvent',
90+
value: 'SourcesAuditEvent',
9491
};
9592

93+
export const NOTEBOOK_AUDIT_QUERY: AuditQuery = { label: 'Notebook Events', value: 'LabBookEvent' };
94+
export const NOTEBOOK_REVIEW_AUDIT_QUERY: AuditQuery = { label: 'Notebook Review Events', value: 'NotebookEvent' };
9695
export const REGISTRY_AUDIT_QUERY: AuditQuery = { label: 'Registry Events', value: 'RegistryEvent' };
97-
9896
export const REPORT_AUDIT_QUERY: AuditQuery = { label: 'Report Events', value: 'ReportEvent' };
9997

10098
export const FILE_SYSTEM_AUDIT_QUERY: AuditQuery = {
10199
hasTransactionId: true,
102100
label: 'File Events',
103-
value: 'filesystem',
101+
value: 'FileSystem',
104102
};
105103

106-
export const AUDIT_EVENT_TYPE_PARAM = 'eventType';
104+
export const PLATE_AUDIT_QUERY: AuditQuery = {
105+
hasDetail: true,
106+
hasTransactionId: true,
107+
label: 'Plate Events',
108+
value: 'PlateEvent',
109+
};
110+
111+
export const PLATE_DATA_AUDIT_QUERY: AuditQuery = {
112+
hasDetail: true,
113+
hasTransactionId: true,
114+
isQueryUpdateEvent: true,
115+
label: 'Plate Data Events',
116+
value: 'PlateDataAuditEvent',
117+
};
118+
119+
export const PLATE_SET_AUDIT_QUERY: AuditQuery = {
120+
hasDetail: true,
121+
hasTransactionId: true,
122+
label: 'Plate Set Events',
123+
value: 'PlateSetEvent',
124+
};
107125

108126
export const COMMON_AUDIT_QUERIES: AuditQuery[] = [
109127
ATTACHMENT_AUDIT_QUERY,
110128
DOMAIN_AUDIT_QUERY,
111129
DOMAIN_PROPERTY_AUDIT_QUERY,
112130
FILE_SYSTEM_AUDIT_QUERY,
113-
QUERY_UPDATE_AUDIT_QUERY,
131+
GROUP_AUDIT_QUERY,
114132
INVENTORY_AUDIT_QUERY,
115133
LIST_AUDIT_QUERY,
116-
GROUP_AUDIT_QUERY,
134+
QUERY_UPDATE_AUDIT_QUERY,
117135
SAMPLE_TYPE_AUDIT_QUERY,
118136
SAMPLE_TIMELINE_AUDIT_QUERY,
119137
USER_AUDIT_QUERY,
120138
];
121139

122-
export const EXPERIMENT_AUDIT_EVENT = 'experimentauditevent';
140+
export const EXPERIMENT_AUDIT_EVENT = 'ExperimentAuditEvent';
123141

124142
export const AUDIT_DETAIL_FIELD_VALUE_INHERITED = '$$aliquot-inherited-field$$';

packages/components/src/internal/components/auditlog/utils.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
*/
55
import React, { ReactNode } from 'react';
66
import { Map, OrderedMap } from 'immutable';
7+
import { QueryKey } from '@labkey/api';
78

89
import { FREEZER_MANAGER_PRODUCT_ID, isSampleManagerEnabled } from '../../app/products';
910
import {
1011
isAssayEnabled,
1112
isChartBuilderEnabled,
1213
isELNEnabled,
14+
isPlatesEnabled,
1315
isProductFoldersEnabled,
1416
isRegistryEnabled,
1517
isWorkflowEnabled,
@@ -30,31 +32,28 @@ import {
3032
DATACLASS_DATA_UPDATE_AUDIT_QUERY,
3133
NOTEBOOK_AUDIT_QUERY,
3234
NOTEBOOK_REVIEW_AUDIT_QUERY,
35+
PLATE_AUDIT_QUERY,
36+
PLATE_DATA_AUDIT_QUERY,
37+
PLATE_SET_AUDIT_QUERY,
3338
REGISTRY_AUDIT_QUERY,
3439
REPORT_AUDIT_QUERY,
3540
SOURCE_AUDIT_QUERY,
3641
WORKFLOW_AUDIT_QUERY,
3742
} from './constants';
38-
import { QueryKey } from '@labkey/api';
43+
import { GENERAL_ASSAY_PROVIDER_NAME } from '../assay/constants';
3944

4045
export function getAuditQueries(ctx: ModuleContext): AuditQuery[] {
4146
const queries = [...COMMON_AUDIT_QUERIES];
47+
4248
if (isProductFoldersEnabled(ctx)) queries.push(CONTAINER_AUDIT_QUERY);
4349
if (isWorkflowEnabled(ctx)) queries.push(WORKFLOW_AUDIT_QUERY);
44-
if (isAssayEnabled(ctx)) {
45-
queries.push(ASSAY_AUDIT_QUERY);
46-
queries.push(ASSAY_RESULT_AUDIT_QUERY);
47-
}
50+
if (isAssayEnabled(ctx)) queries.push(ASSAY_AUDIT_QUERY, ASSAY_RESULT_AUDIT_QUERY);
4851
if (isSampleManagerEnabled(ctx) && !isRegistryEnabled(ctx)) queries.push(SOURCE_AUDIT_QUERY);
49-
if (isRegistryEnabled(ctx)) {
50-
queries.push(DATACLASS_DATA_UPDATE_AUDIT_QUERY);
51-
queries.push(REGISTRY_AUDIT_QUERY);
52-
}
53-
if (isELNEnabled(ctx)) {
54-
queries.push(NOTEBOOK_AUDIT_QUERY);
55-
queries.push(NOTEBOOK_REVIEW_AUDIT_QUERY);
56-
}
52+
if (isRegistryEnabled(ctx)) queries.push(DATACLASS_DATA_UPDATE_AUDIT_QUERY, REGISTRY_AUDIT_QUERY);
53+
if (isELNEnabled(ctx)) queries.push(NOTEBOOK_AUDIT_QUERY, NOTEBOOK_REVIEW_AUDIT_QUERY);
5754
if (isChartBuilderEnabled(ctx)) queries.push(REPORT_AUDIT_QUERY);
55+
if (isPlatesEnabled(ctx)) queries.push(PLATE_AUDIT_QUERY, PLATE_DATA_AUDIT_QUERY, PLATE_SET_AUDIT_QUERY);
56+
5857
return queries.sort(naturalSortByProperty('label'));
5958
}
6059

@@ -113,11 +112,17 @@ export function getTimelineEntityUrl(d: Record<string, any>): AppURL {
113112
switch (urlType) {
114113
case 'assayRun':
115114
if (Array.isArray(value) && value.length > 1) {
116-
url = AppURL.create(ASSAYS_KEY, 'general', value[0], 'runs', value[1]);
115+
url = AppURL.create(
116+
ASSAYS_KEY,
117+
GENERAL_ASSAY_PROVIDER_NAME.toLowerCase(),
118+
value[0],
119+
'runs',
120+
value[1]
121+
);
117122
}
118123
break;
119124
case ASSAYS_KEY:
120-
url = AppURL.create(ASSAYS_KEY, 'general', value);
125+
url = AppURL.create(ASSAYS_KEY, GENERAL_ASSAY_PROVIDER_NAME.toLowerCase(), value);
121126
break;
122127
case 'inventoryBox':
123128
url = AppURL.create(BOXES_KEY, value).setProductId(FREEZER_MANAGER_PRODUCT_ID);

0 commit comments

Comments
 (0)