Skip to content

Commit d5737dc

Browse files
committed
Add daily limits support for events (1 startup/day)
Signed-off-by: Fred Bricon <fbricon@gmail.com>
1 parent 82034e5 commit d5737dc

File tree

10 files changed

+137
-17
lines changed

10 files changed

+137
-17
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ Starting with 0.6.1, you can configure ratios on included events, meaning X% of
4242
"refresh": "12h",
4343
"includes": [
4444
{
45-
"name" : "*"
45+
"name" : "startup",
46+
"dailyLimit": "1" // Limit to 1 event per day per extension
47+
},
48+
{
49+
"name" : "*" // Always put wildcard patterns last in the array, to ensure other events are included
4650
}
4751
]
4852
},

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@redhat-developer/vscode-redhat-telemetry",
3-
"version": "0.7.1",
3+
"version": "0.8.0",
44
"description": "Provides Telemetry APIs for Red Hat applications",
55
"main": "lib/index.js",
66
"types": "lib",

src/common/impl/configuration.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import * as picomatch from "picomatch";
22
import { isError } from "../utils/events";
33
import { numValue } from "../utils/hashcode";
44
import { AnalyticsEvent } from "../api/analyticsEvent";
5-
import { Logger } from "../utils/logger";
65

76
interface EventNamePattern {
87
name: string;
98
ratio?: string;
9+
dailyLimit?: string;
1010
}
1111

1212
interface PropertyPattern {
@@ -43,11 +43,27 @@ export class Configuration {
4343
return false;
4444
}
4545

46-
const isIncluded = this.isIncluded(event, currUserRatioValue) && !this.isExcluded(event, currUserRatioValue);
47-
46+
const isIncluded = this.isIncluded(event, currUserRatioValue)
47+
&& !this.isExcluded(event, currUserRatioValue)
4848
return isIncluded;
4949
}
5050

