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

Fault tolerance #413

Merged
merged 43 commits into from
May 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
45a1ab3
move global Vue reference to NetworkService
dmnisson May 9, 2020
ceeaa61
fault-tolerant session starting using promise-retry
dmnisson May 9, 2020
905b8dc
update package.json and package-lock.json
dmnisson May 9, 2020
ba85275
fault tolerance in SessionService
dmnisson May 9, 2020
3f93d3e
monitor connection status in Vuex store
dmnisson May 9, 2020
179ae5f
add new Vuex state for connection status
dmnisson May 9, 2020
b3724f6
update connection status when "session-change" is received
dmnisson May 9, 2020
533c34a
modal dialog for when connection issues cause trouble starting a session
dmnisson May 9, 2020
56a6018
Merge branch 'master' into show-connection-warning
dmnisson May 9, 2020
0442b54
fix modal
dmnisson May 9, 2020
b6eef43
generate new HTTP promise for each attempt
dmnisson May 9, 2020
8ead1e1
increase max timeout
dmnisson May 9, 2020
d7e34f4
don't retry after user aborts
dmnisson May 9, 2020
4853bf6
don't show error message when user aborts
dmnisson May 9, 2020
fec7a70
track when session is disconnected by user
dmnisson May 9, 2020
467908c
display connection warning message in chat
dmnisson May 9, 2020
bed6ba3
lint
dmnisson May 9, 2020
b146466
fix computed prop
dmnisson May 9, 2020
d36e72f
update to reflect server changes
dmnisson May 12, 2020
ecdff8f
join session using id from route
dmnisson May 12, 2020
93b0f44
improve readability of faultTolerantHttp function
dmnisson May 12, 2020
828f207
turn TroubleStartingModal into a generic ConnectionTroubleModal
dmnisson May 12, 2020
ed72de0
fault-tolerant joining of existing sessions
dmnisson May 12, 2020
64e9175
DRY out the endSession code
dmnisson May 12, 2020
a3d0165
fetch user upon successful HTTP response from server
dmnisson May 12, 2020
bfdc689
capture Sentry error before redirect
dmnisson May 13, 2020
616e8d0
use Vuex store to keep track of current session
dmnisson May 13, 2020
eefa349
Merge branch 'master' into show-connection-warning
dmnisson May 13, 2020
2291c44
use queued-socket.io
dmnisson May 13, 2020
8778590
Merge branch 'show-connection-warning' of https://github.com/UPchieve…
dmnisson May 13, 2020
6efe263
remove unused imports
dmnisson May 13, 2020
84987b7
remove obsolete user/clearSession dispatch
dmnisson May 13, 2020
d528225
make pretty
dmnisson May 13, 2020
5edbe1d
Merge branch 'master' into show-connection-warning
dmnisson May 14, 2020
a9016f4
touch up warning message about connection issues
jkeat May 18, 2020
f427f1a
lint
jkeat May 18, 2020
e96f76e
Merge branch 'master' into show-connection-warning
jkeat May 18, 2020
ab18afa
Merge branch 'master' into show-connection-warning
jkeat May 19, 2020
4948893
Merge branch 'master' into show-connection-warning
jkeat May 19, 2020
929c09d
Merge branch 'master' into show-connection-warning
jkeat May 19, 2020
57af34a
remove .prevent modifier on End Session button
jkeat May 20, 2020
0342ab5
dispatch user/sessionConnected if user is already connected
jkeat May 20, 2020
8b1d1e3
Merge branch 'master' into show-connection-warning
jkeat May 20, 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
338 changes: 250 additions & 88 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
"moment-timezone": "^0.5.26",
"node-sass": "^4.13.1",
"portal-gun": "git://github.com/claydotio/portal-gun.git",
"promise-retry": "^1.1.1",
"queued-socket.io": "^1.0.5",
"sass-loader": "^7.1.0",
"socket.io-client": "^2.3.0",
jkeat marked this conversation as resolved.
Show resolved Hide resolved
"style-resources-loader": "^1.2.1",
"typeface-work-sans": "0.0.72",
"validator": "^6.3.0",
Expand Down
5 changes: 0 additions & 5 deletions src/components/App/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
<script>
import { mapState, mapGetters } from "vuex";

import SessionService from "@/services/SessionService";

