Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
abe330c3
Commit
abe330c3
authored
May 27, 2021
by
Thomas Randolph
Committed by
David O'Regan
May 27, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Delay showing errors when MR Notes polling fails
parent
b09454a2
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
171 additions
and
35 deletions
+171
-35
app/assets/javascripts/notes/stores/actions.js
app/assets/javascripts/notes/stores/actions.js
+24
-2
spec/frontend/notes/stores/actions_spec.js
spec/frontend/notes/stores/actions_spec.js
+147
-33
No files found.
app/assets/javascripts/notes/stores/actions.js
View file @
abe330c3
...
@@ -12,6 +12,7 @@ import loadAwardsHandler from '../../awards_handler';
...
@@ -12,6 +12,7 @@ import loadAwardsHandler from '../../awards_handler';
import
{
deprecatedCreateFlash
as
Flash
}
from
'
../../flash
'
;
import
{
deprecatedCreateFlash
as
Flash
}
from
'
../../flash
'
;
import
{
isInViewport
,
scrollToElement
,
isInMRPage
}
from
'
../../lib/utils/common_utils
'
;
import
{
isInViewport
,
scrollToElement
,
isInMRPage
}
from
'
../../lib/utils/common_utils
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
Poll
from
'
../../lib/utils/poll
'
;
import
{
create
}
from
'
../../lib/utils/recurrence
'
;
import
{
mergeUrlParams
}
from
'
../../lib/utils/url_utility
'
;
import
{
mergeUrlParams
}
from
'
../../lib/utils/url_utility
'
;
import
sidebarTimeTrackingEventHub
from
'
../../sidebar/event_hub
'
;
import
sidebarTimeTrackingEventHub
from
'
../../sidebar/event_hub
'
;
import
TaskList
from
'
../../task_list
'
;
import
TaskList
from
'
../../task_list
'
;
...
@@ -21,6 +22,7 @@ import eventHub from '../event_hub';
...
@@ -21,6 +22,7 @@ import eventHub from '../event_hub';
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
*
as
utils
from
'
./utils
'
;
import
*
as
utils
from
'
./utils
'
;
const
NOTES_POLLING_INTERVAL
=
6000
;
let
eTagPoll
;
let
eTagPoll
;
export
const
updateLockedAttribute
=
({
commit
,
getters
},
{
locked
,
fullPath
})
=>
{
export
const
updateLockedAttribute
=
({
commit
,
getters
},
{
locked
,
fullPath
})
=>
{
...
@@ -469,6 +471,19 @@ const getFetchDataParams = (state) => {
...
@@ -469,6 +471,19 @@ const getFetchDataParams = (state) => {
};
};
export
const
poll
=
({
commit
,
state
,
getters
,
dispatch
})
=>
{
export
const
poll
=
({
commit
,
state
,
getters
,
dispatch
})
=>
{
const
notePollOccurrenceTracking
=
create
();
let
flashContainer
;
notePollOccurrenceTracking
.
handle
(
1
,
()
=>
{
// Since polling halts internally after 1 failure, we manually try one more time
setTimeout
(()
=>
eTagPoll
.
restart
(),
NOTES_POLLING_INTERVAL
);
});
notePollOccurrenceTracking
.
handle
(
2
,
()
=>
{
// On the second failure in a row, show the alert and try one more time (hoping to succeed and clear the error)
flashContainer
=
Flash
(
__
(
'
Something went wrong while fetching latest comments.
'
));
setTimeout
(()
=>
eTagPoll
.
restart
(),
NOTES_POLLING_INTERVAL
);
});
eTagPoll
=
new
Poll
({
eTagPoll
=
new
Poll
({
resource
:
{
resource
:
{
poll
:
()
=>
{
poll
:
()
=>
{
...
@@ -477,8 +492,15 @@ export const poll = ({ commit, state, getters, dispatch }) => {
...
@@ -477,8 +492,15 @@ export const poll = ({ commit, state, getters, dispatch }) => {
},
},
},
},
method
:
'
poll
'
,
method
:
'
poll
'
,
successCallback
:
({
data
})
=>
pollSuccessCallBack
(
data
,
commit
,
state
,
getters
,
dispatch
),
successCallback
:
({
data
})
=>
{
errorCallback
:
()
=>
Flash
(
__
(
'
Something went wrong while fetching latest comments.
'
)),
pollSuccessCallBack
(
data
,
commit
,
state
,
getters
,
dispatch
);
if
(
notePollOccurrenceTracking
.
count
)
{
notePollOccurrenceTracking
.
reset
();
}
flashContainer
?.
close
();
},
errorCallback
:
()
=>
notePollOccurrenceTracking
.
occur
(),
});
});
if
(
!
Visibility
.
hidden
())
{
if
(
!
Visibility
.
hidden
())
{
...
...
spec/frontend/notes/stores/actions_spec.js
View file @
abe330c3
...
@@ -25,7 +25,19 @@ import {
...
@@ -25,7 +25,19 @@ import {
}
from
'
../mock_data
'
;
}
from
'
../mock_data
'
;
const
TEST_ERROR_MESSAGE
=
'
Test error message
'
;
const
TEST_ERROR_MESSAGE
=
'
Test error message
'
;
jest
.
mock
(
'
~/flash
'
);
const
mockFlashClose
=
jest
.
fn
();
jest
.
mock
(
'
~/flash
'
,
()
=>
{
const
flash
=
jest
.
fn
().
mockImplementation
(()
=>
{
return
{
close
:
mockFlashClose
,
};
});
return
{
createFlash
:
flash
,
deprecatedCreateFlash
:
flash
,
};
});
describe
(
'
Actions Notes Store
'
,
()
=>
{
describe
(
'
Actions Notes Store
'
,
()
=>
{
let
commit
;
let
commit
;
...
@@ -254,42 +266,144 @@ describe('Actions Notes Store', () => {
...
@@ -254,42 +266,144 @@ describe('Actions Notes Store', () => {
});
});
describe
(
'
poll
'
,
()
=>
{
describe
(
'
poll
'
,
()
=>
{
beforeEach
((
done
)
=>
{
const
pollInterval
=
6000
;
axiosMock
const
pollResponse
=
{
notes
:
[],
last_fetched_at
:
'
123456
'
};
.
onGet
(
notesDataMock
.
notesPath
)
const
pollHeaders
=
{
'
poll-interval
'
:
`
${
pollInterval
}
`
};
.
reply
(
200
,
{
notes
:
[],
last_fetched_at
:
'
123456
'
},
{
'
poll-interval
'
:
'
1000
'
});
const
successMock
=
()
=>
axiosMock
.
onGet
(
notesDataMock
.
notesPath
).
reply
(
200
,
pollResponse
,
pollHeaders
);
const
failureMock
=
()
=>
axiosMock
.
onGet
(
notesDataMock
.
notesPath
).
reply
(
500
);
const
advanceAndRAF
=
async
(
time
)
=>
{
if
(
time
)
{
jest
.
advanceTimersByTime
(
time
);
}
return
new
Promise
((
resolve
)
=>
requestAnimationFrame
(
resolve
));
};
const
advanceXMoreIntervals
=
async
(
number
)
=>
{
const
timeoutLength
=
pollInterval
*
number
;
return
advanceAndRAF
(
timeoutLength
);
};
const
startPolling
=
async
()
=>
{
await
store
.
dispatch
(
'
poll
'
);
await
advanceAndRAF
(
2
);
};
const
cleanUp
=
async
()
=>
{
jest
.
clearAllTimers
();
return
store
.
dispatch
(
'
stopPolling
'
);
};
beforeEach
((
done
)
=>
{
store
.
dispatch
(
'
setNotesData
'
,
notesDataMock
).
then
(
done
).
catch
(
done
.
fail
);
store
.
dispatch
(
'
setNotesData
'
,
notesDataMock
).
then
(
done
).
catch
(
done
.
fail
);
});
});
it
(
'
calls service with last fetched state
'
,
(
done
)
=>
{
afterEach
(()
=>
{
store
return
cleanUp
();
.
dispatch
(
'
poll
'
)
});
.
then
(()
=>
{
jest
.
advanceTimersByTime
(
2
);
it
(
'
calls service with last fetched state
'
,
async
()
=>
{
})
successMock
();
.
then
(()
=>
new
Promise
((
resolve
)
=>
requestAnimationFrame
(
resolve
)))
.
then
(()
=>
{
await
startPolling
();
expect
(
store
.
state
.
lastFetchedAt
).
toBe
(
'
123456
'
);
expect
(
store
.
state
.
lastFetchedAt
).
toBe
(
'
123456
'
);
jest
.
advanceTimersByTime
(
1500
);
})
await
advanceXMoreIntervals
(
1
);
.
then
(
()
=>
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
2
);
new
Promise
((
resolve
)
=>
{
expect
(
axiosMock
.
history
.
get
[
1
].
headers
).
toMatchObject
({
requestAnimationFrame
(
resolve
);
'
X-Last-Fetched-At
'
:
'
123456
'
,
}),
});
)
});
.
then
(()
=>
{
const
expectedGetRequests
=
2
;
describe
(
'
polling side effects
'
,
()
=>
{
expect
(
axiosMock
.
history
.
get
.
length
).
toBe
(
expectedGetRequests
);
it
(
'
retries twice
'
,
async
()
=>
{
expect
(
axiosMock
.
history
.
get
[
expectedGetRequests
-
1
].
headers
).
toMatchObject
({
failureMock
();
'
X-Last-Fetched-At
'
:
'
123456
'
,
});
await
startPolling
();
})
.
then
(()
=>
store
.
dispatch
(
'
stopPolling
'
))
// This is the first request, not a retry
.
then
(
done
)
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
1
);
.
catch
(
done
.
fail
);
await
advanceXMoreIntervals
(
1
);
// Retry #1
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
2
);
await
advanceXMoreIntervals
(
1
);
// Retry #2
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
3
);
await
advanceXMoreIntervals
(
10
);
// There are no more retries
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
3
);
});
it
(
'
shows the error display on the second failure
'
,
async
()
=>
{
failureMock
();
await
startPolling
();
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
1
);
expect
(
Flash
).
not
.
toHaveBeenCalled
();
await
advanceXMoreIntervals
(
1
);
expect
(
axiosMock
.
history
.
get
).
toHaveLength
(
2
);
expect
(
Flash
).
toHaveBeenCalled
();
expect
(
Flash
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
resets the failure counter on success
'
,
async
()
=>
{
// We can't get access to the actual counter in the polling closure.
// So we can infer that it's reset by ensuring that the error is only
// shown when we cause two failures in a row - no successes between
axiosMock
.
onGet
(
notesDataMock
.
notesPath
)
.
replyOnce
(
500
)
// cause one error
.
onGet
(
notesDataMock
.
notesPath
)
.
replyOnce
(
200
,
pollResponse
,
pollHeaders
)
// then a success
.
onGet
(
notesDataMock
.
notesPath
)
.
reply
(
500
);
// and then more errors
await
startPolling
();
// Failure #1
await
advanceXMoreIntervals
(
1
);
// Success #1
await
advanceXMoreIntervals
(
1
);
// Failure #2
// That was the first failure AFTER a success, so we should NOT see the error displayed
expect
(
Flash
).
not
.
toHaveBeenCalled
();
// Now we'll allow another failure
await
advanceXMoreIntervals
(
1
);
// Failure #3
// Since this is the second failure in a row, the error should happen
expect
(
Flash
).
toHaveBeenCalled
();
expect
(
Flash
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
hides the error display if it exists on success
'
,
async
()
=>
{
jest
.
mock
();
failureMock
();
await
startPolling
();
await
advanceXMoreIntervals
(
2
);
// After two errors, the error should be displayed
expect
(
Flash
).
toHaveBeenCalled
();
expect
(
Flash
).
toHaveBeenCalledTimes
(
1
);
axiosMock
.
reset
();
successMock
();
await
advanceXMoreIntervals
(
1
);
expect
(
mockFlashClose
).
toHaveBeenCalled
();
expect
(
mockFlashClose
).
toHaveBeenCalledTimes
(
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