Skip to content

Commit

Permalink
Merge 23.11 to 24.3
Browse files Browse the repository at this point in the history
  • Loading branch information
labkey-teamcity committed Aug 23, 2024
2 parents be7ce8a + 6ab4427 commit 56db25e
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 32 deletions.
6 changes: 3 additions & 3 deletions snprc_ehr/resources/queries/study/u24MarmosetStats.sql
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,11 @@ FROM
ac.label AS ageClass,
CASE WHEN aa.account IN('4876-001-00', '3508-402-12') THEN 1 ELSE 0 END AS U24_Status,
CASE WHEN aa.account = '3508-402-12' AND ac.label = 'Adult' THEN 1 ELSE 0 END AS available_to_transfer,
CASE WHEN do.HousingStatus.Description = 'Single' THEN 0 --single housed
WHEN do.HousingStatus.Description = 'Group' AND va.accountGroup <> 'Breeder' THEN 1 -- natal family unit
CASE WHEN do.HousingStatus.Description IN ('Single', 'Grooming Contact', 'Visual Contact') THEN 0 --single housed
WHEN do.HousingStatus.Description IN ('Group', 'with Dam', 'with Infant') AND va.accountGroup <> 'Breeder' THEN 1 -- natal family unit
WHEN do.HousingStatus.Description IN ('Paired', 'Group') AND va.accountGroup = 'Breeder' THEN 2 -- active breeding
WHEN do.HousingStatus.Description = 'Paired' AND va.accountGroup <> 'Breeder' THEN 3 -- social non breeding
WHEN do.HousingStatus IS NULL and ac.label = 'Infant' THEN 1 -- *Asumption* we don't yet have observation data and animal is an infant - assume it is with its dam
WHEN do.HousingStatus IS NULL AND ac.label = 'Infant' THEN 1 -- *Asumption* we don't yet have observation data and animal is an infant - assume it is with its dam
ELSE NULL END AS current_housing_status
FROM study.animalaccounts AS aa
INNER JOIN study.demographics AS d ON aa.id = d.id AND d.calculated_status = 'Alive' AND d.species = 'CTJ'
Expand Down
3 changes: 2 additions & 1 deletion snprc_ehr/resources/web/snprc_ehr/snprcReports.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,8 @@ EHR.reports.SndEvents = function (panel, tab) {
filterConfig: JSON.stringify({
filters: tab.filters
}),
hasPermission: userPermsInfo.container.effectivePermissions.includes('org.labkey.api.security.permissions.AdminPermission')
hasReadPermission: userPermsInfo.container.effectivePermissions.includes('org.labkey.snd.security.permissions.SNDViewerPermission'),
hasWritePermission: userPermsInfo.container.effectivePermissions.includes('org.labkey.snd.security.permissions.SNDEditorPermission')
}

const wp = new LABKEY.WebPart({
Expand Down
21 changes: 11 additions & 10 deletions snprc_ehr/src/client/SndEventsWidget/SndEventsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ import React, { FC, memo, useState, useEffect } from 'react';
import { EventListingGridPanel } from './components/EventListingGridPanel';
import './styles/sndEventsWidget.scss';
import { FormGroup, ControlLabel, FormControl } from 'react-bootstrap';
import { getMultiRow } from './actions';
import { getMultiRow } from './actions/actions';
import { Alert } from '@labkey/components';

interface Props {
filterConfig: any,
hasPermission?: boolean
hasReadPermission: boolean,
hasWritePermission: boolean
}

export const SndEventsWidget: FC<Props> = memo((props: Props) => {
const {filterConfig, hasPermission} = props;
const {filterConfig, hasReadPermission, hasWritePermission} = props;
const [subjectIds, setSubjectIds] = useState<string[]>(['']);
const [message, setMessage] = useState<string>(undefined);
const [status, setStatus] = useState<string>(undefined);

useEffect(() => {
(async () => {
if (hasPermission && filterConfig !== undefined && filterConfig.length != 0) {
if (hasReadPermission && filterConfig !== undefined && filterConfig.length != 0) {
await getSubjectIdsFromFilters(filterConfig, setSubjectIds);
}
})();
Expand Down Expand Up @@ -62,24 +63,24 @@ export const SndEventsWidget: FC<Props> = memo((props: Props) => {

return (
<div>
{!hasPermission && (
{!hasReadPermission && (
<Alert>User Does not have permission to view this panel</Alert>
)}
{hasPermission && (filterConfig === undefined || filterConfig.length == 0) && (
{hasReadPermission && (filterConfig === undefined || filterConfig.length == 0) && (
form()
)
}
{hasPermission && (filterConfig !== undefined && filterConfig.length != 0 && filterConfig?.filters.inputType === 'none') && (
{hasReadPermission && (filterConfig !== undefined && filterConfig.length != 0 && filterConfig?.filters.inputType === 'none') && (
<Alert>'Entire Database' filter is not supported for this query.</Alert>
)}
{hasPermission && subjectIds[0] === 'none' && (
{hasReadPermission && subjectIds[0] === 'none' && (
<Alert>No animals were found for filter selections</Alert>
)}
{message && (
<Alert bsStyle={status}>{message}</Alert>
)}
{hasPermission && subjectIds && (
<EventListingGridPanel subjectIDs={subjectIds} onChange={handleUpdateResponse} onError={handleError}/>
{hasReadPermission && subjectIds && (
<EventListingGridPanel subjectIDs={subjectIds} onChange={handleUpdateResponse} onError={handleError} hasWritePermission={hasWritePermission}/>
)}

</div>
Expand Down
80 changes: 80 additions & 0 deletions snprc_ehr/src/client/SndEventsWidget/actions/fetchEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {ActionURL, getServerContext } from "@labkey/api";

export const fetchEvent = async (eventID: string | undefined): Promise<FetchAnimalEventResponse> => {
const url = ActionURL.buildURL('snd', 'getEvent.api')
const headers = {
'Content-Type': 'application/json',
accept: 'application/json',
'X-LABKEY-CSRF': getServerContext().CSRF
}

const body = JSON.stringify({
eventId: eventID,
getTextNarrative: false,
getRedactedNarrative: false,
getHtmlNarrative: false,
getRedactedHtmlNarrative: false,
})

return fetch(url, {
headers,
body,
method: 'post',
credentials: 'include',
}).then(async response => {
const data = await response.json()
if (!data.success) {
throw new Error(data.event.exception.message)
}
if(response.status === 200)
return data.event
})
}

export type FetchAnimalEventResponse = {
date: string
eventData: SuperPackageEventData[]
eventId: number
extraFields: Field[]
htmlNarrative: string
note: string
projectIdRev: string
qcState: QCState
redactedHtmlNarrative: string
subjectId: string
textNarrative: string
}

type SuperPackageEventData = {
attributes: []
eventDataId: number
extraFields: Field[]
narrativeTemplate: string
subPackages: SuperPackageEventData[]
superPkgId: number
}

type Field = {
}

type QCState = 'Completed' | 'In Progress' | 'Review Required' | 'Rejected'

type ResponseError = {
exception: string
}

export class ResponseHadErrorException extends Error {
stackTrace: string[]

constructor({ exception, exceptionClass, stackTrace }: ResponseWithError) {
super(exception)
this.name = exceptionClass
this.stackTrace = stackTrace
}
}

interface ResponseWithError {
exception: string
exceptionClass: string
stackTrace: string[]
}
89 changes: 89 additions & 0 deletions snprc_ehr/src/client/SndEventsWidget/actions/saveEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { ActionURL, getServerContext } from '@labkey/api';

export const saveEvent = async (eventToSave: EventToSave): Promise<SaveEventResponse> => {
const url = ActionURL.buildURL('snd', 'saveEvent.api').replace('.org//', '.org/')

const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(eventToSave),
credentials: 'include',
headers: {
'Content-Type': 'application/json',
accept: 'application/json',
'X-LABKEY-CSRF': getServerContext().CSRF,
},
})

const json = await response.json()

if (response?.status === 500) {
throw new LabkeyError(json)
}

if (response?.status !== 200) {
// @todo, investigate if this is needed
throw new LabkeyError(json)
}

if (!json.success) {
throw new LabkeyError(json)
}

return json
}

export interface EventToSave {
eventId?: number,
date: string, // YYYY-MM-DDTHH:mm:ss
projectIdRev: string
subjectId: string,
qcState: QCState,
note?: string,
extraFields?: PropertyDescriptor[],
eventData: EventData[]
}

export interface SaveEventResponse {
success: boolean
event: Event
}

class LabkeyError extends Error {
constructor({ exception, exceptionClass, stackTrace }) {
const message = `
${exception}
${stackTrace.join('\n\t').toString()}
`.trim()

super(message)
this.name = exceptionClass
this.stack = stackTrace
}
}

interface EventData {
eventDataId?: number,
exception?: Exception
superPkgId: number,
narrativeTemplate?: string,
extraFields?: PropertyDescriptor[],
attributes: Attribute[]
subPackages: EventData[]
}

export interface Attribute {
propertyId: number,
propertyName?: string,
value: string | number,
propertyDescriptor?: PropertyDescriptor
exception?: Exception
}


interface Exception {
message: string,
severity: string
}

type QCState = 'Completed' | 'Rejected' | 'Review Required' | 'In Progress'
2 changes: 1 addition & 1 deletion snprc_ehr/src/client/SndEventsWidget/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import {SndEventsWidget} from "./SndEventsWidget";

// Need to wait for container element to be available in labkey wrapper before render
window.addEventListener('DOMContentLoaded', (event) => {
const config = {filterConfig: [], hasPermission: true}
const config = {filterConfig: [], hasReadPermission: true, hasWritePermission: true }
ReactDOM.render(<SndEventsWidget {...config} />, document.getElementById('app'));
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { FC, memo, useRef, useState } from 'react';
import { OverlayTrigger, Popover } from 'react-bootstrap';
import { getTableRow } from '../actions';
import { getTableRow } from '../actions/actions';

interface Props {
admitChargeId: string,
Expand Down
71 changes: 71 additions & 0 deletions snprc_ehr/src/client/SndEventsWidget/components/DeleteModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { FC, memo, ReactNode, useState } from 'react';
import { ConfirmModal, resolveErrorMessage } from '@labkey/components';
import { FetchAnimalEventResponse, fetchEvent } from '../actions/fetchEvent';
import { EventToSave, saveEvent } from '../actions/saveEvent';

interface Props {
onCancel: () => any,
onComplete: (message, status) => any,
onError: (message) => any,
eventId: string,
}
export const DeleteModal: FC<Props> = memo((props: Props) => {
const {onCancel, onComplete, onError, eventId } = props;
const [error, setError] = useState<ReactNode>(undefined);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

const handleDelete = async () => {
setIsSubmitting(true);

const event: FetchAnimalEventResponse | void = await fetchEvent(eventId).catch(error => {
console.error(error);
setError(resolveErrorMessage(error, "Event", "Event" + 's', 'delete'));
setIsSubmitting(false);
});

if (event) {
const deleteRequest: EventToSave = {
eventId: event.eventId,
date: event.date,
projectIdRev: event.projectIdRev,
subjectId: event.subjectId,
qcState: event.qcState,
note: '',
extraFields: event.extraFields,
eventData: []
}

saveEvent(deleteRequest)
.then(t => {
if (t.success) {
onComplete('Successfully deleted Event', 'success');
} else {
onComplete('There was a problem deleting the Event', 'danger');
}

})
.catch(error => {
console.error(error);
setError(resolveErrorMessage(error, "Event", "Event" + 's', 'delete'));
setIsSubmitting(false);
});
}

}

return (
<div className={'delete-modal'}>
<ConfirmModal
title={'Delete Event'}
onConfirm={handleDelete}
onCancel={onCancel}
confirmVariant={'danger'}
confirmButtonText={'Yes, Permanently delete Event'}
cancelButtonText={'Cancel'}
submitting={isSubmitting}
>
<p><b>Event for this Procedure will be permanently deleted. Do you want to proceed?</b></p>
</ConfirmModal>
</div>
)
})
Loading

0 comments on commit 56db25e

Please sign in to comment.