Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Accumulate Events #65

Merged
merged 41 commits into from
Jan 27, 2023
Merged

feat: Accumulate Events #65

merged 41 commits into from
Jan 27, 2023

Conversation

whizzzkid
Copy link
Collaborator

@whizzzkid whizzzkid commented Jan 19, 2023

Closes: #61

In this PR:

  • implementing accumulating interface
  • Fixed types to accommodate accumulation of events.
  • Accumulated events also accumulate segments.
  • A single type of event can be flushed
  • All events can be flushed.
  • Automatic hookup to beforeunload event to prevent event loss.

Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's in progress but I looked anyway since you checked mine out

Comment on lines 2 to 9
export interface CountlyEventData {
key: string
count: number
sum: number
segmentation: Record<string, string | number>
dur: number
segmentation: Segments
}
interface CountlyEvent {
export interface CountlyEvent {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 1 to 6
import { COUNTLY_SETUP_DEFAULTS } from './config.js'
import { EventAccumulator } from './EventAccumulator.js'

import type { metricFeatures, CountlyWebSdk } from 'countly-sdk-web'
import type { CountlyNodeSdk } from 'countly-sdk-nodejs'
import type { CountlyWebSdk, metricFeatures } from 'countly-sdk-web'
import type { consentTypes, consentTypesExceptAll } from './types/index.js'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should update eslint so it's organizing imports instead of depending on your editor for that! otherwise they'll keep getting out of order ;)

speaking of ordering imports. Can we group the imports? we've currently got two relative imports at the top, then 2 3p imports (countly-sdk-*), then another relative.

*
* @param {string} key - event key
*/
flush (key: string): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we have a flush_all?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added with tests

key: '',
count: 1,
sum: 1,
dur: Date.now(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duration should be an amount of time, not a timestamp. This duration is currently going to be the full time since 1970.

We should probably default this to 0 or even leave it unset entirely.

see

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gets reset before the event gets flushed. setting default to 0.

src/EventAccumulator.ts Outdated Show resolved Hide resolved
timeout: setTimeout(() => {
this.flush(key)
}, this.flushInterval)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
})

}
} else {
// if event is in the store, update the event data.
const { eventData } = this.events[key]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { eventData } = this.events[key]
const { eventData } = this.events.get(key) as eventStore

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

const { eventData } = this.events[key]
eventData.count += count
eventData.sum += 1
eventData.segmentation = { ...eventData.segmentation, ...newEvent.segmentation }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
eventData.segmentation = { ...eventData.segmentation, ...newEvent.segmentation }
eventData.segmentation = { ...eventData.segmentation, ...newEvent.segmentation }
this.events.set(key, eventData)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do I need to set it back? Map.get should get me reference to the object, modifying the object updates the referenced object.

*/
export class EventAccumulator implements IEventAccumulator {
private readonly metricsService: CountlyWebSdk
private readonly events: Record<string, eventStore>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private readonly events: Record<string, eventStore>
private readonly events: Map<string, eventStore>

src/EventAccumulator.ts Outdated Show resolved Hide resolved
Copy link
Collaborator Author

@whizzzkid whizzzkid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explainers

src/EventAccumulator.ts Show resolved Hide resolved
key: '',
count: 1,
sum: 1,
dur: Date.now(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this gets reset before the event gets flushed. setting default to 0.

src/EventAccumulator.ts Outdated Show resolved Hide resolved
this.metricsService.q.push(['add_event', eventData])
clearTimeout(timeout)
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this.events[key]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep

*
* @param {string} key - event key
*/
flush (key: string): void {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added with tests

@whizzzkid whizzzkid marked this pull request as ready for review January 20, 2023 10:25
@whizzzkid whizzzkid changed the title [WIP] feat: Accumulate Events feat: Accumulate Events Jan 21, 2023
.eslintrc.json Outdated Show resolved Hide resolved
.eslintrc.json Show resolved Hide resolved
.eslintrc.json Show resolved Hide resolved
Comment on lines +1 to +9
{
"extensions": ["ts"],
"spec": ["test/**/*.spec.*"],
"require": ["ts-node/register"],
"node-option": [
"experimental-specifier-resolution=node",
"loader=ts-node/esm"
]
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change for running the tests without build should probably be in a separate PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but I started when we did not have any testing in place, apologies, if you want I can spend sometime splitting these, but I hope it's fine for this time?

"spec": ["test/**/*.spec.*"],
"require": ["ts-node/register"],
"node-option": [
"experimental-specifier-resolution=node",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this if we're using ts-node/esm loader?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some reason, it wants both.


accumulator.addEvent(event1)
// adding the second event after 50ms so that the first event is flushed first
await sleep(50)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await sleep(50)
clock.tick(50)

// adding the second event after 50ms so that the first event is flushed first
await sleep(50)
accumulator.addEvent(event2)
await sleep(50)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
await sleep(50)
clock.tick(50)

expect(test2Results.sum).to.be.equal(1)
expect(test2Results.segmentation).to.be.deep.equals({ bar: 'baz' })

expect(countlyStub.add_event.callCount).to.be.equal(2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check before getting args

Suggested change
expect(countlyStub.add_event.callCount).to.be.equal(2)

}

accumulator.addEvent(event1)
// adding the second event after 50ms so that the first event is flushed first
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure I understand this. If the event names are different, why does a 50ms delay ensure the first event is flushed first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because right now events are isolated per key type, i.e. they are flushed per event key and not all of those are flushed together. We can make all events flush at once, but that would break the use case where we only want to flush click events, that would not clear the right timeout and would need to be handled separately for other event types. I can change this to flush everything if you like that better.

package.json Outdated
"test": "run-s test:*",
"test:node": "aegir test -t node -f 'dist/test/node.spec.js'",
"test:node": "aegir test --target node --files 'test/**/*.spec.ts'",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a separate PR.

we will need a way to separate node and browser tests because of the countly import. if you're proposing we just find all tests in node/ then we need to update this

Suggested change
"test:node": "aegir test --target node --files 'test/**/*.spec.ts'",
"test:node": "aegir test --target node --files 'test/node/**/*.spec.ts'",

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tsconfig can have both node and browser types, just wondering if it still be a problem, moving for now.

@whizzzkid
Copy link
Collaborator Author

@SgtPooki thanks for a thorough review, I fixed most of the issues:

👍🏽 = fixed.

Discussions:

Ready for a re-review.

Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the updates, looks great. A few non-blocking comments

this.accumulate = new EventAccumulator(metricsService)

this.storageProvider = storageProvider ?? null
this.metricsService.init(serviceConfig)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea we should. we can handle in a separate PR though.

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need this one here if we're ignoring this rule in tests? but we probably shouldn't disable it completely in all tests.. I'm good with it either way.. eslint can be painful in test code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

expect(telemetry).to.have.property('storageProvider').that.is.not.null

seems to drive eslint crazy, I can look at this later.

@whizzzkid whizzzkid merged commit 2d43252 into main Jan 27, 2023
@github-actions
Copy link

🎉 This PR is included in version 1.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: MetricsProvider should provide interface to accumulate events
2 participants