import "@/scss/main.scss";
import AppHeader from "./AppHeader";
import AppSidebar from "./AppSidebar";
Expand Down Expand Up @@ -179,9 +177,6 @@ export default {
},
sockets: {
"session-change"(sessionData) {
SessionService.currentSession.sessionId = sessionData._id;
SessionService.currentSession.data = sessionData;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome to get rid of this :)


this.$store.dispatch("user/updateSession", sessionData);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import vSelect from "vue-select";
import VuePhoneNumberInput from "vue-phone-number-input";
import VueStarRating from "vue-star-rating";

import Socket from "socket.io-client";
import Socket from "queued-socket.io";
import moment from "moment";

import App from "./components/App";
Expand All @@ -20,11 +20,13 @@ import store from "./store";
Vue.config.productionTip = false;

// Set up SocketIO
const socket = Socket(process.env.VUE_APP_SOCKET_ADDRESS, {
const socket = Socket.connect(process.env.VUE_APP_SOCKET_ADDRESS, {
autoConnect: false
});
Vue.use(VueSocketIO, socket);

Vue.prototype.$queuedSocket = Socket;

// Set up Vue Router
Vue.use(VueRouter);

Expand Down
4 changes: 1 addition & 3 deletions src/services/AuthService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import Validator from "validator";

import Vue from "vue";

import NetworkService from "./NetworkService";
import AnalyticsService from "./AnalyticsService";

Expand Down Expand Up @@ -125,7 +123,7 @@ export default {

const authPromise =
!context || isGlobal
? NetworkService.userGlobal(Vue)
? NetworkService.userGlobal()
: NetworkService.user(context);
return authPromise
.then(res => {
Expand Down
73 changes: 64 additions & 9 deletions src/services/NetworkService.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
import Vue from "vue";
import promiseRetry from "promise-retry";
import errcode from "err-code";

const AUTH_ROOT = `${process.env.VUE_APP_SERVER_ROOT}/auth`;
const API_ROOT = `${process.env.VUE_APP_SERVER_ROOT}/api`;
const ELIGIBILITY_API_ROOT = `${process.env.VUE_APP_SERVER_ROOT}/eligibility`;
const CONTACT_API_ROOT = `${process.env.VUE_APP_SERVER_ROOT}/contact`;

const FAULT_TOLERANT_HTTP_TIMEOUT = 10000;
const FAULT_TOLERANT_HTTP_MAX_RETRY_TIMEOUT = 100000;
const FAULT_TOLERANT_HTTP_MAX_RETRIES = 10;

export default {
_successHandler(res) {
return Promise.resolve(res);
},
_errorHandler(res) {
return Promise.reject(res);
},
_faultTolerantHttp(http, method, onRetry, url, data) {
const promiseToRetry = () => {
return (["get", "delete", "head", "jsonp"].indexOf(method) !== -1
? http[method](url, {
timeout: FAULT_TOLERANT_HTTP_TIMEOUT
})
: http[method](url, data, {
timeout: FAULT_TOLERANT_HTTP_TIMEOUT
})
).then(this._successHandler, this._errorHandler);
};

// object property specifying whether this function is aborted
const requestState = { isAborted: false };

return promiseRetry(
retry => {
if (requestState.isAborted) {
// early exit
throw errcode(new Error("Aborted by user"), "EUSERABORTED");
}

return promiseToRetry().catch(res => {
if (res.status === 0) {
if (onRetry) {
onRetry(res, () => {
requestState.isAborted = true;
});
}
retry(res);
}

throw res;
});
},
{
retries: FAULT_TOLERANT_HTTP_MAX_RETRIES,
maxTimeout: FAULT_TOLERANT_HTTP_MAX_RETRY_TIMEOUT
}
);
},

// Server route defintions
login(context, data) {
Expand Down Expand Up @@ -86,7 +133,7 @@ export default {
.get(`${API_ROOT}/user`)
.then(this._successHandler, this._errorHandler);
},
userGlobal(Vue) {
userGlobal() {
ericyliu marked this conversation as resolved.
Show resolved Hide resolved
return Vue.http
.get(`${API_ROOT}/user`)
.then(this._successHandler, this._errorHandler);
Expand Down Expand Up @@ -126,20 +173,28 @@ export default {
.get(`${API_ROOT}/volunteers`)
.then(this._successHandler, this._errorHandler);
},
newSession(context, data) {
return context.$http
.post(`${API_ROOT}/session/new`, data)
.then(this._successHandler, this._errorHandler);
newSession(context, data, onRetry) {
return this._faultTolerantHttp(
context.$http,
"post",
onRetry,
`${API_ROOT}/session/new`,
data
);
},
endSession(context, data) {
return context.$http
.post(`${API_ROOT}/session/end`, data)
.then(this._successHandler, this._errorHandler);
},
checkSession(context, data) {
return context.$http
.post(`${API_ROOT}/session/check`, data)
.then(this._successHandler, this._errorHandler);
checkSession(context, data, onRetry) {
return this._faultTolerantHttp(
context.$http,
"post",
onRetry,
`${API_ROOT}/session/check`,
data
);
},
currentSession(context, data) {
return context.$http
Expand Down
47 changes: 22 additions & 25 deletions src/services/SessionService.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,39 @@ import errorFromHttpResponse from "../utils/error-from-http-response.js";

export default {
loading: false,
currentSession: {
sessionId: null,
data: {}
},

endSession(context, sessionId) {
return NetworkService.endSession(context, { sessionId }).then(() => {
const currentSessionData = context.$store.state.user.session;
context.$store.dispatch("user/clearSession");

// analytics: track when a help session has ended
AnalyticsService.trackSessionEnded(
context,
this.currentSession.data,
currentSessionData,
context.$store.state.user.isFakeUser
);

this.currentSession.sessionId = null;
this.currentSession.data = {};
});
},

newSession(context, sessionType, sessionSubTopic) {
return NetworkService.newSession(context, {
sessionType,
sessionSubTopic
}).then(res => {
newSession(context, sessionType, sessionSubTopic, options) {
const onRetry = options && options.onRetry;

return NetworkService.newSession(
context,
{
sessionType,
sessionSubTopic
},
onRetry
).then(res => {
const data = res.data || {};
const { sessionId } = data;

this.currentSession.sessionId = sessionId;
const currentSession = {
sessionId,
data: context.$store.state.user.session
};

if (sessionId) {
const sessionData = {
Expand All @@ -50,7 +53,7 @@ export default {
// analytics: track when a session has started
AnalyticsService.trackSessionStarted(
context,
this.currentSession,
currentSession,
sessionType,
sessionSubTopic,
context.$store.state.user.isFakeUser
Expand All @@ -60,14 +63,14 @@ export default {
});
},

useExistingSession(context, sessionId) {
return NetworkService.checkSession(context, { sessionId })
useExistingSession(context, sessionId, options) {
const onRetry = options && options.onRetry;

return NetworkService.checkSession(context, { sessionId }, onRetry)
.then(res => {
const data = res.data || {};
const { sessionId } = data;

this.currentSession.sessionId = sessionId;

return sessionId;
})
.catch(res => {
Expand All @@ -89,16 +92,10 @@ export default {
const { type, subTopic } = data;

if (type && subTopic && sessionId) {
this.currentSession.sessionId = sessionId;
this.currentSession.data = data;

return Promise.resolve({ sessionData: data });
}
})
.catch(resp => {
this.currentSession.sessionId = null;
this.currentSession.data = {};

throw errorFromHttpResponse(resp);
});
},
Expand Down
15 changes: 14 additions & 1 deletion src/store/modules/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default {
session: {},
latestSession: {},
volunteerStats: {},
isFirstDashboardVisit: false
isFirstDashboardVisit: false,
isSessionConnectionAlive: false
},
mutations: {
setUser: (state, user = {}) => (state.user = user),
Expand Down Expand Up @@ -55,6 +56,10 @@ export default {

setIsFirstDashboardVisit: (state, isFirstDashboardVisit) => {
state.isFirstDashboardVisit = isFirstDashboardVisit;
},

setIsSessionConnectionAlive: (state, isSessionConnectionAlive) => {
state.isSessionConnectionAlive = isSessionConnectionAlive;
}
},
actions: {
Expand Down Expand Up @@ -112,6 +117,14 @@ export default {
commit("setSession", {});
},

sessionDisconnected: ({ commit }) => {
commit("setIsSessionConnectionAlive", false);
},

sessionConnected: ({ commit }) => {
commit("setIsSessionConnectionAlive", true);
},

updateSession: ({ commit }, sessionData) => {
commit("setSession", sessionData);
},
Expand Down
2 changes: 1 addition & 1 deletion src/utils/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const rejoinSession = (router, sessionPath) => {
* @param {object} context
*/
export const endSession = context => {
const sessionId = SessionService.currentSession.sessionId;
const sessionId = context.$store.state.user.session._id;
SessionService.endSession(context, sessionId)
.then(() => {
context.$socket.disconnect();
Expand Down
56 changes: 56 additions & 0 deletions src/views/SessionView/ConnectionTroubleModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<div class="ConnectionTroubleModal">
<h1 class="ConnectionTroubleModal-title">Connection Problems</h1>
<div class="ConnectionTroubleModal-message">
{{ modalData.message }}
</div>
<large-button v-if="mobileMode" primary @click.native="onAccept">{{
modalData.acceptText
}}</large-button>
</div>
</template>

<script>
import { mapGetters } from "vuex";
import LargeButton from "@/components/LargeButton";

export default {
components: { LargeButton },
props: {
modalData: { type: Object, required: true }
},
computed: {
...mapGetters({
mobileMode: "app/mobileMode",
isVolunteer: "user/isVolunteer"
})
},
methods: {
onAccept() {
this.modalData.abortFunction();
this.$router.push("/");
}
}
};
</script>

<style lang="scss" scoped>
.ConnectionTroubleModal {
@include flex-container(column);
@include child-spacing(top, 24px);
@include breakpoint-above("medium") {
@include child-spacing(top, 16px);
}
}

.ConnectionTroubleModal-title {
@include font-category("display-small");
@include breakpoint-above("medium") {
margin-top: 24px;
}
}

.ConnectionTroubleModal-message {
@include font-category("body");
}
</style>
Loading