Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LIST-2974] restore latitude and logitude to location object #5

21 changes: 20 additions & 1 deletion data-serving/data-service/src/controllers/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -858,9 +858,28 @@ export class CasesController {
}
// Currently a 1:1 match between the GeocodeResult and the data service API.
// We also store the original query to match it later on and help debugging.
let found_location = features[0];

// If latitude and longitude was specified by used we want to use it
// and set geoResolution to Point
if (location?.geometry?.latitude && location.geometry?.longitude) {
found_location = {
...found_location,
geometry: location.geometry,
geoResolution: Resolution.Point,
};
}

// If geoResolution was provided by curator we want to use it
if (location?.geoResolution) {
found_location = {
...found_location,
geoResolution: location.geoResolution,
};
}
return {
query: location?.query,
...features[0],
...found_location,
};
}
throw new GeocodeNotFoundError(
Expand Down
10 changes: 10 additions & 0 deletions data-serving/data-service/src/model/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,30 @@ export const locationSchema = new mongoose.Schema(
minLength: 2,
maxLength: 2,
},
geoResolution: String,
// Location represents a precise location, such as an establishment or POI.
location: String,
city: String,
query: String,
name: String,
geometry: {
latitude: Number,
longitude: Number,
},
},
{ _id: false },
);

export type LocationDocument = mongoose.Document & {
country: string;
countryISO2: string;
geoResolution: string;
location?: string;
city?: string;
query?: string;
name?: string;
geometry?: {
latitude?: number;
longitude?: number;
};
};
6 changes: 6 additions & 0 deletions data-serving/data-service/src/util/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,13 @@ function denormalizeLocationFields(
denormalizedData['location.countryISO2'] = doc.countryISO2 || '';
denormalizedData['location.location'] = doc.location || '';
denormalizedData['location.city'] = doc.city || '';
denormalizedData['location.geoResolution'] = doc.geoResolution || '';
denormalizedData['location.geometry.latitude'] =
doc.geometry?.latitude || '';
denormalizedData['location.geometry.longitude'] =
doc.geometry?.longitude || '';
denormalizedData['location.query'] = doc.query || '';

return denormalizedData;
}

Expand Down
179 changes: 178 additions & 1 deletion data-serving/data-service/test/controllers/case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,183 @@ describe('POST', () => {
.expect(201);
expect(await Day0Case.collection.countDocuments()).toEqual(1);
});
it('create with only required location fields should complete with data from geocoding', async () => {
seedFakeGeocodes('Canada', {
country: 'CA',
geoResolution: 'Country',
geometry: { latitude: 42.42, longitude: 11.11 },
name: 'Canada',
});

const minimalLocationRequest = {
...minimalRequest,
location: { countryISO2: 'CA', country: 'Canada', query: 'Canada' },
};

const expectedLocation = {
country: 'CA',
countryISO2: 'CA',
geoResolution: 'Country',
geometry: { latitude: 42.42, longitude: 11.11 },
name: 'Canada',
query: 'Canada',
};

const res = await request(app)
.post('/api/cases')
.send(minimalLocationRequest)
.expect('Content-Type', /json/)
.expect(201);

expect(res.body.location).toEqual(expectedLocation);
});
it('create with overrided geoResolution', async () => {
seedFakeGeocodes('Canada', {
country: 'CA',
geoResolution: 'Country',
geometry: { latitude: 42.42, longitude: 11.11 },
name: 'Canada',
});

const minimalLocationRequest = {
...minimalRequest,
location: {
countryISO2: 'CA',
country: 'Canada',
query: 'Canada',
geoResolution: 'Admin3',
},
};

const expectedLocation = {
country: 'CA',
countryISO2: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 42.42, longitude: 11.11 },
name: 'Canada',
query: 'Canada',
};

const res = await request(app)
.post('/api/cases')
.send(minimalLocationRequest)
.expect('Content-Type', /json/)
.expect(201);

expect(res.body.location).toEqual(expectedLocation);
});
it('create with minimal + city should complete rest with geocoding', async () => {
seedFakeGeocodes('Montreal, Canada', {
country: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 45.5019, longitude: 73.5674 },
name: 'Montreal, Canada',
});

const minimalLocationRequest = {
...minimalRequest,
location: {
countryISO2: 'CA',
country: 'Canada',
query: 'Montreal, Canada',
city: 'Montreal',
},
};

const expectedLocation = {
country: 'CA',
city: 'Montreal',
countryISO2: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 45.5019, longitude: 73.5674 },
name: 'Montreal, Canada',
query: 'Montreal, Canada',
};

const res = await request(app)
.post('/api/cases')
.send(minimalLocationRequest)
.expect('Content-Type', /json/)
.expect(201);

expect(res.body.location).toEqual(expectedLocation);
});
it('create with minimal + city + location should complete rest with geocoding', async () => {
seedFakeGeocodes('Jacques Cartier Bridge, Montreal, Canada', {
country: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 45.5218, longitude: 73.5418 },
name: 'Jacques Cartier Bridge, Montreal, Canada',
});

const minimalLocationRequest = {
...minimalRequest,
location: {
countryISO2: 'CA',
country: 'Canada',
query: 'Jacques Cartier Bridge, Montreal, Canada',
city: 'Montreal',
location: 'Jacques Cartier Bridge',
},
};

const expectedLocation = {
country: 'CA',
city: 'Montreal',
location: 'Jacques Cartier Bridge',
countryISO2: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 45.5218, longitude: 73.5418 },
name: 'Jacques Cartier Bridge, Montreal, Canada',
query: 'Jacques Cartier Bridge, Montreal, Canada',
};

const res = await request(app)
.post('/api/cases')
.send(minimalLocationRequest)
.expect('Content-Type', /json/)
.expect(201);

expect(res.body.location).toEqual(expectedLocation);
});

it('create with minimal + city + latitude + longitude should automatically set geoResolution to Point', async () => {
seedFakeGeocodes('Jacques Cartier Bridge, Montreal, Canada', {
country: 'CA',
geoResolution: 'Admin3',
geometry: { latitude: 45.5019, longitude: 73.5674 },
name: 'Jacques Cartier Bridge, Montreal, Canada',
});

const minimalLocationRequest = {
...minimalRequest,
location: {
countryISO2: 'CA',
country: 'Canada',
query: 'Jacques Cartier Bridge, Montreal, Canada',
city: 'Montreal',
geometry: { latitude: 45.5018, longitude: 73.5673 },
},
};

const expectedLocation = {
country: 'CA',
city: 'Montreal',
countryISO2: 'CA',
geoResolution: 'Point',
geometry: { latitude: 45.5018, longitude: 73.5673 },
name: 'Jacques Cartier Bridge, Montreal, Canada',
query: 'Jacques Cartier Bridge, Montreal, Canada',
};

const res = await request(app)
.post('/api/cases')
.send(minimalLocationRequest)
.expect('Content-Type', /json/)
.expect(201);

expect(res.body.location).toEqual(expectedLocation);
});
it('create with valid input should bucket the age range', async () => {
seedFakeGeocodes('Canada', {
country: 'CA',
Expand Down Expand Up @@ -1547,4 +1724,4 @@ describe('DELETE', () => {
expect(await Day0Case.collection.countDocuments()).toEqual(3);
expect(await CaseRevision.collection.countDocuments()).toEqual(0);
});
});
});
3 changes: 2 additions & 1 deletion data-serving/data-service/test/model/data/case.full.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"countryISO2": "FR",
"query": "France",
"name": "France",
"geoResolution": "Country"
"geoResolution": "Country",
"geometry": { "latitude": 42.42, "longitude": 11.11 }
},
"events": {
"dateEntry": "2020-01-10",
Expand Down
6 changes: 5 additions & 1 deletion data-serving/data-service/test/model/data/location.full.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@
"location": "Brooklyn",
"geoResolution": "Point",
"name": "Kings County Hospital Center",
"query": "Brooklyn, New York"
"query": "Brooklyn, New York",
"geometry": {
"latitude": 40.6809611,
"longitude": -73.9740028
}
}
16 changes: 16 additions & 0 deletions data-serving/data-service/test/util/case.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ describe('Case', () => {
expect(denormalizedCase['location.countryISO2']).toEqual('');
expect(denormalizedCase['location.location']).toEqual('');
expect(denormalizedCase['location.city']).toEqual('');
expect(denormalizedCase['location.geoResolution']).toEqual('');
expect(denormalizedCase['location.geometry.latitude']).toEqual('');
expect(denormalizedCase['location.geometry.longitude']).toEqual('');

expect(denormalizedCase['pathogen']).toEqual('');
expect(denormalizedCase['caseStatus']).toEqual('');
Expand Down Expand Up @@ -457,6 +460,10 @@ describe('Case', () => {
name: 'Tbilisi',
city: 'Tibilisi',
geoResolution: 'Point',
geometry: {
latitude: 41.7151,
longitude: 44.8271,
},
} as LocationDocument;

const caseDoc = {
Expand All @@ -483,6 +490,15 @@ describe('Case', () => {
);
expect(denormalizedCase['location.countryISO2']).toEqual('GE');
expect(denormalizedCase['location.city']).toEqual(locationDoc.city);
expect(denormalizedCase['location.geoResolution']).toEqual(
locationDoc.geoResolution,
);
expect(denormalizedCase['location.geometry.latitude']).toEqual(
locationDoc.geometry.latitude,
);
expect(denormalizedCase['location.geometry.longitude']).toEqual(
locationDoc.geometry.longitude,
);
});
it('denormalizes preexisting conditions fields', async () => {
const conditionsDoc = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ describe('Curator', function () {
.clear()
.type('Germany');
cy.contains('Germany').click();
cy.get('div[data-testid="location.geoResolution"]').click()
cy.get('li[data-value="Country"').click();

// EVENTS
cy.get('input[name="events.dateEntry"]').type('2020-01-01');
Expand Down
9 changes: 9 additions & 0 deletions verification/curator-service/ui/src/api/models/Day0Case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,21 @@ export interface GeocodeLocation {
limitToResolution?: string;
}

export interface Geometry {
longitude?: number;
latitude?: number;
}

export interface Location {
geoResolution?: string;
country: string;
countryISO2: string;
location?: string;
city?: string;
// this variable is needed in the API in order to geocode properly
query?: string;
name?: string;
geometry?: Geometry;
}

export interface Events {
Expand Down Expand Up @@ -217,12 +224,14 @@ export interface Day0CaseFormValues {
healthcareWorker?: YesNo | '';
};
location: {
geoResolution?: string;
country: string;
countryISO2: string;
location?: string;
city?: string;
geocodeLocation?: GeocodeLocation;
query?: string;
geometry?: Geometry;
};
events: Events;
symptoms?: string[];
Expand Down
8 changes: 8 additions & 0 deletions verification/curator-service/ui/src/components/CaseForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,15 @@ const initialValuesFromCase = (
healthcareWorker: '',
},
location: {
geoResolution: undefined,
country: '',
countryISO2: '',
location: '',
city: '',
geometry: {
latitude: undefined,
longitude: undefined,
},
},
events: {
dateEntry: null,
Expand Down Expand Up @@ -314,6 +319,9 @@ export default function CaseForm(props: Props): JSX.Element {
const diseaseName = useAppSelector(selectDiseaseName);

const submitCase = async (values: Day0CaseFormValues): Promise<void> => {
if (values.location.geoResolution === '') {
values.location.geoResolution = undefined;
}
if (values.caseReference && values.caseReference.sourceId === '') {
try {
const newCaseReference = await submitSource({
Expand Down
Loading