51+
public getDailyLimit(event: AnalyticsEvent): number {
52+
const includes = this.getIncludePatterns();
53+
if (includes.length) {
54+
const pattern = includes.filter(isEventNamePattern).map(p => p as EventNamePattern)
55+
.find(p => picomatch.isMatch(event.event, p.name))
56+
if (pattern?.dailyLimit) {
57+
try {
58+
return parseInt(pattern.dailyLimit);
59+
} catch(e) {
60+
// ignore
61+
}
62+
}
63+
}
64+
return Number.MAX_VALUE;
65+
}
66+
5167
isIncluded(event: AnalyticsEvent, currUserRatioValue: number): boolean {
5268
const includes = this.getIncludePatterns();
5369
if (includes.length) {
@@ -72,7 +88,7 @@ export class Configuration {
7288
}
7389

7490
getExcludePatterns(): EventPattern[] {
75-
if (this.json.excludes) {
91+
if (this.json?.excludes) {
7692
return this.json.excludes as EventPattern[];
7793
}
7894
return [];
@@ -121,11 +137,16 @@ function getRatio(ratioAsString ?:string): number {
121137
return 1.0;
122138
}
123139

124-
125-
126140
function isPropertyPattern(event: EventPattern): event is PropertyPattern {
127141
if ((event as PropertyPattern).property) {
128142
return true
129143
}
130144
return false
145+
}
146+
147+
function isEventNamePattern(event: EventPattern): event is EventNamePattern {
148+
if ((event as EventNamePattern).name) {
149+
return true
150+
}
151+
return false
131152
}

src/common/impl/eventTracker.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Memento } from "vscode";
2+
import { AnalyticsEvent } from '../api/analyticsEvent';
3+
4+
5+
interface EventTracking {
6+
lastUpdated: number;
7+
count: number;
8+
}
9+
10+
export class EventTracker {
11+
12+
constructor(private globalState: Memento) { }
13+
14+
async storeEventCount(payload: AnalyticsEvent, newCount: number): Promise<void> {
15+
const newTracking = {
16+
count: newCount,
17+
lastUpdated: this.getTodaysTimestamp()
18+
} as EventTracking;
19+
return this.globalState.update(this.getEventTrackingKey(payload.event), newTracking);
20+
}
21+
22+
async getEventCount(payload: AnalyticsEvent): Promise<number> {
23+
const eventTracking = this.globalState.get<EventTracking>(this.getEventTrackingKey(payload.event));
24+
if (eventTracking) {
25+
//Check if eventTracking timestamp is older than today
26+
let today = this.getTodaysTimestamp();
27+
let lastEventDay = eventTracking.lastUpdated;
28+
//check if now and lastEventTime are in the same day
29+
if (lastEventDay === today) {
30+
return eventTracking.count;
31+
}
32+
// new day, reset count
33+
}
34+
return 0;
35+
}
36+
37+
private getTodaysTimestamp(): number {
38+
const now = new Date();
39+
now.setHours(0, 0, 0, 0);
40+
return now.getTime();
41+
}
42+
43+
private getEventTrackingKey(eventName: string): string {
44+
//replace all non alphanumeric characters with a _
45+
const key = eventName.replace(/[^a-zA-Z0-9]/g, '_');
46+
return `telemetry.events.tracking.${key}`;
47+
}
48+
}
49+

src/common/impl/telemetryServiceImpl.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,26 @@ import { IdProvider } from '../api/idProvider';
77
import { Environment } from '../api/environment';
88
import { transform, isError } from '../utils/events';
99
import { IReporter } from '../api/reporter';
10+
import { EventTracker } from './eventTracker';
11+
import { Memento } from 'vscode';
1012

1113
/**
1214
* Implementation of a `TelemetryService`
1315
*/
1416
export class TelemetryServiceImpl implements TelemetryService {
1517
private startTime: number;
18+
private eventTracker: EventTracker;
1619

17-
constructor(private reporter: IReporter,
20+
constructor(globalState: Memento,
21+
private reporter: IReporter,
1822
private queue: TelemetryEventQueue | undefined,
1923
private settings: TelemetrySettings,
2024
private idManager: IdProvider,
2125
private environment: Environment,
22-
private configurationManager?: ConfigurationManager) {
26+
private configurationManager?: ConfigurationManager
27+
) {
2328
this.startTime = this.getCurrentTimeInSeconds();
29+
this.eventTracker = new EventTracker(globalState);
2430
}
2531

2632
/*
@@ -64,7 +70,25 @@ export class TelemetryServiceImpl implements TelemetryService {
6470
//Check against Extension configuration
6571
const config = await this.configurationManager?.getExtensionConfiguration();
6672
if (!config || config.canSend(payload)) {
67-
return this.reporter.report(payload);
73+
74+
const dailyLimit = (config)?config.getDailyLimit(payload):Number.MAX_VALUE;
75+
let count = 0;
76+
if (dailyLimit < Number.MAX_VALUE) {
77+
//find currently stored count
78+
count = await this.eventTracker.getEventCount(payload);
79+
if (count >= dailyLimit){
80+
//daily limit reached, do not send event
81+
Logger.log(`Daily limit reached for ${event.name}: ${dailyLimit}`);
82+
return;
83+
}
84+
}
85+
return this.reporter.report(payload).then(()=>{
86+
if (dailyLimit < Number.MAX_VALUE) {
87+
//update count
88+
Logger.log(`Storing event count (${count+1}/${dailyLimit}) for ${event.name}`);
89+
return this.eventTracker.storeEventCount(payload, count+1);
90+
}
91+
});
6892
}
6993
}
7094

@@ -87,9 +111,8 @@ export class TelemetryServiceImpl implements TelemetryService {
87111
return this.reporter.closeAndFlush();
88112
}
89113

90-
91114
private getCurrentTimeInSeconds(): number {
92115
const now = Date.now();
93116
return Math.floor(now/1000);
94117
}
95-
}
118+
}

src/common/telemetryServiceBuilder.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import { TelemetryService } from './api/telemetry';
55
import { TelemetryServiceImpl } from './impl/telemetryServiceImpl';
66
import { TelemetryEventQueue } from './impl/telemetryEventQueue';
77
import { TelemetrySettings } from './api/settings';
8-
import { getSegmentKey } from './utils/keyLocator';
98
import { getExtensionId } from './utils/extensions';
10-
import { CacheService } from './api/cacheService';
119
import { ConfigurationManager } from './impl/configurationManager';
10+
import { ExtensionContext } from 'vscode';
1211

1312
/**
1413
* `TelemetryService` builder
@@ -20,6 +19,7 @@ export class TelemetryServiceBuilder {
2019
private environment?: Environment;
2120
private configurationManager?: ConfigurationManager;
2221
private reporter?: IReporter;
22+
private context?:ExtensionContext;
2323

2424
constructor(packageJson?: any) {
2525
this.packageJson = packageJson;
@@ -55,6 +55,11 @@ export class TelemetryServiceBuilder {
5555
return this;
5656
}
5757

58+
public setContext(context: ExtensionContext): TelemetryServiceBuilder {
59+
this.context = context;
60+
return this;
61+
}
62+
5863
public async build(): Promise<TelemetryService> {
5964
this.validate();
6065
if (!this.environment) {
@@ -76,10 +81,13 @@ export class TelemetryServiceBuilder {
7681
const queue = this.settings!.isTelemetryConfigured()
7782
? undefined
7883
: new TelemetryEventQueue();
79-
return new TelemetryServiceImpl(this.reporter!, queue, this.settings!, this.idProvider!, this.environment!, this.configurationManager);
84+
return new TelemetryServiceImpl(this.context?.globalState!, this.reporter!, queue, this.settings!, this.idProvider!, this.environment!, this.configurationManager);
8085
}
8186

8287
private validate() {
88+
if (!this.context) {
89+
throw new Error('context is not set');
90+
}
8391
if (!this.idProvider) {
8492
throw new Error('idProvider is not set');
8593
}

src/config/telemetry-config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"enabled":"all",
44
"refresh": "12h",
55
"includes": [
6+
{
7+
"name" : "startup",
8+
"dailyLimit": 1
9+
},
610
{
711
"name" : "*"
812
}
@@ -17,6 +21,15 @@
1721
"redhat.java": {
1822
"enabled": "all",
1923
"ratio": "0.5",
24+
"includes": [
25+
{
26+
"name" : "startup",
27+
"dailyLimit": 1000
28+
},
29+
{
30+
"name" : "*"
31+
}
32+
],
2033
"excludes": [
2134
{
2235
"name": "textCompletion",

src/node/redHatServiceNodeProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class RedHatServiceNodeProvider extends AbstractRedHatServiceProvider {
1919
const reporter = new Reporter(this.getSegmentApi(packageJson), new EventCacheService(storageService));
2020
const idManager = IdManagerFactory.getIdManager();
2121
const builder = new TelemetryServiceBuilder(packageJson)
22+
.setContext(this.context)
2223
.setSettings(this.settings)
2324
.setIdProvider(idManager)
2425
.setReporter(reporter)

src/webworker/redHatServiceWebWorkerProvider.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class RedHatServiceWebWorkerProvider extends AbstractRedHatServiceProvide
1919
const reporter = new Reporter(this.getSegmentApi(packageJson), new EventCacheService(storageService));
2020
const idManager = new VFSSystemIdProvider(storageService);
2121
const builder = new TelemetryServiceBuilder(packageJson)
22+
.setContext(this.context)
2223
.setSettings(this.settings)
2324
.setIdProvider(idManager)
2425
.setReporter(reporter)

0 commit comments

Comments
 (0)