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

View Trips Page Real time Updates #96

Merged
merged 20 commits into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b068a0b
Remove state, func, and all associated references.
zghera Jul 22, 2020
9f0fe8a
Complete initial integration of onSnapshot listener for real-time upd…
zghera Jul 22, 2020
e1ca5d4
Add documentation to componentDidMount and change annotation to overr…
zghera Jul 22, 2020
5c6d205
Merge branch 'partial-integrate-auth' into real-time-updates
zghera Jul 22, 2020
830b75f
Delete extra line.
zghera Jul 23, 2020
dbd5dc6
Add todo to move getErrElement into component.
zghera Jul 23, 2020
fb7d32f
Merge branch 'partial-integrate-auth' into real-time-updates
zghera Jul 24, 2020
7490d46
Merge branch 'master' into real-time-updates
zghera Jul 29, 2020
294f5bc
Add correct issue number for todo in getErrorElement.
zghera Jul 29, 2020
6bbde68
Rename trips state to tripsContainer and tripsContainer local var to …
zghera Jul 29, 2020
e2afe02
Make errorElement const.
zghera Jul 29, 2020
efbe873
Merge branch 'master' into real-time-updates
zghera Jul 29, 2020
e36fb9f
Fix syntax that broke tests.
zghera Jul 29, 2020
861d273
Revert "Fix syntax that broke tests."
zghera Jul 29, 2020
ec74200
Merge branch 'fix-trips-timezones' into real-time-updates
zghera Jul 30, 2020
985ca72
Change getErrorElement back to synchronous.
zghera Jul 30, 2020
79db042
Merge branch 'fix-trips-timezones' into real-time-updates
zghera Jul 30, 2020
8c41d72
Merge branch 'fix-trips-timezones' into real-time-updates
zghera Aug 3, 2020
2764c83
Merge pull request #119 from googleinterns/fix-trips-timezones
zghera Aug 5, 2020
d54c962
Merge branch 'master' into real-time-updates
zghera Aug 5, 2020
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
8 changes: 1 addition & 7 deletions frontend/src/components/ViewTrips/delete-trip-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ const LIMIT_QUERY_DOCS_RETRIEVED = 5;
*
* @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) => {
/**
Expand Down Expand Up @@ -78,8 +76,6 @@ const DeleteTripsButton = (props) => {
/**
* 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' +
Expand All @@ -101,9 +97,7 @@ const DeleteTripsButton = (props) => {
}).catch(error => {
console.error("Error removing document: ", error);
});

props.refreshTripsContainer();
}
}
}

return (
Expand Down
18 changes: 0 additions & 18 deletions frontend/src/components/ViewTrips/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,12 @@ class ViewTrips extends React.Component {
constructor() {
super();
this.state = { showModal: false,
refreshTripsContainer: false,
refreshSaveTripModal: false,
tripId: null,
defaultFormObj: null,
};
}

/**
* Handler that flips `refreshTripsContainer` property which causes that
* TripsContainer component to be reloaded.
*
* This allows a trip creator's view trips page to be updated in real time.
*
* In the future, the use of refreshTripContainer and the key prop on Trips
* container should be removed with the addition of real time listening with
* onShapshot (Issue #62).
*/
refreshTripsContainer = () => {
this.setState({ refreshTripsContainer: !this.state.refreshTripsContainer });
}

/**
* Handler that flips `refreshSaveTripModal` property which causes that
* save/edit trip component to be reloaded.
Expand Down Expand Up @@ -97,7 +82,6 @@ class ViewTrips extends React.Component {
<SaveTripModal
show={this.state.showModal}
handleClose={this.hideSaveTripModal}
refreshTripsContainer={this.refreshTripsContainer}
tripId={this.state.tripId}
defaultFormObj={this.state.defaultFormObj}
key={this.state.refreshSaveTripModal}
Expand All @@ -109,8 +93,6 @@ class ViewTrips extends React.Component {
</div>
<TripsContainer
handleEditTrip={this.showEditTripModal}
refreshTripsContainer={this.refreshTripsContainer}
key={this.state.refreshTripsContainer}
/>
</div>
);
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/components/ViewTrips/save-trip-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const db = app.firestore();
* @param {Object} props These are the props for this component:
* - show: Boolean that determines if the add trips modal should be displayed.
* - handleClose: Event handler responsible for closing the add trips modal.
* - refreshTripsContainer: Handler that refreshes the TripsContainer
* component upon trip creation (Remove when fix Issue #62).
* - tripId: For adding a new trip, this will be null. For editting an existing
* trip, this will the document id associated with the trip.
* - defaultFormObj: Object containing the placeholder/default values for the
Expand Down Expand Up @@ -128,18 +126,15 @@ class SaveTripModal extends React.Component {
} else {
this.updateExistingTrip(this.props.tripId, tripData);
}

}

/**
* Handles submission of the form which includes:
* - Creation of the trip.
* - Refreshing the trips container.
* - Closing the modal.
*/
handleSubmitForm = () => {
this.saveTrip();
this.props.refreshTripsContainer();
this.props.handleClose();
}

Expand Down
7 changes: 1 addition & 6 deletions frontend/src/components/ViewTrips/trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ 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
*/
Expand Down Expand Up @@ -83,10 +81,7 @@ const Trip = (props) => {
<p>{description}</p>
<p>{collaboratorEmailsStr}</p>

<DeleteTripButton
tripId={props.tripId}
refreshTripsContainer={props.refreshTripsContainer}
/>
<DeleteTripButton tripId={props.tripId} />
<Button
type='button'
onClick={() => props.handleEditTrip(props.tripId, formattedTripData)}
Expand Down
107 changes: 43 additions & 64 deletions frontend/src/components/ViewTrips/trips-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,62 +8,25 @@ import Trip from './trip.js';

const db = app.firestore();

/**
* Returns a promise of a query object containg the array of Trip Documents
* corresponding to the trips that the current user is a collaborator on.
*
* @param {firebase.firestore.Firestore} db The Firestore database instance.
* @return {Promise<!firebase.firestore.QuerySnapshot>} Promise object
* containing the query results with zero or more Trip documents.
*/
function queryUserTrips(db) {
const curUserUid = getCurUserUid();
return db.collection(DB.COLLECTION_TRIPS)
.where(DB.TRIPS_COLLABORATORS, 'array-contains', curUserUid)
.orderBy(DB.TRIPS_CREATION_TIME, 'desc')
.get();
}

/**
* Grabs Trips query result from `queryUserTrips()` and returns an array of
* `<Trip>` elements as defined in `trip.js`.
*
* @param {Promise<!firebase.firestore.QuerySnapshot>} querySnapshot Promise
* object containing the query results with zero or more Trip documents.
* @param {EventHandler} handleEditTrip Displays the edit trip modal.
* @param {EventHandler} refreshTripsContainer Refreshed the TripsContainer
* component (Remove when fix Issue #62).
* @return {Promise<!Array<Trip>>} Promise object containing an array
* of Trip React/HTML elements corresponding to the Trip documents included
* in `querySnapshot`.
*/
function serveTrips(querySnapshot, handleEditTrip, refreshTripsContainer) {
return new Promise(function(resolve) {
const tripsContainer = querySnapshot.docs.map(doc =>
( <Trip
tripData={doc.data()}
tripId={doc.id}
handleEditTrip={handleEditTrip}
refreshTripsContainer={refreshTripsContainer}
key={doc.id}
/>
)
);
resolve(tripsContainer);
});
}

/**
* Returns a `<div>` element with the specified error message.
*
* TODO(Issue #98): Turn this func into component and add to Errors directory.
*
* @param {string} error Error message in `componentDidMount` catch statement.
* @return {Promise<HTMLDivElement>} Promise object containing a `<div>` element
* with the error message `error` inside.
*/
function getErrorElement(error) {
return new Promise(function(resolve) {
console.log(`Error in Trips Container: ${error}`);
resolve(( <div><p>Error: Unable to load your trips.</p></div> ));
resolve(
<div>
<p>Oops, it looks like we were unable to load your trips.
Please wait a few minutes and try again.
</p>
</div>
zghera marked this conversation as resolved.
Show resolved Hide resolved
);
});
}

Expand All @@ -73,38 +36,54 @@ function getErrorElement(error) {
*
* @param {Object} props These are the props for this component:
* - 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 TripsContainer instance is
* created whenever this key is updated (Remove when fix Issue #62).
* @extends React.Component
*/
class TripsContainer extends React.Component {
/** @inheritdoc */
constructor(props) {
super(props);
this.state = {trips: []};
this.state = {tripsContainer: []};
}

/** @inheritdoc */
/**
* When the TripsContainer mounts, a listener is attached to the QuerySnapshot
* event that grabs all trip documents where the current user uid is contained
* in the collaborator uid array (collaborators field). This allows real-time
* updates for all collaborators on a trip whenever a trip is updated (add,
* edit, or delete).
*
* In the case where there is an error, an error component is returned in
* place of the array of trips.
*
* @override
*/
async componentDidMount() {
try {
const querySnapshot = await queryUserTrips(db);
let tripsContainer = await serveTrips(querySnapshot,
this.props.handleEditTrip,
this.props.refreshTripsContainer);
this.setState({ trips: tripsContainer });
}
catch (error) {
let errorElement = await getErrorElement(error);
this.setState({ trips: errorElement });
}
const curUserUid = getCurUserUid();
db.collection(DB.COLLECTION_TRIPS)
.where(DB.TRIPS_COLLABORATORS, 'array-contains', curUserUid)
.orderBy(DB.TRIPS_CREATION_TIME, 'desc')
.onSnapshot(querySnapshot => {
const tripsArr = querySnapshot.docs.map(doc =>
( <Trip
tripData={doc.data()}
tripId={doc.id}
handleEditTrip={this.props.handleEditTrip}
key={doc.id}
/>
)
);

this.setState({ tripsContainer: tripsArr });
}, (error) => {
const errorElement = getErrorElement(error);
this.setState({ tripsContainer: errorElement });
});
zghera marked this conversation as resolved.
Show resolved Hide resolved
}

/** @inheritdoc */
render() {
return (
<div>{this.state.trips}</div>
<div>{this.state.tripsContainer}</div>
);
}
}
Expand Down