Add a small utility for tracking (and reacting to) repeated events
What does this MR do?
For #292008 (closed)
Adds a small utility that tracks occurrences of anything (as determined by the call-site logic) and reacts to particular counts with caller-defined behavior.
😠
Why is this so large? Sorry!
It's the tests and JSDoc blocks. The actual code here is about 50 LoC.
Sorry, such is the life we live!
Here's all of the code (without tests) with the comments removed...
It's certainly not as easy to review, but this comment-free version might be easier to scan initially and get a good grasp of what's happening.
import { uuids } from '../../diffs/utils/uuids';
const instances = {};
export function create() {
const id = uuids()[0];
let handlers = {};
let count = 0;
const instance = {
get id() {
return id;
},
get count() {
return count;
},
get handlers() {
return handlers;
},
free: () => {
return delete instances[id];
},
handle: (onCount, behavior) => {
if (onCount && behavior) {
handlers[onCount] = behavior;
}
},
eject: (onCount) => {
if (onCount) {
delete handlers[onCount];
}
},
occur: () => {
count += 1;
if (typeof handlers[count] === 'function') {
handlers[count](count);
}
},
reset: ({ currentCount = true, handlersList = false } = {}) => {
if (currentCount) {
count = 0;
}
if (handlersList) {
handlers = {};
}
},
};
instances[id] = instance;
return instance;
}
export function recall(id) {
return instances[id];
}
export function free(id) {
return recall(id)?.free() || false;
}
Fine, what does this do?
It tracks occurrences of something when you expect it to happen multiple times. Thus recurrence
.
Here's a very rough example:
let count = 0;
// something occurs
count += 1;
if( count == 5 ){
// do things
}
else if( count == 10 ){
// do another thing
}
// it occurs again
count += 1;
if( count == 5 ){
// do things
}
else if( count == 10 ){
// do another thing
}
This is annoying.
A better implementation of the above...
let count = 0;
const itHappened = () => {
count += 1;
if( count == 5 ){
// do things
}
else if( count == 10 ){
// do another thing
}
};
// something occurs
itHappened();
// it occurs again
itHappened()
As you can see, this is starting to look a lot like the abstracted implementation found in this MR...
Here's
import { create } from 'lib/utils/recurrence';
const thingRecurrence = create();
thingRecurrence.handle( 5, () => { // do things } );
thingRecurrence.handle( 10, () => { // do another thing } );
// something occurs
thingRecurrence.occur();
// it occurs again
thingRecurrence.occur();
Not sure I see why...
Check out !60533 (merged) for the ur-example.
tl;dr: We need to track network errors across multiple calls. But - in practice - those calls aren't actually linked: they're not retries of the same initial call, for example. So this helper will count occurrences of a network call failing, and trigger error displays only when it's not a transient failure (2 in a row).
As you can imagine from the boilerplate above, doing this manually in the action is a bit klunky. Also, storing it in Vuex is extremely inefficient: we don't need a global object that informs every single subscriber, we just need a closure over some data!
Okay, but...
It's
- Fully documented with JSDoc blocks
- Make sure you're up to date (
yarn upgrade
) and runyarn run jsdoc
. Once it's compiled, serve the/jsdocs
folder (I likenpx serve
) and check out the extensive documentation!
- Make sure you're up to date (
- Pretty robust internally
- There are plenty of safety checks when functions try to execute behavior that guard against unexpected use paths.
- It uses
getters
to expose values that shouldn't be mutated externally
- Tested thoroughly
- There are almost as many individual tests as there are lines of functional code in the source file!
Where are we?
MR | Description |
---|---|
We're here |
Add a small utility that adds a stateful memory bank for tracking repeated occurrences of [thing]. This is a small, generic utility that's not specific to error handling, it just remembers how many times you tell it something happened, and it will perform your requested behaviors for certain counts. |
!60537 (merged) | Make Flash messages programmatically dismissable |
!60533 (merged) | When the Notes app is polling for new notes and that request fails, wait to display the error until we're sure the failure isn't transient (it happens twice in a row) |
Screenshots (strongly suggested)
N/A, this is all utility JavaScript code.
Does this MR meet the acceptance criteria?
Conformity
-
📋 Does this MR need a changelog?- [-] I have included a changelog entry.
-
I have not included a changelog entry because this is a new developer-only utility.
- [-] Documentation (if required)
-
Code review guidelines -
Merge request performance guidelines -
Style guides - [-] Database guides
- [-] Separation of EE specific content
Availability and Testing
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process. -
Tested in all supported browsers - [-] Informed Infrastructure department of a default or new setting change, if applicable per definition of done
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
- [-] Label as security and @ mention
@gitlab-com/gl-security/appsec
- [-] The MR includes necessary changes to maintain consistency between UI, API, email, or other methods
- [-] Security reports checked/validated by a reviewer from the AppSec team