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

Delete Trips #82

Merged
merged 16 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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';
zghera marked this conversation as resolved.
Show resolved Hide resolved

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) {
zghera marked this conversation as resolved.
Show resolved Hide resolved
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);
anan-ya-y marked this conversation as resolved.
Show resolved Hide resolved
})
.catch(error => {
console.error("Error deleting activities subcollection: ", error);
zghera marked this conversation as resolved.
Show resolved Hide resolved
});

db.collection(DB.COLLECTION_TRIPS)
.doc(props.tripId)
.delete()
.then(() => {
console.log("Document successfully deleted with id: ", props.tripId);
zghera marked this conversation as resolved.
Show resolved Hide resolved
}).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