diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index ef41b45d6b..c431e7d2e7 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "6.62.6", + "version": "6.62.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "6.62.6", + "version": "6.62.7", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 64e8293a2f..ed783d6fcb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "6.62.6", + "version": "6.62.7", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 1e3cfee843..98b5366887 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,11 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 6.62.7 +*Released*: 29 September 2025 +- Enumerate plate, plate set auditing events +- Introduce `isQueryUpdateEvent` to flag which events are backed by query audit events + ### version 6.62.6 *Released*: 29 September 2025 - Issue 53979: TextInput to handle non-finite numeric values diff --git a/packages/components/src/internal/components/auditlog/AuditDetails.tsx b/packages/components/src/internal/components/auditlog/AuditDetails.tsx index 1ab7c69d7c..abe3e8d404 100644 --- a/packages/components/src/internal/components/auditlog/AuditDetails.tsx +++ b/packages/components/src/internal/components/auditlog/AuditDetails.tsx @@ -2,7 +2,7 @@ * Copyright (c) 2016-2018 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, { Component, PropsWithChildren } from 'react'; +import React, { Component, PropsWithChildren, ReactNode } from 'react'; import { List, Map } from 'immutable'; import { User } from '../base/models/User'; @@ -17,11 +17,17 @@ import { AuditDetailsModel } from './models'; import { LabelHelpTip } from '../base/LabelHelpTip'; import { AUDIT_DETAIL_FIELD_VALUE_INHERITED } from './constants'; +const USER_FIELDS = ['created by', 'createdby', 'modified by', 'modifiedby']; + +function isUserFieldLabel(field: string): boolean { + return USER_FIELDS.indexOf(field.toLowerCase()) > -1; +} + interface Props extends PropsWithChildren { changeDetails?: AuditDetailsModel; emptyMsg?: string; - fieldValueRenderer?: (label, value, displayValue, hasProvidedValue?: boolean) => any; - gridColumnRenderer?: (data: any, row: any, displayValue: any) => any; + fieldValueRenderer?: (label: string, value: any, displayValue: any, hasProvidedValue?: boolean) => ReactNode; + gridColumnRenderer?: (data: any, row: any, displayValue: any) => ReactNode; gridData?: List>; inheritedFieldMsg?: string; rowId?: number; @@ -36,10 +42,6 @@ export class AuditDetails extends Component { emptyMsg: 'No audit event selected.', }; - static isUserFieldLabel(field: string): boolean { - return ['created by', 'createdby', 'modified by', 'modifiedby'].indexOf(field.toLowerCase()) > -1; - } - getValueDisplay = (field: string, value: string, hasProvidedValues: boolean, isInherited?: boolean): any => { const { fieldValueRenderer } = this.props; @@ -48,7 +50,7 @@ export class AuditDetails extends Component { let displayVal: any = value; if (value == null || value === '') displayVal = 'NA'; - if (AuditDetails.isUserFieldLabel(field) && value !== undefined) { + if (isUserFieldLabel(field) && value !== undefined) { displayVal = ; } @@ -68,7 +70,7 @@ export class AuditDetails extends Component { ): React.ReactNode { const { user, inheritedFieldMsg } = this.props; - if (!user.isSignedIn && AuditDetails.isUserFieldLabel(field)) return null; + if (!user.isSignedIn && isUserFieldLabel(field)) return null; const oldValue = this.getValueDisplay( field, @@ -88,6 +90,7 @@ export class AuditDetails extends Component { if (providedVal) { providedVals.push('Provided value: ' + providedVal); } + return (
@@ -131,10 +134,9 @@ export class AuditDetails extends Component { renderChanges() { const { changeDetails } = this.props; - const isUpdate = changeDetails.isUpdate(); const isInsert = changeDetails.isInsert(); - const usedFields = []; + const usedFields: string[] = []; let newFields, oldFields; if (changeDetails.oldData) { diff --git a/packages/components/src/internal/components/auditlog/constants.ts b/packages/components/src/internal/components/auditlog/constants.ts index 3efa30039e..304162451f 100644 --- a/packages/components/src/internal/components/auditlog/constants.ts +++ b/packages/components/src/internal/components/auditlog/constants.ts @@ -4,121 +4,139 @@ export type AuditQuery = { containerFilter?: Query.ContainerFilter; hasDetail?: boolean; hasTransactionId?: boolean; + /** Indicates that the audit event is backed by a query update event. */ + isQueryUpdateEvent?: boolean; label: string; value: string; }; -export const ATTACHMENT_AUDIT_QUERY: AuditQuery = { label: 'Attachment Events', value: 'attachmentauditevent' }; -export const DOMAIN_AUDIT_QUERY: AuditQuery = { label: 'Domain Events', value: 'domainauditevent' }; +export const AUDIT_EVENT_TYPE_PARAM = 'eventType'; + +export const ATTACHMENT_AUDIT_QUERY: AuditQuery = { label: 'Attachment Events', value: 'AttachmentAuditEvent' }; +export const DOMAIN_AUDIT_QUERY: AuditQuery = { label: 'Domain Events', value: 'DomainAuditEvent' }; export const DOMAIN_PROPERTY_AUDIT_QUERY: AuditQuery = { label: 'Domain Property Events', - value: 'domainpropertyauditevent', + value: 'DomainPropertyAuditEvent', }; export const QUERY_UPDATE_AUDIT_QUERY: AuditQuery = { hasDetail: true, label: 'Query Update Events', - value: 'queryupdateauditevent', + value: 'QueryUpdateAuditEvent', }; export const DATACLASS_DATA_UPDATE_AUDIT_QUERY: AuditQuery = { hasDetail: true, hasTransactionId: true, + isQueryUpdateEvent: true, label: 'Data Update Events', - value: 'dataclassdataauditevent', + value: 'DataClassDataAuditEvent', }; export const INVENTORY_AUDIT_QUERY: AuditQuery = { hasDetail: true, hasTransactionId: true, label: 'Storage Management Events', - value: 'inventoryauditevent', + value: 'InventoryAuditEvent', }; -export const LIST_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'List Events', value: 'listauditevent' }; +export const LIST_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'List Events', value: 'ListAuditEvent' }; export const GROUP_AUDIT_QUERY: AuditQuery = { containerFilter: Query.ContainerFilter.allFolders, label: 'Roles and Assignment Events', - value: 'groupauditevent', + value: 'GroupAuditEvent', }; export const CONTAINER_AUDIT_QUERY: AuditQuery = { containerFilter: Query.ContainerFilter.allFolders, label: 'Folder Events', - value: 'containerauditevent', + value: 'ContainerAuditEvent', }; export const SAMPLE_TYPE_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'Sample Type Events', - value: 'samplesetauditevent', + value: 'SampleSetAuditEvent', }; export const SAMPLE_TIMELINE_AUDIT_QUERY: AuditQuery = { hasDetail: true, hasTransactionId: true, label: 'Sample Timeline Events', - value: 'sampletimelineevent', + value: 'SampleTimelineEvent', }; export const USER_AUDIT_QUERY: AuditQuery = { containerFilter: Query.ContainerFilter.allFolders, label: 'User Events', - value: 'userauditevent', + value: 'UserAuditEvent', }; export const ASSAY_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, - value: 'assayauditevent', label: 'Assay Events', + value: 'AssayAuditEvent', }; export const ASSAY_RESULT_AUDIT_QUERY: AuditQuery = { hasDetail: true, hasTransactionId: true, + isQueryUpdateEvent: true, label: 'Assay Result Events', - value: 'assayresultauditevent', + value: 'AssayResultAuditEvent', }; export const WORKFLOW_AUDIT_QUERY: AuditQuery = { hasDetail: true, label: 'Sample Workflow Events', - value: 'samplesworkflowauditevent', + value: 'SamplesWorkflowAuditEvent', }; export const SOURCE_AUDIT_QUERY: AuditQuery = { hasDetail: true, hasTransactionId: true, + isQueryUpdateEvent: true, label: 'Sources Events', - value: 'sourcesauditevent', -}; - -export const NOTEBOOK_AUDIT_QUERY: AuditQuery = { - label: 'Notebook Events', - value: 'LabBookEvent', -}; - -export const NOTEBOOK_REVIEW_AUDIT_QUERY: AuditQuery = { - label: 'Notebook Review Events', - value: 'NotebookEvent', + value: 'SourcesAuditEvent', }; +export const NOTEBOOK_AUDIT_QUERY: AuditQuery = { label: 'Notebook Events', value: 'LabBookEvent' }; +export const NOTEBOOK_REVIEW_AUDIT_QUERY: AuditQuery = { label: 'Notebook Review Events', value: 'NotebookEvent' }; export const REGISTRY_AUDIT_QUERY: AuditQuery = { label: 'Registry Events', value: 'RegistryEvent' }; - export const REPORT_AUDIT_QUERY: AuditQuery = { label: 'Report Events', value: 'ReportEvent' }; export const FILE_SYSTEM_AUDIT_QUERY: AuditQuery = { hasTransactionId: true, label: 'File Events', - value: 'filesystem', + value: 'FileSystem', }; -export const AUDIT_EVENT_TYPE_PARAM = 'eventType'; +export const PLATE_AUDIT_QUERY: AuditQuery = { + hasDetail: true, + hasTransactionId: true, + label: 'Plate Events', + value: 'PlateEvent', +}; + +export const PLATE_DATA_AUDIT_QUERY: AuditQuery = { + hasDetail: true, + hasTransactionId: true, + isQueryUpdateEvent: true, + label: 'Plate Data Events', + value: 'PlateDataAuditEvent', +}; + +export const PLATE_SET_AUDIT_QUERY: AuditQuery = { + hasDetail: true, + hasTransactionId: true, + label: 'Plate Set Events', + value: 'PlateSetEvent', +}; export const COMMON_AUDIT_QUERIES: AuditQuery[] = [ ATTACHMENT_AUDIT_QUERY, DOMAIN_AUDIT_QUERY, DOMAIN_PROPERTY_AUDIT_QUERY, FILE_SYSTEM_AUDIT_QUERY, - QUERY_UPDATE_AUDIT_QUERY, + GROUP_AUDIT_QUERY, INVENTORY_AUDIT_QUERY, LIST_AUDIT_QUERY, - GROUP_AUDIT_QUERY, + QUERY_UPDATE_AUDIT_QUERY, SAMPLE_TYPE_AUDIT_QUERY, SAMPLE_TIMELINE_AUDIT_QUERY, USER_AUDIT_QUERY, ]; -export const EXPERIMENT_AUDIT_EVENT = 'experimentauditevent'; +export const EXPERIMENT_AUDIT_EVENT = 'ExperimentAuditEvent'; export const AUDIT_DETAIL_FIELD_VALUE_INHERITED = '$$aliquot-inherited-field$$'; diff --git a/packages/components/src/internal/components/auditlog/utils.ts b/packages/components/src/internal/components/auditlog/utils.ts index 90be434cf8..a898b6b3cd 100644 --- a/packages/components/src/internal/components/auditlog/utils.ts +++ b/packages/components/src/internal/components/auditlog/utils.ts @@ -4,12 +4,14 @@ */ import React, { ReactNode } from 'react'; import { Map, OrderedMap } from 'immutable'; +import { QueryKey } from '@labkey/api'; import { FREEZER_MANAGER_PRODUCT_ID, isSampleManagerEnabled } from '../../app/products'; import { isAssayEnabled, isChartBuilderEnabled, isELNEnabled, + isPlatesEnabled, isProductFoldersEnabled, isRegistryEnabled, isWorkflowEnabled, @@ -30,31 +32,28 @@ import { DATACLASS_DATA_UPDATE_AUDIT_QUERY, NOTEBOOK_AUDIT_QUERY, NOTEBOOK_REVIEW_AUDIT_QUERY, + PLATE_AUDIT_QUERY, + PLATE_DATA_AUDIT_QUERY, + PLATE_SET_AUDIT_QUERY, REGISTRY_AUDIT_QUERY, REPORT_AUDIT_QUERY, SOURCE_AUDIT_QUERY, WORKFLOW_AUDIT_QUERY, } from './constants'; -import { QueryKey } from '@labkey/api'; +import { GENERAL_ASSAY_PROVIDER_NAME } from '../assay/constants'; export function getAuditQueries(ctx: ModuleContext): AuditQuery[] { const queries = [...COMMON_AUDIT_QUERIES]; + if (isProductFoldersEnabled(ctx)) queries.push(CONTAINER_AUDIT_QUERY); if (isWorkflowEnabled(ctx)) queries.push(WORKFLOW_AUDIT_QUERY); - if (isAssayEnabled(ctx)) { - queries.push(ASSAY_AUDIT_QUERY); - queries.push(ASSAY_RESULT_AUDIT_QUERY); - } + if (isAssayEnabled(ctx)) queries.push(ASSAY_AUDIT_QUERY, ASSAY_RESULT_AUDIT_QUERY); if (isSampleManagerEnabled(ctx) && !isRegistryEnabled(ctx)) queries.push(SOURCE_AUDIT_QUERY); - if (isRegistryEnabled(ctx)) { - queries.push(DATACLASS_DATA_UPDATE_AUDIT_QUERY); - queries.push(REGISTRY_AUDIT_QUERY); - } - if (isELNEnabled(ctx)) { - queries.push(NOTEBOOK_AUDIT_QUERY); - queries.push(NOTEBOOK_REVIEW_AUDIT_QUERY); - } + if (isRegistryEnabled(ctx)) queries.push(DATACLASS_DATA_UPDATE_AUDIT_QUERY, REGISTRY_AUDIT_QUERY); + if (isELNEnabled(ctx)) queries.push(NOTEBOOK_AUDIT_QUERY, NOTEBOOK_REVIEW_AUDIT_QUERY); if (isChartBuilderEnabled(ctx)) queries.push(REPORT_AUDIT_QUERY); + if (isPlatesEnabled(ctx)) queries.push(PLATE_AUDIT_QUERY, PLATE_DATA_AUDIT_QUERY, PLATE_SET_AUDIT_QUERY); + return queries.sort(naturalSortByProperty('label')); } @@ -113,11 +112,17 @@ export function getTimelineEntityUrl(d: Record): AppURL { switch (urlType) { case 'assayRun': if (Array.isArray(value) && value.length > 1) { - url = AppURL.create(ASSAYS_KEY, 'general', value[0], 'runs', value[1]); + url = AppURL.create( + ASSAYS_KEY, + GENERAL_ASSAY_PROVIDER_NAME.toLowerCase(), + value[0], + 'runs', + value[1] + ); } break; case ASSAYS_KEY: - url = AppURL.create(ASSAYS_KEY, 'general', value); + url = AppURL.create(ASSAYS_KEY, GENERAL_ASSAY_PROVIDER_NAME.toLowerCase(), value); break; case 'inventoryBox': url = AppURL.create(BOXES_KEY, value).setProductId(FREEZER_MANAGER_PRODUCT_ID);