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

[Edit survey] Write using new proto-based Firestore representation #1889

Merged
merged 12 commits into from
Jul 7, 2024
7 changes: 7 additions & 0 deletions firestore/firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -138,5 +138,12 @@
allow create: if canManageSurvey(getSurvey(surveyId), getEmail()) || isCreator(request.resource, getEmail());
allow write: if canManageSurvey(getSurvey(surveyId), getEmail()) || isCreator(resource, getEmail());
}

// Apply passlist and survey-level ACLs to job documents.
match /surveys/{surveyId}/jobs/{jobId} {
allow read: if canViewSurvey(getSurvey(surveyId), getEmail());
allow create: if canManageSurvey(getSurvey(surveyId), getEmail()) || isCreator(request.resource, getEmail());
allow write: if canManageSurvey(getSurvey(surveyId), getEmail()) || isCreator(resource, getEmail());
}
}
}
41 changes: 41 additions & 0 deletions web/src/app/converters/proto-model-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import {toDocumentData} from '@ground/lib';
import {GroundProtos} from '@ground/proto';
import {Map} from 'immutable';

import {Job} from 'app/models/job.model';
import {Role} from 'app/models/role.model';
import {Task, TaskType} from 'app/models/task/task.model';

const Pb = GroundProtos.google.ground.v1beta1;

Expand Down Expand Up @@ -74,3 +76,42 @@ export function partialSurveyToProto(
})
);
}

/**
* Creates a proto rapresentation of a Job.
*/
export function jobToProto(job: Job): DocumentData {
return toDocumentData(
new Pb.Job({
id: job.id,
index: job.index,
name: job.name,
style: new Pb.Style({color: job.color}),
tasks: job.tasks
?.map(task => {
const pbTask = new Pb.Task({
id: task.id,
index: task.index,
prompt: task.label,
required: task.required,
takePhoto: new Pb.Task.TakePhoto({}),
level: task.addLoiTask
? Pb.Task.DataCollectionLevel.LOI_DATA
: Pb.Task.DataCollectionLevel.LOI_METADATA,
});

switch (task.type) {
case TaskType.CAPTURE_LOCATION:
pbTask.captureLocation = new Pb.Task.CaptureLocation({
minAccuracyMeters: null,
});
break;
}

return pbTask;
})
.toList()
.toArray(),
})
);
}
58 changes: 39 additions & 19 deletions web/src/app/services/data-store/data-store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {map} from 'rxjs/operators';
import {FirebaseDataConverter} from 'app/converters/firebase-data-converter';
import {LoiDataConverter} from 'app/converters/loi-converter/loi-data-converter';
import {
jobToProto,
newSurveyToProto,
partialSurveyToProto,
} from 'app/converters/proto-model-converter';
Expand Down Expand Up @@ -152,26 +153,35 @@ export class DataStoreService {
* submissions that are related to the jobs to be deleted.
*/

updateSurvey(survey: Survey, jobIdsToDelete: List<string>): Promise<void> {
const {title, description, id} = survey;
async updateSurvey(
survey: Survey,
jobIdsToDelete: List<string>
): Promise<void> {
const {title, description, id: surveyId, jobs} = survey;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const surveyJS: {[key: string]: any} = {
title: title,
description: description,
};
survey.jobs.forEach(
job => (surveyJS[`jobs.${job.id}`] = FirebaseDataConverter.jobToJS(job))
);
jobIdsToDelete.forEach(jobId => {
this.deleteAllLocationsOfInterestInJob(id, jobId);
this.deleteAllSubmissionsInJob(id, jobId);
surveyJS[`jobs.${jobId}`] = deleteField();
jobs.forEach(job => {
const {id: jobId} = job;
if (jobIdsToDelete.includes(jobId)) {
this.deleteAllLocationsOfInterestInJob(surveyId, jobId);
this.deleteAllSubmissionsInJob(surveyId, jobId);
surveyJS[`jobs.${jobId}`] = deleteField();
} else {
surveyJS[`jobs.${jobId}`] = FirebaseDataConverter.jobToJS(job);
}
});

return this.db.firestore
await this.db.firestore
.collection(SURVEYS_COLLECTION_NAME)
.doc(survey.id)
.update(surveyJS);
.doc(surveyId)
.update({
...surveyJS,
...partialSurveyToProto(title, description),
});

await Promise.all(jobs.map(job => this.updateJob(surveyId, job)));
}

/**
Expand Down Expand Up @@ -212,13 +222,23 @@ export class DataStoreService {
);
}

addOrUpdateJob(surveyId: string, job: Job): Promise<void> {
addOrUpdateJob(surveyId: string, job: Job): Promise<[void, void]> {
return Promise.all([
this.db
.collection(SURVEYS_COLLECTION_NAME)
.doc(surveyId)
.update({
[`jobs.${job.id}`]: FirebaseDataConverter.jobToJS(job),
}),
this.updateJob(surveyId, job),
]);
}

updateJob(surveyId: string, job: Job): Promise<void> {
return this.db
.collection(SURVEYS_COLLECTION_NAME)
.doc(surveyId)
.update({
[`jobs.${job.id}`]: FirebaseDataConverter.jobToJS(job),
});
.collection(`${SURVEYS_COLLECTION_NAME}/${surveyId}/jobs`)
.doc(job.id)
.set(jobToProto(job));
}

async deleteSurvey(survey: Survey) {
Expand Down
2 changes: 1 addition & 1 deletion web/src/app/services/job/job.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class JobService {
/**
* Adds/Updates the job of a survey with a given job value.
*/
async addOrUpdateJob(surveyId: string, job: Job): Promise<void> {
async addOrUpdateJob(surveyId: string, job: Job): Promise<[void, void]> {
if (job.index === -1) {
const index = await this.getJobCount();
job = job.copyWith({index});
Expand Down
Loading