Skip to content

Commit

Permalink
Merge pull request #82 from googleinterns/delete-trip
Browse files Browse the repository at this point in the history
Delete Trips
  • Loading branch information
zghera authored Jul 28, 2020
2 parents f28e5c3 + d10c6ee commit 0b04c09
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 60 deletions.
4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"bootstrap": "^4.5.0",
"firebase": "^7.15.5",
"firebase": "^7.17.1",
"history": "^5.0.0",
"moment-timezone": "^0.5.31",
"history": "^5.0.0"
"react": "^16.13.1",
"react-bootstrap": "1.0.1",
"react-dom": "^16.13.1",
Expand Down
58 changes: 29 additions & 29 deletions frontend/src/components/ViewActivities/editActivity.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Button, Col, Form, Row } from 'react-bootstrap';
import { Button, Form } from 'react-bootstrap';
import { getField, writeActivity } from './activityfns.js';
import * as DB from '../../constants/database.js'
import { countryList } from '../../constants/countries.js';
Expand All @@ -11,8 +11,8 @@ const db = app.firestore();

/**
* React component for the form that's used when the user is editing an activity.
*
* @property {Object} props ReactJS props.
*
* @property {Object} props ReactJS props.
* @property {ActivityInfo} props.activity The activity to display.
* @property {function} props.submitFunction The function to run upon submission.
*/
Expand All @@ -29,7 +29,7 @@ class EditActivity extends React.Component {
this.deleteActivity = this.deleteActivity.bind(this);
this.timezoneDropdown = this.timezoneDropdown.bind(this);

// References.
// References.
this.editTitleRef = React.createRef();
this.editStartDateRef = React.createRef();
this.editEndDateRef = React.createRef();
Expand All @@ -41,7 +41,7 @@ class EditActivity extends React.Component {
this.startTz = React.createRef();
this.endTz = React.createRef();
}

/**
* Edit an activity in the database upon form submission.
* TODO: Update times as well! This only does the text field forms (#64).
Expand Down Expand Up @@ -72,19 +72,19 @@ class EditActivity extends React.Component {
this.props.submitFunction();
}

// "Flip switch" on timezone dropdown so the dropdown's contents update to the
// selected country's timezones.
// "Flip switch" on timezone dropdown so the dropdown's contents update to the
// selected country's timezones.
startTimeTzUpdate = () => { this.setState({startTz : !this.state.startTz})};
endTimeTzUpdate = () => { this.setState({endTz : !this.state.endTz})};

/**
* Returns a dropdown of all the timezones.
* The dropdown's values change based on the corrresponding country dropdown to
* reduce scrolling and ensure that the location corresponds to the time zone.
*
*
* Tests done manually using UI.
*
* @param {string} st Either 'start' or 'end' depending on whether the
*
* @param {string} st Either 'start' or 'end' depending on whether the
* timezone is for the start or end timezone.
* @return {HTML} HTML dropdown item.
*/
Expand All @@ -110,13 +110,13 @@ class EditActivity extends React.Component {
)
}
/**
* Create a dropdown of all the countries.
* This dropdown is linked to the corresponding timezone dropdown,
* so when the country changes here, the values in the timezone dropdown
* change as well.
*
* Create a dropdown of all the countries.
* This dropdown is linked to the corresponding timezone dropdown,
* so when the country changes here, the values in the timezone dropdown
* change as well.
*
* @param {ref} ref The reference to attach to the dropdown.
* @param {ref} tzref The corresponding time zone reference field.
* @param {ref} tzref The corresponding time zone reference field.
* @return {HTML} HTML dropdown of all the countries with timezones.
*/
countriesDropdown(ref, tzref) {
Expand All @@ -133,8 +133,8 @@ class EditActivity extends React.Component {
}

/**
* Delete this activity.
*
* Delete this activity.
*
* @return {boolean} true if the activity was successfully deleted.
*/
async deleteActivity() {
Expand All @@ -156,7 +156,7 @@ class EditActivity extends React.Component {
{formElements.textElementFormGroup(
'formActivityTitle', // controlId
'Title:', // formLabel
activity[DB.ACTIVITIES_TITLE],// placeHolder
activity[DB.ACTIVITIES_TITLE],// placeHolder
this.editTitleRef // ref
)}
{formElements.locationElementFormGroup(
Expand All @@ -173,28 +173,28 @@ class EditActivity extends React.Component {
'formActivityStartTime', // controlId
'From:', // formLabel
this.editStartDateRef, // dateRef
null, // dateDefault
this.editStartTimeRef, // timeRef,
null, // timeDefault,
this.timezoneDropdown('start') // tzpicker
null, // dateDefault
this.editStartTimeRef, // timeRef,
null, // timeDefault,
this.timezoneDropdown('start') // tzpicker
)}
{formElements.dateTimeTzFormGroup(
'formActivityEndTime', // controlId
'To:', // formLabel
this.editEndDateRef, // dateRef
null, // dateDefault
this.editEndTimeRef, // timeRef,
null, //timeDefault,
this.timezoneDropdown('end') // tzpicker
null, // dateDefault
this.editEndTimeRef, // timeRef,
null, //timeDefault,
this.timezoneDropdown('end') // tzpicker
)}
{formElements.textElementFormGroup(
'formActivityDescription', // controlId
'Description:', // formLabel
getField(activity, DB.ACTIVITIES_DESCRIPTION, 'Add some details!'), // placeHolder
getField(activity, DB.ACTIVITIES_DESCRIPTION, 'Add some details!'), // placeHolder
this.editDescriptionRef // ref
)}
<Button type='submit' className='float-right'>Done!</Button>
<Button type='button' onClick={this.deleteActivity}>
<Button type='button' onClick={this.deleteActivity}>
Delete
</Button>
</Form>
Expand Down
48 changes: 24 additions & 24 deletions frontend/src/components/ViewActivities/editActivityFormElements.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
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;
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.
*/
Expand All @@ -24,19 +24,19 @@ export function textElementFormGroup(controlId, formLabel, placeHolder, ref) {
<Col sm={TITLEWIDTH}><Form.Label>{formLabel}</Form.Label></Col>
<Col>
<Form.Control type='text'
placeholder={placeHolder}
placeholder={placeHolder}
ref={ref}/>
</Col>
</Form.Group>
);
}

/**
* 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) {
Expand All @@ -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 (
<Form.Group as={Row} controlId={controlId}>
Expand All @@ -76,4 +76,4 @@ export function dateTimeTzFormGroup(controlId, formLabel, dateRef,
<Col sm={TZPICKERWIDTH}>{tzpicker}</Col>
</Form.Group>
);
}
}
119 changes: 119 additions & 0 deletions frontend/src/components/ViewTrips/delete-trip-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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();
const LIMIT_QUERY_DOCS_RETRIEVED = 5;

/**
* 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.
*
* This was taken from the delete collection snippets in the documentation
* at https://firebase.google.com/docs/firestore/manage-data/delete-data.
*
* @param {firebase.firestore.Firestore} db Firestore database instance.
* @param {firebase.firestore.Query} query Query containing documents from
* the activities subcollection of a trip documents.
* @param {Function} resolve Resolve function that returns a void Promise.
*/
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 trip's subcollection of activities corresponding to the
* `tripId` prop.
*
* This was adapted from the delete collection snippets in the documentation
* at https://firebase.google.com/docs/firestore/manage-data/delete-data.
*
* TODO(Issue #81): Consider deleting data with callable 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(LIMIT_QUERY_DOCS_RETRIEVED);

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() {
if (window.confirm('Are you sure you want to delete this trip? This' +
' action cannot be undone!')) {
await deleteTripActivities()
.then(() => {
console.log("Activity subcollection successfully deleted for trip" +
" with id: ", props.tripId);
})
.catch(error => {
console.error("Error deleting activities subcollection: ", error);
});

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 (
<Button
type='button'
variant='primary'
onClick={deleteTrip} >
Delete Trip
</Button>
);
}

export default DeleteTripsButton;
1 change: 1 addition & 0 deletions frontend/src/components/ViewTrips/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class ViewTrips extends React.Component {
</div>
<TripsContainer
handleEditTrip={this.showEditTripModal}
refreshTripsContainer={this.refreshTripsContainer}
key={this.state.refreshTripsContainer}
/>
</div>
Expand Down
Loading

0 comments on commit 0b04c09

Please sign in to comment.