From 51a2fc9a1a1a8bf5fb32cbcffef51141e281cc05 Mon Sep 17 00:00:00 2001 From: Zach Ghera Date: Sat, 18 Jul 2020 17:32:36 -0400 Subject: [PATCH 01/10] Implement deleting a trip and its corresponding sub-collection of activities. --- .../ViewTrips/delete-trip-button.js | 96 +++++++++++++++++++ frontend/src/components/ViewTrips/index.js | 1 + frontend/src/components/ViewTrips/trip.js | 7 ++ .../components/ViewTrips/trips-container.js | 19 ++-- 4 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/ViewTrips/delete-trip-button.js diff --git a/frontend/src/components/ViewTrips/delete-trip-button.js b/frontend/src/components/ViewTrips/delete-trip-button.js new file mode 100644 index 00000000..84b30be2 --- /dev/null +++ b/frontend/src/components/ViewTrips/delete-trip-button.js @@ -0,0 +1,96 @@ +import React from 'react'; + +import app from '../Firebase/'; +import Button from 'react-bootstrap/Button'; + +import * as DB from '../../constants/database.js'; + +const db = app.firestore(); + +/** + * Component used to delete a Trip. + * + * + * @param {Object} props These are the props for this component: + * - tripId: Document ID for the current Trip document. + * - refreshTripsContainer: Handler that refreshes the TripsContainer + * component upon trip creation (Remove when fix Issue #62). + */ +const DeleteTripsButton = (props) => { + /** + * Deletes documents in query with a batch delete. + */ + async function deleteQueryBatch(db, query, resolve) { + const snapshot = await query.get(); + + const batchSize = snapshot.size; + if (batchSize === 0) { + // When there are no documents left, we are done + resolve(); + return; + } + + // Delete documents in a batch + const batch = db.batch(); + snapshot.docs.forEach((doc) => { + batch.delete(doc.ref); + }); + await batch.commit(); + + // Recurse on the next process tick, to avoid + // exploding the stack. + process.nextTick(() => { + deleteQueryBatch(db, query, resolve); + }); + } + + /** + * Deletes a trips subcollection of activities corrsponding to the + * `tripId` prop. + * + * TODO(Issue #81): Consider deleting data with callabable cloud function + * https://firebase.google.com/docs/firestore/solutions/delete-collections + */ + async function deleteTripActivities() { + const query = db.collection(DB.COLLECTION_TRIPS) + .doc(props.tripId) + .collection(DB.COLLECTION_ACTIVITIES) + .orderBy(DB.ACTIVITIES_TITLE) + .limit(3); + + return new Promise((resolve, reject) => { + deleteQueryBatch(db, query, resolve).catch(reject); + }); + } + + /** + * Deletes a trip and its subcollection of activities corrsponding to the + * `tripId` prop and then refreshes the TripsContainer component. + * TODO(Issue #62): Remove refreshTripsContainer. + */ + async function deleteTrip() { + await deleteTripActivities(); + + db.collection(DB.COLLECTION_TRIPS) + .doc(props.tripId) + .delete() + .then(() => { + console.log("Document successfully deleted with id: ", props.tripId); + }).catch(error => { + console.error("Error removing document: ", error); + }); + + props.refreshTripsContainer(); + } + + return ( + + ); +} + +export default DeleteTripsButton; diff --git a/frontend/src/components/ViewTrips/index.js b/frontend/src/components/ViewTrips/index.js index 227bb0c8..249496a1 100644 --- a/frontend/src/components/ViewTrips/index.js +++ b/frontend/src/components/ViewTrips/index.js @@ -123,6 +123,7 @@ class ViewTrips extends React.Component { diff --git a/frontend/src/components/ViewTrips/trip.js b/frontend/src/components/ViewTrips/trip.js index 09fbe117..d121a61f 100644 --- a/frontend/src/components/ViewTrips/trip.js +++ b/frontend/src/components/ViewTrips/trip.js @@ -4,6 +4,7 @@ import Button from 'react-bootstrap/Button'; import { timestampToISOString } from '../Utils/time.js'; import { getUserEmailFromUid } from '../Utils/temp-auth-utils.js'; +import DeleteTripButton from './delete-trip-button.js'; import ViewActivitiesButton from './view-activities-button.js'; /** @@ -51,6 +52,8 @@ export function getCollaboratorEmails(collaboratorUidArr) { * - tripData: Object holding a Trip document fields and corresponding values. * - tripId: Document ID for the current Trip document. * - handleEditTrip: Handler that displays the edit trip modal. + * - refreshTripsContainer: Handler that refreshes the TripsContainer + * component upon trip creation (Remove when fix Issue #62). * - key: Special React attribute that ensures a new Trip instance is * created whenever this key is updated */ @@ -78,6 +81,10 @@ const Trip = (props) => {

{description}

{collaboratorEmailsStr}

+ - diff --git a/frontend/src/components/ViewActivities/editActivityFormElements.js b/frontend/src/components/ViewActivities/editActivityFormElements.js index f53ccaba..8bde0ef6 100644 --- a/frontend/src/components/ViewActivities/editActivityFormElements.js +++ b/frontend/src/components/ViewActivities/editActivityFormElements.js @@ -1,8 +1,8 @@ import React from 'react'; -import { Button, Col, Form, Row } from 'react-bootstrap'; +import { Col, Form, Row } from 'react-bootstrap'; -// This file waas written after #87 was created. -// As a result, some fields and functions may not be used yet. +// This file waas written after #87 was created. +// As a result, some fields and functions may not be used yet. const TITLEWIDTH = 3; const COUNTRYWIDTH = 6; const DATEWIDTH = 4; @@ -10,11 +10,11 @@ const TIMEWIDTH = 2; const TZPICKERWIDTH = 3; /** - * Create a Text element Form Group for the editActivity form. - * + * Create a Text element Form Group for the editActivity form. + * * @param {string} controlId FormGroup's control ID. - * @param {string} formLabel The label of the field for this FormGroup. - * @param {string} placeHolder The input's placeholder. + * @param {string} formLabel The label of the field for this FormGroup. + * @param {string} placeHolder The input's placeholder. * @param {ref} ref The input's reference. * @returns {HTML} A text element form group. */ @@ -24,7 +24,7 @@ export function textElementFormGroup(controlId, formLabel, placeHolder, ref) { {formLabel} @@ -32,11 +32,11 @@ export function textElementFormGroup(controlId, formLabel, placeHolder, ref) { } /** - * Create a Location Dropdown element Form Group for the editActivity form. - * + * Create a Location Dropdown element Form Group for the editActivity form. + * * @param {string} controlId FormGroup's control ID. - * @param {string} formLabel The label of the field for this FormGroup. - * @param {string} dropdown The dropdown. + * @param {string} formLabel The label of the field for this FormGroup. + * @param {string} dropdown The dropdown. * @returns {HTML} a location dropdown form group. */ export function locationElementFormGroup(controlId, formLabel, dropdown) { @@ -49,19 +49,19 @@ export function locationElementFormGroup(controlId, formLabel, dropdown) { } /** - * Create a Form Group for inserting date, time, and timezone for + * Create a Form Group for inserting date, time, and timezone for * the editActivity form.. - * - * @param {string} controlId FormGroup's control ID. - * @param {string} formLabel Label of the field for this FormGroup. - * @param {ref} dateRef Date's reference. - * @param {string} dateDefault Default date. - * @param {ref} timeRef Time's reference. - * @param {ref} timeDefault Default time. - * @param {HTML} tzpicker Timezone picker dropdown. - * @returns {HTML} A FormGroup for date, time, and timezone. + * + * @param {string} controlId FormGroup's control ID. + * @param {string} formLabel Label of the field for this FormGroup. + * @param {ref} dateRef Date's reference. + * @param {string} dateDefault Default date. + * @param {ref} timeRef Time's reference. + * @param {ref} timeDefault Default time. + * @param {HTML} tzpicker Timezone picker dropdown. + * @returns {HTML} A FormGroup for date, time, and timezone. */ -export function dateTimeTzFormGroup(controlId, formLabel, dateRef, +export function dateTimeTzFormGroup(controlId, formLabel, dateRef, dateDefault, timeRef, timeDefault, tzpicker) { return ( @@ -76,4 +76,4 @@ export function dateTimeTzFormGroup(controlId, formLabel, dateRef, {tzpicker} ); -} \ No newline at end of file +}