Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
scratch-www
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
xpstem
scratch-www
Commits
05075712
Unverified
Commit
05075712
authored
Aug 07, 2020
by
picklesrus
Committed by
GitHub
Aug 07, 2020
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4292 from picklesrus/backoff-messages
Exponentially back off the time between message polling.
parents
a2651b74
7d470131
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
153 additions
and
27 deletions
+153
-27
src/components/navigation/www/navigation.jsx
src/components/navigation/www/navigation.jsx
+32
-26
test/unit/components/navigation.test.jsx
test/unit/components/navigation.test.jsx
+121
-1
No files found.
src/components/navigation/www/navigation.jsx
View file @
05075712
...
@@ -27,56 +27,62 @@ class Navigation extends React.Component {
...
@@ -27,56 +27,62 @@ class Navigation extends React.Component {
super
(
props
);
super
(
props
);
bindAll
(
this
,
[
bindAll
(
this
,
[
'
getProfileUrl
'
,
'
getProfileUrl
'
,
'
handleSearchSubmit
'
'
handleSearchSubmit
'
,
'
pollForMessages
'
]);
]);
this
.
state
=
{
// Keep the timeout id so we can cancel it (e.g. when we unmount)
messageCountIntervalId
:
-
1
// javascript method interval id for getting messsage count.
this
.
messageCountTimeoutId
=
-
1
;
};
}
}
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
);
const
twoMinInMs
=
2
*
60
*
1000
;
},
120000
);
// check for new messages every 2 mins.
this
.
messageCountTimeoutId
=
setTimeout
(
this
.
pollForMessages
.
bind
(
this
,
twoMinInMs
),
twoMinInMs
);
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
(()
=>
{
const
twoMinInMs
=
2
*
60
*
1000
;
this
.
props
.
getMessageCount
(
this
.
props
.
user
.
username
);
this
.
messageCountTimeoutId
=
setTimeout
(
this
.
pollForMessages
.
bind
(
this
,
twoMinInMs
),
twoMinInMs
);
},
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
);
if
(
this
.
messageCountTimeoutId
!==
-
1
)
{
clearTimeout
(
this
.
messageCountTimeoutId
);
}
this
.
props
.
setMessageCount
(
0
);
this
.
props
.
setMessageCount
(
0
);
this
.
setState
({
// eslint-disable-line react/no-did-update-set-state
this
.
messageCountTimeoutId
=
-
1
;
messageCountIntervalId
:
-
1
});
}
}
}
}
}
}
componentWillUnmount
()
{
componentWillUnmount
()
{
// clear message interval if it exists
// clear message interval if it exists
if
(
this
.
state
.
messageCountInterval
Id
!==
-
1
)
{
if
(
this
.
messageCountTimeout
Id
!==
-
1
)
{
clear
Interval
(
this
.
state
.
messageCountInterval
Id
);
clear
Timeout
(
this
.
messageCountTimeout
Id
);
this
.
props
.
setMessageCount
(
0
);
this
.
props
.
setMessageCount
(
0
);
this
.
setState
({
this
.
messageCountTimeoutId
=
-
1
;
messageCountIntervalId
:
-
1
});
}
}
}
}
getProfileUrl
()
{
getProfileUrl
()
{
if
(
!
this
.
props
.
user
)
return
;
if
(
!
this
.
props
.
user
)
return
;
return
`/users/
${
this
.
props
.
user
.
username
}
/`
;
return
`/users/
${
this
.
props
.
user
.
username
}
/`
;
}
}
pollForMessages
(
ms
)
{
this
.
props
.
getMessageCount
(
this
.
props
.
user
.
username
);
// We only poll if it has been less than 32 minutes.
// Chances of someone actively using the page for that long without
// a navigation is low.
if
(
ms
<
32
*
60
*
1000
)
{
// 32 minutes
const
nextFetch
=
ms
*
2
;
// exponentially back off next fetch time.
const
timeoutId
=
setTimeout
(()
=>
{
this
.
pollForMessages
(
nextFetch
);
},
nextFetch
);
this
.
messageCountTimeoutId
=
timeoutId
;
}
}
handleSearchSubmit
(
formData
)
{
handleSearchSubmit
(
formData
)
{
let
targetUrl
=
'
/search/projects
'
;
let
targetUrl
=
'
/search/projects
'
;
if
(
formData
.
q
)
{
if
(
formData
.
q
)
{
...
...
test/unit/components/navigation.test.jsx
View file @
05075712
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', () => {
...
@@ -11,6 +11,7 @@ describe('Navigation', () => {
beforeEach
(()
=>
{
beforeEach
(()
=>
{
store
=
null
;
store
=
null
;
jest
.
useFakeTimers
();
});
});
const
getNavigationWrapper
=
props
=>
{
const
getNavigationWrapper
=
props
=>
{
...
@@ -103,4 +104,123 @@ describe('Navigation', () => {
...
@@ -103,4 +104,123 @@ 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
}
});
const
navInstance
=
intlWrapper
.
children
().
find
(
'
Navigation
'
)
.
instance
();
const
twoMin
=
2
*
60
*
1000
;
expect
(
setTimeout
).
toHaveBeenLastCalledWith
(
expect
.
any
(
Function
),
twoMin
);
expect
(
navInstance
.
messageCountTimeoutId
).
not
.
toEqual
(
-
1
);
// Advance timers passed the intial two minutes.
jest
.
advanceTimersByTime
(
twoMin
+
1
);
expect
(
setTimeout
).
toHaveBeenLastCalledWith
(
expect
.
any
(
Function
),
twoMin
*
2
);
expect
(
props
.
getMessageCount
).
toHaveBeenCalled
();
expect
(
navInstance
.
messageCountTimeoutId
).
not
.
toEqual
(
-
1
);
});
test
(
'
Component cancels timers when it unmounts
'
,
()
=>
{
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
();
const
twoMin
=
2
*
60
*
1000
;
expect
(
setTimeout
).
toHaveBeenLastCalledWith
(
expect
.
any
(
Function
),
twoMin
);
expect
(
navInstance
.
messageCountTimeoutId
).
not
.
toEqual
(
-
1
);
navInstance
.
componentWillUnmount
();
expect
(
clearTimeout
).
toHaveBeenCalledWith
(
expect
.
any
(
Number
));
expect
(
navInstance
.
messageCountTimeoutId
).
toEqual
(
-
1
);
});
test
(
'
pollForMessages 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 pollForMessages.
jest
.
clearAllTimers
();
jest
.
clearAllMocks
();
let
twoMinInMs
=
2
*
60
*
1000
;
// 2 minutes in ms.
navInstance
.
pollForMessages
(
twoMinInMs
);
expect
(
navInstance
.
messageCountTimeoutId
).
not
.
toEqual
(
-
1
);
// Check that we set the timeout to backoff exponentially
for
(
let
count
=
1
;
count
<
5
;
++
count
)
{
jest
.
advanceTimersByTime
(
twoMinInMs
+
1
);
expect
(
setTimeout
).
toHaveBeenLastCalledWith
(
expect
.
any
(
Function
),
twoMinInMs
*
2
);
expect
(
props
.
getMessageCount
).
toHaveBeenCalledTimes
(
count
);
twoMinInMs
=
twoMinInMs
*
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
// pollForMessages isn't counted here.
expect
(
setTimeout
).
toHaveBeenCalledTimes
(
4
);
expect
(
navInstance
.
messageCountTimeoutId
).
not
.
toEqual
(
-
1
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment