Skip to content

Commit

Permalink
Merge branch 'dev' into UserRoleTesting
Browse files Browse the repository at this point in the history
  • Loading branch information
NickPhura authored Sep 21, 2023
2 parents 5b1591a + 18aae70 commit 5fe9546
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 47 deletions.
7 changes: 4 additions & 3 deletions app/src/components/fields/CbSelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ICbSelectField extends ICbSelectSharedProps {
route: string;
param?: string;
query?: string;
disabledValues?: Record<string, boolean>;
handleChangeSideEffect?: (value: string, label: string) => void;
}

Expand All @@ -35,7 +36,7 @@ interface ICbSelectOption {
**/

const CbSelectField: React.FC<ICbSelectField> = (props) => {
const { name, label, route, param, query, handleChangeSideEffect, controlProps } = props;
const { name, label, route, param, query, handleChangeSideEffect, controlProps, disabledValues } = props;

const api = useCritterbaseApi();
const isMounted = useIsMounted();
Expand Down Expand Up @@ -83,13 +84,13 @@ const CbSelectField: React.FC<ICbSelectField> = (props) => {
<CbSelectWrapper
name={name}
label={label}
controlProps={{ ...controlProps, disabled: !data?.length }}
controlProps={{ ...controlProps, disabled: controlProps?.disabled || !data?.length }}
onChange={innerChangeHandler}
value={isValueInRange ? val : ''}>
{data?.map((a) => {
const item = typeof a === 'string' ? { label: a, value: a } : { label: a.value, value: a.id };
return (
<MenuItem key={item.value} value={item.value}>
<MenuItem disabled={disabledValues?.[item.value]} key={item.value} value={item.value}>
{item.label}
</MenuItem>
);
Expand Down
10 changes: 8 additions & 2 deletions app/src/constants/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,17 @@ export const SurveyAnimalsI18N = {
animalMortalityHelp:
"Mortality Events describe an individual's death, including the suspected location, date, and cause of death. An individual can only have one Mortality Event.",
animalMortalityAddBtn: 'Add Mortality',

animalCollectionUnitTitle: 'Ecological Units',
animalCollectionUnitTitle2: 'Ecological Unit',
animalCollectionUnitHelp:
'Ecological units are groups such as population units, herds, and packs. Different species may different units and unit names.',
animalCollectionUnitAddBtn: 'Add Unit',
// Input help strings
taxonHelp:
'The species or taxon of the animal. If the species is unknown, select the lowest-ranking known taxon, such as the genus or family',
taxonLabelHelp: 'A custom and unique name for you to recognize this individual'
taxonLabelHelp: 'A custom and unique name for you to recognize this individual',
wlhIdHelp: 'The Wildlife Health ID associated with this critter.',
sexHelp: 'The sex of this critter. Leave as Unknown if unsure.'
};

export const FundingSourceI18N = {
Expand Down
11 changes: 9 additions & 2 deletions app/src/features/surveys/view/SurveyAnimals.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,14 @@ describe('SurveyAnimals', () => {

it('renders correctly with animals', async () => {
mockUseBiohub.survey.getSurveyCritters.mockResolvedValueOnce([
{ critter_id: 'critter_uuid', survey_critter_id: 1, animal_id: 'a', taxon: 'a', created_at: 'a' }
{
critter_id: 'critter_uuid',
survey_critter_id: 1,
animal_id: 'a',
taxon: 'a',
created_at: 'a',
wlh_id: '123-45'
}
]);

mockUseBiohub.survey.getDeploymentsInSurvey.mockResolvedValue([{ critter_id: 'critter_uuid', device_id: 123 }]);
Expand All @@ -119,7 +126,7 @@ describe('SurveyAnimals', () => {
);

await waitFor(() => {
expect(getByText('critter_uuid')).toBeInTheDocument();
expect(getByText('123-45')).toBeInTheDocument();
expect(getByTestId('survey-animal-table')).toBeInTheDocument();
fireEvent.click(getByTestId('animal actions'));
fireEvent.click(getByTestId('animal-table-row-edit-timespan'));
Expand Down
5 changes: 3 additions & 2 deletions app/src/features/surveys/view/SurveyAnimals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import React, { useContext, useState } from 'react';
import { datesSameNullable, pluralize } from 'utils/Utils';
import yup from 'utils/YupSchema';
import NoSurveySectionData from '../components/NoSurveySectionData';
import { AnimalSchema, Critter, IAnimal } from './survey-animals/animal';
import { AnimalSchema, AnimalSex, Critter, IAnimal } from './survey-animals/animal';
import { AnimalTelemetryDeviceSchema, Device, IAnimalTelemetryDevice } from './survey-animals/device';
import IndividualAnimalForm from './survey-animals/IndividualAnimalForm';
import { SurveyAnimalsTable } from './survey-animals/SurveyAnimalsTable';
Expand Down Expand Up @@ -74,10 +74,11 @@ const SurveyAnimals: React.FC = () => {
};

const AnimalFormValues: IAnimal = {
general: { taxon_id: '', taxon_name: '', animal_id: '' },
general: { wlh_id: '', taxon_id: '', taxon_name: '', animal_id: '', sex: AnimalSex.UNKNOWN },
captures: [],
markings: [],
mortality: [],
collectionUnits: [],
measurements: [],
family: [],
images: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Form, useFormikContext } from 'formik';
import { useEffect } from 'react';
import { Critter, IAnimal } from './animal';
import CaptureAnimalForm from './form-sections/CaptureAnimalForm';
import CollectionUnitAnimalForm from './form-sections/CollectionUnitAnimalForm';
import FamilyAnimalForm from './form-sections/FamilyAnimalForm';
import GeneralAnimalForm from './form-sections/GeneralAnimalForm';
import MarkingAnimalForm from './form-sections/MarkingAnimalForm';
Expand Down Expand Up @@ -37,6 +38,7 @@ const IndividualAnimalForm = ({ getAnimalCount }: IndividualAnimalFormProps) =>
<Form>
<Typography variant="h4">Add New Individual</Typography>
<GeneralAnimalForm />
<CollectionUnitAnimalForm />
<CaptureAnimalForm />
<MortalityAnimalForm />
<MarkingAnimalForm />
Expand Down
47 changes: 31 additions & 16 deletions app/src/features/surveys/view/survey-animals/SurveyAnimalsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { GridColDef } from '@mui/x-data-grid';
import { CustomDataGrid } from 'components/tables/CustomDataGrid';
import { DATE_FORMAT } from 'constants/dateTimeFormats';
import { IDetailedCritterWithInternalId } from 'interfaces/useSurveyApi.interface';
import moment from 'moment';
import { getFormattedDate } from 'utils/Utils';
import { IAnimalDeployment } from './device';
import SurveyAnimalsTableActions from './SurveyAnimalsTableActions';
Expand Down Expand Up @@ -47,17 +48,17 @@ export const SurveyAnimalsTable = ({
: animalData;

const columns: GridColDef<ISurveyAnimalsTableEntry>[] = [
{
field: 'critter_id',
headerName: 'Critter ID',
flex: 1,
minWidth: 300
},
{
field: 'animal_id',
headerName: 'Animal ID',
headerName: 'Alias',
flex: 1
},
{
field: 'wlh_id',
headerName: 'WLH ID',
flex: 1,
renderCell: (params) => <Typography>{params.value ? params.value : 'None'}</Typography>
},
{
field: 'taxon',
headerName: 'Taxon',
Expand All @@ -72,16 +73,30 @@ export const SurveyAnimalsTable = ({
)
},
{
field: 'deployments',
headerName: 'Device ID',
field: 'current_devices',
headerName: 'Current Devices',
flex: 1,
renderCell: (params) => (
<Typography>
{params.value?.length
? params.value?.map((device: IAnimalDeployment) => device.device_id).join(', ')
: 'No Device'}
</Typography>
)
valueGetter: (params) => {
const currentDeploys = params.row.deployments?.filter(
(device: IAnimalDeployment) => !device.attachment_end || moment(device.attachment_end).isAfter(moment())
);
return currentDeploys?.length
? currentDeploys.map((device: IAnimalDeployment) => device.device_id).join(', ')
: 'No Devices';
}
},
{
field: 'previous_devices',
headerName: 'Previous Devices',
flex: 1,
valueGetter: (params) => {
const previousDeploys = params.row.deployments?.filter(
(device: IAnimalDeployment) => device.attachment_end && moment(device.attachment_end).isBefore(moment())
);
return previousDeploys?.length
? previousDeploys.map((device: IAnimalDeployment) => device.device_id).join(', ')
: 'No Devices';
}
},
{
field: 'actions',
Expand Down
26 changes: 19 additions & 7 deletions app/src/features/surveys/view/survey-animals/animal.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import yup from 'utils/YupSchema';
import { v4 } from 'uuid';
import {
AnimalSex,
Critter,
getAnimalFieldName,
glt,
IAnimal,
IAnimalMarking,
ICritterMarking,
isRequiredInSchema,
lastAnimalValueValid
} from './animal';

const animal: IAnimal = {
general: { taxon_id: 'a', taxon_name: 'taxon', animal_id: 'animal' },
general: { taxon_id: 'a', taxon_name: 'taxon', animal_id: 'animal', wlh_id: 'a', sex: AnimalSex.MALE },
captures: [
{
_id: v4(),
Expand Down Expand Up @@ -50,7 +50,8 @@ const animal: IAnimal = {
measurements: [],
family: [],
images: [],
device: undefined
device: undefined,
collectionUnits: [{ collection_category_id: 'a', collection_unit_id: 'b', _id: v4() }]
};

describe('Animal', () => {
Expand Down Expand Up @@ -170,11 +171,22 @@ describe('Animal', () => {

it('constructor should create critter markings', () => {
const c_marking = critter.markings[0];
const a_marking = critter.markings[0];
const a_marking = animal.markings[0];
expect(c_marking.critter_id).toBe(critter.critter_id);
for (const prop in c_marking) {
expect(c_marking[prop as keyof ICritterMarking]).toBe(a_marking[prop as keyof ICritterMarking]);
}

expect(c_marking.marking_type_id).toBe(a_marking.marking_type_id);
expect(c_marking.taxon_marking_body_location_id).toBe(a_marking.taxon_marking_body_location_id);
expect(c_marking.primary_colour_id).toBe(a_marking.primary_colour_id);
expect(c_marking.secondary_colour_id).toBe(a_marking.secondary_colour_id);
expect(c_marking.marking_comment).toBe(a_marking.marking_comment);
});

it('constructor should create collections and strip extra vals', () => {
const c_collection = critter.collections[0];
const a_collection = animal.collectionUnits[0];
expect(c_collection.critter_id).toBe(critter.critter_id);
expect((c_collection as any)['collection_category_id']).not.toBeDefined();
expect(c_collection.collection_unit_id).toBe(a_collection.collection_unit_id);
});
});
});
37 changes: 35 additions & 2 deletions app/src/features/surveys/view/survey-animals/animal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import { v4 } from 'uuid';
import { AnyObjectSchema, InferType, reach } from 'yup';
import { AnimalTelemetryDeviceSchema } from './device';

export enum AnimalSex {
MALE = 'Male',
FEMALE = 'Female',
UNKNOWN = 'Unknown',
HERM = 'Hermaphroditic'
}

/**
* Provides an acceptable amount of type security with formik field names for animal forms
* Returns formatted field name in regular or array format
Expand Down Expand Up @@ -56,7 +63,9 @@ export type ProjectionMode = 'wgs' | 'utm';
export const AnimalGeneralSchema = yup.object({}).shape({
taxon_id: yup.string().required(req),
animal_id: yup.string().required(req),
taxon_name: yup.string()
taxon_name: yup.string(),
wlh_id: yup.string(),
sex: yup.mixed<AnimalSex>().oneOf(Object.values(AnimalSex))
});

export const AnimalCaptureSchema = yup.object({}).shape({
Expand Down Expand Up @@ -101,7 +110,13 @@ export const AnimalMarkingSchema = yup.object({}).shape({
secondary_colour_id: yup.string().optional(),
marking_comment: yup.string()
});
//proprietaryDataForm

export const AnimalCollectionUnitSchema = yup.object({}).shape({
_id: yup.string().required(),
collection_unit_id: yup.string().required(),
collection_category_id: yup.string().required()
});

export const AnimalMeasurementSchema = yup.object({}).shape(
{
_id: yup.string().required(),
Expand Down Expand Up @@ -159,6 +174,7 @@ export const AnimalSchema = yup.object({}).shape({
mortality: yup.array().of(AnimalMortalitySchema).required(),
family: yup.array().of(AnimalRelationshipSchema).required(),
images: yup.array().of(AnimalImageSchema).required(),
collectionUnits: yup.array().of(AnimalCollectionUnitSchema).required(),
device: AnimalTelemetryDeviceSchema.default(undefined)
});

Expand All @@ -178,6 +194,8 @@ export type IAnimalCapture = InferType<typeof AnimalCaptureSchema>;

export type IAnimalMarking = InferType<typeof AnimalMarkingSchema>;

export type IAnimalCollectionUnit = InferType<typeof AnimalCollectionUnitSchema>;

export type IAnimalMeasurement = InferType<typeof AnimalMeasurementSchema>;

export type IAnimalMortality = InferType<typeof AnimalMortalitySchema>;
Expand Down Expand Up @@ -220,6 +238,8 @@ type ICritterCapture = Omit<

export type ICritterMarking = Omit<ICritterID & IAnimalMarking, '_id'>;

export type ICritterCollection = Omit<ICritterID & IAnimalCollectionUnit, '_id' | 'collection_category_id'>;

type ICritterQualitativeMeasurement = Omit<ICritterID & IAnimalMeasurement, 'value' | '_id'>;

type ICritterQuantitativeMeasurement = Omit<ICritterID & IAnimalMeasurement, 'qualitative_option_id' | '_id'>;
Expand Down Expand Up @@ -256,6 +276,8 @@ export class Critter {
critter_id: string;
taxon_id: string;
animal_id: string;
wlh_id?: string;
sex?: AnimalSex;
captures: ICritterCapture[];
markings: ICritterMarking[];
measurements: {
Expand All @@ -265,6 +287,7 @@ export class Critter {
mortalities: Omit<ICritterMortality, '_id'>[];
families: ICritterRelationships;
locations: ICritterLocation[];
collections: ICritterCollection[];

private taxon_name?: string;

Expand Down Expand Up @@ -355,6 +378,13 @@ export class Critter {
});
}

_formatCritterCollectionUnits(animal_collections: IAnimalCollectionUnit[]): ICritterCollection[] {
return animal_collections.map((collection) => ({
critter_id: this.critter_id,
...omit(collection, ['_id', 'collection_category_id'])
}));
}

_formatCritterQualitativeMeasurements(animal_measurements: IAnimalMeasurement[]): ICritterQualitativeMeasurement[] {
const filteredQualitativeMeasurements = animal_measurements.filter((measurement) => {
if (measurement.qualitative_option_id && measurement.value) {
Expand Down Expand Up @@ -421,6 +451,8 @@ export class Critter {
this.taxon_id = animal.general.taxon_id;
this.taxon_name = animal.general.taxon_name;
this.animal_id = animal.general.animal_id;
this.wlh_id = animal.general.wlh_id;
this.sex = animal.general.sex;
const { captures, capture_locations } = this._formatCritterCaptures(animal.captures);
const { mortalities, mortalities_locations } = this._formatCritterMortalities(animal.mortality);

Expand All @@ -429,6 +461,7 @@ export class Critter {
this.locations = [...capture_locations, ...mortalities_locations];

this.markings = this._formatCritterMarkings(animal.markings);
this.collections = this._formatCritterCollectionUnits(animal.collectionUnits);

this.measurements = {
qualitative: this._formatCritterQualitativeMeasurements(animal.measurements),
Expand Down
Loading

0 comments on commit 5fe9546

Please sign in to comment.