* feat: added warning for unreachable server * fix: update initial state in test file * fix: make offline warning scroll with page * adjust z-indexes for warning banners * add hyperlink for offline warning
110 lines
3.3 KiB
JavaScript
110 lines
3.3 KiB
JavaScript
import { ofType } from 'redux-observable';
|
|
import { merge, empty } from 'rxjs';
|
|
import {
|
|
tap,
|
|
filter,
|
|
map,
|
|
ignoreElements,
|
|
switchMap,
|
|
catchError
|
|
} from 'rxjs/operators';
|
|
import store from 'store';
|
|
import { v4 as uuid } from 'uuid';
|
|
|
|
import { backEndProject } from '../../utils/challenge-types';
|
|
import { isGoodXHRStatus } from '../templates/Challenges/utils';
|
|
import postUpdate$ from '../templates/Challenges/utils/postUpdate$';
|
|
import { actionTypes } from './action-types';
|
|
import {
|
|
serverStatusChange,
|
|
isServerOnlineSelector,
|
|
isSignedInSelector
|
|
} from './';
|
|
|
|
const key = 'fcc-failed-updates';
|
|
|
|
function delay(time = 0, fn) {
|
|
return setTimeout(fn, time);
|
|
}
|
|
|
|
// check if backendEndProjects have a solution
|
|
const isSubmitable = failure =>
|
|
failure.payload.challengeType !== backEndProject || failure.payload.solution;
|
|
|
|
function failedUpdateEpic(action$, state$) {
|
|
const storeUpdates = action$.pipe(
|
|
ofType(actionTypes.updateFailed),
|
|
tap(({ payload = {} }) => {
|
|
if ('endpoint' in payload && 'payload' in payload) {
|
|
const failures = store.get(key) || [];
|
|
payload.id = uuid();
|
|
store.set(key, [...failures, payload]);
|
|
}
|
|
}),
|
|
map(() => serverStatusChange(false))
|
|
);
|
|
|
|
const flushUpdates = action$.pipe(
|
|
ofType(actionTypes.fetchUserComplete, actionTypes.updateComplete),
|
|
filter(() => isSignedInSelector(state$.value)),
|
|
filter(() => store.get(key)),
|
|
filter(() => isServerOnlineSelector(state$.value)),
|
|
tap(() => {
|
|
let failures = store.get(key) || [];
|
|
|
|
let submitableFailures = failures.filter(isSubmitable);
|
|
|
|
// delete unsubmitable failed challenges
|
|
if (submitableFailures.length !== failures.length) {
|
|
store.set(key, submitableFailures);
|
|
failures = submitableFailures;
|
|
}
|
|
|
|
let delayTime = 100;
|
|
const batch = failures.map((update, i) => {
|
|
// we stagger the updates here so we don't hammer the server
|
|
// *********************************************************
|
|
// progressively increase additional delay by the amount of updates
|
|
// 1st: 100ms delay
|
|
// 2nd: 200ms delay
|
|
// 3rd: 400ms delay
|
|
// 4th: 700ms delay
|
|
// 5th: 1100ms delay
|
|
// 6th: 1600ms delay
|
|
// and so-on
|
|
delayTime += 100 * i;
|
|
return delay(delayTime, () =>
|
|
postUpdate$(update)
|
|
.pipe(
|
|
switchMap(response => {
|
|
if (
|
|
response &&
|
|
(response.message || isGoodXHRStatus(response.status))
|
|
) {
|
|
console.info(`${update.id} succeeded`);
|
|
// the request completed successfully
|
|
const failures = store.get(key) || [];
|
|
const newFailures = failures.filter(x => x.id !== update.id);
|
|
store.set(key, newFailures);
|
|
}
|
|
return empty();
|
|
}),
|
|
catchError(() => empty())
|
|
)
|
|
.toPromise()
|
|
);
|
|
});
|
|
Promise.all(batch)
|
|
.then(() => console.info('progress updates processed where possible'))
|
|
.catch(err =>
|
|
console.warn('unable to process progress updates', err.message)
|
|
);
|
|
}),
|
|
ignoreElements()
|
|
);
|
|
|
|
return merge(storeUpdates, flushUpdates);
|
|
}
|
|
|
|
export default failedUpdateEpic;
|