mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Exponentially back off the time between message polling instead of doing it every two minutes.
This commit is contained in:
parent
b385288e39
commit
5de96c373d
2 changed files with 110 additions and 16 deletions
|
@ -27,7 +27,8 @@ class Navigation extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
bindAll(this, [
|
bindAll(this, [
|
||||||
'getProfileUrl',
|
'getProfileUrl',
|
||||||
'handleSearchSubmit'
|
'handleSearchSubmit',
|
||||||
|
'setupMessagePolling'
|
||||||
]);
|
]);
|
||||||
this.state = {
|
this.state = {
|
||||||
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
||||||
|
@ -35,27 +36,18 @@ class Navigation extends React.Component {
|
||||||
}
|
}
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
if (this.props.user) {
|
if (this.props.user) {
|
||||||
const intervalId = setInterval(() => {
|
// Setup polling for messages to start in 2 minutes.
|
||||||
this.props.getMessageCount(this.props.user.username);
|
setTimeout(this.setupMessagePolling.bind(this, 2), 2 * 60 * 1000);
|
||||||
}, 120000); // check for new messages every 2 mins.
|
|
||||||
this.setState({ // eslint-disable-line react/no-did-mount-set-state
|
|
||||||
messageCountIntervalId: intervalId
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.user !== this.props.user) {
|
if (prevProps.user !== this.props.user) {
|
||||||
this.props.handleCloseAccountNav();
|
this.props.handleCloseAccountNav();
|
||||||
if (this.props.user) {
|
if (this.props.user) {
|
||||||
const intervalId = setInterval(() => {
|
setTimeout(this.setupMessagePolling.bind(this, 2), 2 * 60 * 1000);
|
||||||
this.props.getMessageCount(this.props.user.username);
|
|
||||||
}, 120000); // check for new messages every 2 mins.
|
|
||||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
|
||||||
messageCountIntervalId: intervalId
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// clear message count check, and set to default id.
|
// clear message count check, and set to default id.
|
||||||
clearInterval(this.state.messageCountIntervalId);
|
clearTimeout(this.state.messageCountIntervalId);
|
||||||
this.props.setMessageCount(0);
|
this.props.setMessageCount(0);
|
||||||
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
this.setState({ // eslint-disable-line react/no-did-update-set-state
|
||||||
messageCountIntervalId: -1
|
messageCountIntervalId: -1
|
||||||
|
@ -66,7 +58,7 @@ class Navigation extends React.Component {
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
// clear message interval if it exists
|
// clear message interval if it exists
|
||||||
if (this.state.messageCountIntervalId !== -1) {
|
if (this.state.messageCountIntervalId !== -1) {
|
||||||
clearInterval(this.state.messageCountIntervalId);
|
clearTimeout(this.state.messageCountIntervalId);
|
||||||
this.props.setMessageCount(0);
|
this.props.setMessageCount(0);
|
||||||
this.setState({
|
this.setState({
|
||||||
messageCountIntervalId: -1
|
messageCountIntervalId: -1
|
||||||
|
@ -77,6 +69,23 @@ class Navigation extends React.Component {
|
||||||
if (!this.props.user) return;
|
if (!this.props.user) return;
|
||||||
return `/users/${this.props.user.username}/`;
|
return `/users/${this.props.user.username}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupMessagePolling (minutes) {
|
||||||
|
this.props.getMessageCount(this.props.user.username);
|
||||||
|
// We only poll if it has been less than 30 minutes.
|
||||||
|
// Chances of someone actively using the page for that long without
|
||||||
|
// a navigation is low.
|
||||||
|
if (minutes < 32) {
|
||||||
|
const nextFetch = minutes * 2;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
this.setupMessagePolling(nextFetch);
|
||||||
|
}, nextFetch * 60000);
|
||||||
|
this.setState({ // eslint-disable-line react/no-did-mount-set-state
|
||||||
|
messageCountIntervalId: timeoutId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleSearchSubmit (formData) {
|
handleSearchSubmit (formData) {
|
||||||
let targetUrl = '/search/projects';
|
let targetUrl = '/search/projects';
|
||||||
if (formData.q) {
|
if (formData.q) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
|
const {shallowWithIntl, mountWithIntl} = require('../../helpers/intl-helpers.jsx');
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
const Navigation = require('../../../src/components/navigation/www/navigation.jsx');
|
const Navigation = require('../../../src/components/navigation/www/navigation.jsx');
|
||||||
const Registration = require('../../../src/components/registration/registration.jsx');
|
const Registration = require('../../../src/components/registration/registration.jsx');
|
||||||
|
@ -11,6 +11,7 @@ describe('Navigation', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store = null;
|
store = null;
|
||||||
|
jest.useFakeTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getNavigationWrapper = props => {
|
const getNavigationWrapper = props => {
|
||||||
|
@ -103,4 +104,88 @@ describe('Navigation', () => {
|
||||||
navWrapper.find('a.registrationLink').simulate('click', {preventDefault () {}});
|
navWrapper.find('a.registrationLink').simulate('click', {preventDefault () {}});
|
||||||
expect(navInstance.props.handleClickRegistration).toHaveBeenCalled();
|
expect(navInstance.props.handleClickRegistration).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Component sets up message polling when it mounts', () => {
|
||||||
|
store = mockStore({
|
||||||
|
navigation: {
|
||||||
|
registrationOpen: false
|
||||||
|
},
|
||||||
|
messageCount: {
|
||||||
|
messageCount: 5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const props = {
|
||||||
|
user: {
|
||||||
|
thumbnailUrl: 'scratch.mit.edu',
|
||||||
|
username: 'auser'
|
||||||
|
},
|
||||||
|
getMessageCount: jest.fn()
|
||||||
|
};
|
||||||
|
const intlWrapper = mountWithIntl(
|
||||||
|
<Navigation
|
||||||
|
{...props}
|
||||||
|
/>, {context: {store},
|
||||||
|
childContextTypes: {store}
|
||||||
|
});
|
||||||
|
|
||||||
|
intlWrapper.children().find('Navigation')
|
||||||
|
.instance();
|
||||||
|
const twoMin = 2 * 60 * 1000;
|
||||||
|
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin);
|
||||||
|
// Advance timers passed the intial two minutes.
|
||||||
|
jest.advanceTimersByTime(twoMin + 1);
|
||||||
|
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), twoMin * 2);
|
||||||
|
expect(props.getMessageCount).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('SetupMessagePolling polls for messages 5 times', () => {
|
||||||
|
store = mockStore({
|
||||||
|
navigation: {
|
||||||
|
registrationOpen: false
|
||||||
|
},
|
||||||
|
messageCount: {
|
||||||
|
messageCount: 5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const props = {
|
||||||
|
user: {
|
||||||
|
thumbnailUrl: 'scratch.mit.edu',
|
||||||
|
username: 'auser'
|
||||||
|
},
|
||||||
|
getMessageCount: jest.fn()
|
||||||
|
};
|
||||||
|
const intlWrapper = mountWithIntl(
|
||||||
|
<Navigation
|
||||||
|
{...props}
|
||||||
|
/>, {context: {store},
|
||||||
|
childContextTypes: {store}
|
||||||
|
});
|
||||||
|
|
||||||
|
const navInstance = intlWrapper.children().find('Navigation')
|
||||||
|
.instance();
|
||||||
|
// Clear the timers and mocks because componentDidMount
|
||||||
|
// has already called setupMessagePolling.
|
||||||
|
jest.clearAllTimers();
|
||||||
|
jest.clearAllMocks();
|
||||||
|
|
||||||
|
navInstance.setupMessagePolling(2);
|
||||||
|
|
||||||
|
// Check that we set the timeout to backoff exponentially
|
||||||
|
let minutes = 2 * 60 * 1000;
|
||||||
|
for (let count = 1; count < 5; ++count) {
|
||||||
|
jest.advanceTimersByTime(minutes + 1);
|
||||||
|
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), minutes * 2);
|
||||||
|
expect(props.getMessageCount).toHaveBeenCalledTimes(count);
|
||||||
|
minutes = minutes * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exhaust all timers (there shouldn't be any left)
|
||||||
|
jest.runAllTimers();
|
||||||
|
// We exponentially back off checking for messages, starting at 2 min
|
||||||
|
// and stop after 32 minutes so it should happen 5 times total.
|
||||||
|
expect(props.getMessageCount).toHaveBeenCalledTimes(5);
|
||||||
|
// setTimeout happens 1 fewer since the original call to
|
||||||
|
// setupMessagePolling isn't counted here.
|
||||||
|
expect(setTimeout).toHaveBeenCalledTimes(4);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue