Skip to content

Commit

Permalink
[43] add comment to location (#44)
Browse files Browse the repository at this point in the history
* WIP created working comment for location

* Add tests for location comment

* Fix eslint errors

* Fix tests failing due to unmocked axios call for location comments
  • Loading branch information
stanislaw-zakrzewski committed Dec 6, 2023
1 parent 7b3b6b2 commit dddd7a8
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 23 deletions.
40 changes: 40 additions & 0 deletions data-serving/data-service/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,31 @@ paths:
$ref: '#/components/responses/403'
'500':
$ref: '#/components/responses/500'
/cases/locationComments:
get:
summary: Lists most frequently used location comments
operationId: listLocationComments
tags: [ Suggest ]
parameters:
- name: limit
in: query
description: The number of items to return
required: false
schema:
type: integer
format: int32
minimum: 1
maximum: 100
default: 5
responses:
'200':
$ref: '#/components/responses/200LocationCommentArray'
'400':
$ref: '#/components/responses/400'
'403':
$ref: '#/components/responses/403'
'500':
$ref: '#/components/responses/500'
/cases/{id}:
parameters:
- name: id
Expand Down Expand Up @@ -890,6 +915,13 @@ components:
type: array
items:
type: string
LocationCommentArray:
type: object
properties:
locationComments:
type: array
items:
type: string
Date:
oneOf:
- type: string
Expand Down Expand Up @@ -936,6 +968,8 @@ components:
description: exact location
geoResolution:
type: string
comment:
type: string
FuzzyLocation:
description: A geo location that can be unspecific. It may not be geocoded if it lacks information.
type: object
Expand Down Expand Up @@ -1098,6 +1132,12 @@ components:
application/json:
schema:
$ref: '#/components/schemas/OccupationArray'
'200LocationCommentArray':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/LocationCommentArray'
'201Case':
description: Created
content:
Expand Down
35 changes: 35 additions & 0 deletions data-serving/data-service/src/controllers/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,41 @@ export const listOccupations = async (
return;
}
};

/**
* List most frequently used location comments.
*
* Handles HTTP GET /api/cases/location-comments.
*/
export const listLocationComments = async (
req: Request,
res: Response,
): Promise<void> => {
const limit = Number(req.query.limit);
try {
const locationComments = await Day0Case.aggregate([
{ $unwind: '$location.comment' },
{ $sortByCount: '$location.comment' },
{ $sort: { count: -1, _id: 1 } },
]).limit(limit);
for (let i = 0; i < locationComments.length; i++) {
if (locationComments[i]._id === null) {
locationComments.splice(i, 1);
}
}
res.json({
locationComments: locationComments.map(
(locationCommentsObject) => locationCommentsObject._id,
),
});
return;
} catch (e) {
if (e instanceof Error) logger.error(e);
res.status(500).json(e);
return;
}
};

function validateCaseAges(caseStart: number, caseEnd: number) {
if (
caseStart < 0 ||
Expand Down
1 change: 1 addition & 0 deletions data-serving/data-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ apiRouter.get('/cases', caseController.list);
apiRouter.get('/cases/symptoms', cases.listSymptoms);
apiRouter.get('/cases/placesOfTransmission', cases.listPlacesOfTransmission);
apiRouter.get('/cases/occupations', cases.listOccupations);
apiRouter.get('/cases/locationComments', cases.listLocationComments);
apiRouter.post(
'/cases/verify/:id(\\d+$)',
createCaseRevision,
Expand Down
2 changes: 2 additions & 0 deletions data-serving/data-service/src/model/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const locationSchema = new mongoose.Schema(
latitude: Number,
longitude: Number,
},
comment: String,
},
{ _id: false },
);
Expand All @@ -42,4 +43,5 @@ export type LocationDocument = mongoose.Document & {
latitude?: number;
longitude?: number;
};
comment?: string;
};
40 changes: 40 additions & 0 deletions verification/curator-service/api/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,31 @@ paths:
$ref: '#/components/responses/403'
'500':
$ref: '#/components/responses/500'
/cases/locationComments:
get:
summary: Lists most frequently used location comments
tags: [ Suggest ]
operationId: listLocationComments
parameters:
- name: limit
in: query
description: The number of items to return
required: false
schema:
type: integer
format: int32
minimum: 1
maximum: 100
default: 5
responses:
'200':
$ref: '#/components/responses/200LocationCommentArray'
'400':
$ref: '#/components/responses/400'
'403':
$ref: '#/components/responses/403'
'500':
$ref: '#/components/responses/500'
/cases/batchStatusChange:
post:
summary: Changes status for a list of cases
Expand Down Expand Up @@ -1651,6 +1676,13 @@ components:
type: array
items:
type: string
LocationCommentArray:
type: object
properties:
locationComments:
type: array
items:
type: string
Date:
oneOf:
- type: string
Expand Down Expand Up @@ -1800,6 +1832,8 @@ components:
- Admin2
- Admin1
- Country
comment:
type: string
FuzzyLocation:
description: A geo location that can be unspecific.
type: object
Expand Down Expand Up @@ -2021,6 +2055,12 @@ components:
application/json:
schema:
$ref: '#/components/schemas/OccupationArray'
'200LocationCommentArray':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/LocationCommentArray'
'200Position':
description: OK
content:
Expand Down
17 changes: 17 additions & 0 deletions verification/curator-service/api/src/controllers/cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,23 @@ export default class CasesController {
}
};

/** listLocationComments simply forwards the request to the data service */
listLocationComments = async (req: Request, res: Response): Promise<void> => {
try {
const response = await axios.get(
this.dataServerURL + '/api' + req.url,
);
res.status(response.status).json(response.data);
} catch (err) {
logger.error(err);
if (err.response?.status && err.response?.data) {
res.status(err.response.status).send(err.response.data);
return;
}
res.status(500).send(err);
}
};

/** get simply forwards the request to the data service */
get = async (req: Request, res: Response): Promise<void> => {
try {
Expand Down
6 changes: 6 additions & 0 deletions verification/curator-service/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ async function makeApp() {
mustHaveAnyRole([Role.Curator, Role.JuniorCurator]),
casesController.listOccupations,
);
apiRouter.get(
'/cases/locationComments',
authenticateByAPIKey,
mustHaveAnyRole([Role.Curator, Role.JuniorCurator]),
casesController.listLocationComments,
);
apiRouter.get(
'/cases/:id(\\d+$)',
authenticateByAPIKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ describe('Curator', function () {
cy.get('input[name="location.district"]').type('Berlin District');
cy.get('input[name="location.place"]').type('Berlin City');
cy.get('input[name="location.location"]').clear().type('Berlin Central Hospital');
cy.get('div[data-testid="location.comment"]').type('2nd wing, 3rd floor');

// EVENTS
cy.get('input[name="events.dateEntry"]').type('2020-01-01');
Expand Down Expand Up @@ -255,6 +256,8 @@ describe('Curator', function () {
'Berlin Central Hospital',
);

cy.get('input[value="2nd wing, 3rd floor"]').should("exist");

// Events.
cy.get('input[name="events.dateEntry"]').should(
'have.value',
Expand Down Expand Up @@ -413,6 +416,8 @@ describe('Curator', function () {
cy.get('input[type="text"]').clear().type('Test occupation');
});
cy.contains('li', 'Test occupation').click();
cy.get('div[data-testid="location.comment"]').clear()
cy.contains('li', '2nd wing, 3rd floor').click();
// Submit the changes.
cy.get('button[data-testid="submit"]').click();

Expand Down
2 changes: 2 additions & 0 deletions verification/curator-service/ui/src/api/models/Day0Case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface Location {
query?: string;
name?: string;
geometry?: Geometry;
comment?: string;
}

export interface Events {
Expand Down Expand Up @@ -286,6 +287,7 @@ export interface Day0CaseFormValues {
geocodeLocation?: GeocodeLocation;
query?: string;
geometry?: Geometry;
comment?: string;
};
events: Events;
symptoms?: string[];
Expand Down
12 changes: 10 additions & 2 deletions verification/curator-service/ui/src/components/CaseForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ describe('<CaseForm />', () => {
headers: {},
};
mockedAxios.get.mockResolvedValueOnce(axiosOccupationResponse);
const axiosLocationCommentsResponse = {
data: { locationComments: [] },
status: 200,
statusText: 'OK',
config: {},
headers: {},
};
mockedAxios.get.mockResolvedValueOnce(axiosLocationCommentsResponse);
});

afterEach(() => {
Expand All @@ -72,13 +80,13 @@ describe('<CaseForm />', () => {
initialRoute: '/cases/new',
},
);
await waitFor(() => expect(mockedAxios.get).toHaveBeenCalledTimes(3));
await waitFor(() => expect(mockedAxios.get).toHaveBeenCalledTimes(4));
expect(
screen.getByText('Enter the details for a new case'),
).toBeInTheDocument();
expect(screen.getByText(/Submit case/i)).toBeInTheDocument();
expect(screen.getAllByText(/Demographics/i)).toHaveLength(1);
expect(screen.getAllByText(/Location/i)).toHaveLength(5);
expect(screen.getAllByText(/Location/i)).toHaveLength(7);
expect(screen.getAllByText(/Events/i)).toHaveLength(1);
expect(screen.getByTestId('caseReference')).toBeInTheDocument();
});
Expand Down
3 changes: 3 additions & 0 deletions verification/curator-service/ui/src/components/ViewCase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,9 @@ function CaseDetails(props: CaseDetailsProps): JSX.Element {
4,
)}`}
/>

<RowHeader title="Comment" />
<RowContent content={props.c.location.comment} />
</Grid>
</Scroll.Element>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import React from 'react';
import { Autocomplete } from '@mui/material';
import { createFilterOptions } from '@mui/material/Autocomplete';
import axios from 'axios';
import { FastField, Field, useFormikContext } from 'formik';

import { AutomatedSourceFormValues } from '../AutomatedSourceForm';
import BulkCaseFormValues from '../bulk-case-form-fields/BulkCaseFormValues';
import { Select, TextField } from 'formik-mui';
import { get } from 'lodash';
import { Autocomplete, FormHelperText } from '@mui/material';
import { createFilterOptions } from '@mui/material/Autocomplete';
import FormControl from '@mui/material/FormControl';
import { FormHelperText } from '@mui/material';
import MenuItem from '@mui/material/MenuItem';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
import MuiTextField from '@mui/material/TextField';
import { Select, TextField } from 'formik-mui';
import axios from 'axios';
import { hasKey } from '../Utils';
import makeStyles from '@mui/styles/makeStyles';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import React, { BaseSyntheticEvent } from 'react';

import { Day0CaseFormValues } from '../../api/models/Day0Case';
import { AutomatedSourceFormValues } from '../AutomatedSourceForm';
import BulkCaseFormValues from '../bulk-case-form-fields/BulkCaseFormValues';
import { hasKey } from '../Utils';

const useStyles = makeStyles(() => ({
fieldRow: {
Expand Down Expand Up @@ -106,7 +106,7 @@ export function FormikAutocomplete(
onClose={(): void => {
setOpen(false);
}}
value={values[props.name] || fallbackValue}
value={get(values, props.name, fallbackValue)}
options={options}
filterOptions={(options: string[], params): string[] => {
const filtered = filter(options, params) as string[];
Expand All @@ -118,9 +118,9 @@ export function FormikAutocomplete(
return filtered;
}}
loading={loading}
onChange={(_, values): void => {
setFieldValue(props.name, values ?? undefined);
}}
onChange={(_, values): void =>
setFieldValue(props.name, values ?? undefined)
}
onBlur={(): void => setTouched({ [props.name]: true })}
defaultValue={props.initialValue}
renderInput={(params): JSX.Element => (
Expand All @@ -134,6 +134,14 @@ export function FormikAutocomplete(
data-testid={props.name}
label={props.label}
component={TextField}
// This onChange event handles situation when user did not
// select element from the list of autocomplete.
// It will only work for single element autocomplete.
onChange={(event: BaseSyntheticEvent) => {
if (!props.multiple) {
setFieldValue(props.name, event.target.value || '');
}
}}
/>
)}
/>
Expand Down
Loading

0 comments on commit dddd7a8

Please sign in to comment.