Skip to content

Commit 653ec37

Browse files
authored
Merge pull request #1728 from rosahbruno/1839934-data-migration
2 parents 1cb0adc + 9e98e74 commit 653ec37

File tree

2 files changed

+134
-3
lines changed

2 files changed

+134
-3
lines changed

glean/src/core/internal_metrics/sync.ts

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,25 @@ export class CoreMetricsSync {
126126
}
127127

128128
initialize(): void {
129-
this.initializeClientId();
130-
this.initializeFirstRunDate();
129+
// If the client had used previous versions of Glean.js before we moved
130+
// to LocalStorage as the data store, then we need to move important
131+
// user data from IndexedDB to LocalStorage.
132+
//
133+
// Currently we are interested in migrating two things
134+
// 1. The client_id - consistent clientId across all sessions.
135+
// 2. The first_run_date - the date when the client was first run.
136+
137+
// The migration is done only once per client. The flag is set in
138+
// LocalStorage to indicate that the migration has been completed.
139+
const migrationFlag = localStorage.getItem("GLEAN_MIGRATION_FLAG");
140+
if (migrationFlag !== "1") {
141+
this.migrateCoreMetricsFromIdb();
142+
localStorage.setItem("GLEAN_MIGRATION_FLAG", "1");
143+
} else {
144+
// If we do not need to migrate anything, then we can set the metrics
145+
// for the first time.
146+
this.initializeUserLifetimeMetrics();
147+
}
131148

132149
this.os.set((Context.platform as PlatformSync).info.os());
133150
this.osVersion.set((Context.platform as PlatformSync).info.osVersion(Context.config.osVersion));
@@ -187,4 +204,80 @@ export class CoreMetricsSync {
187204
this.firstRunDate.set();
188205
}
189206
}
207+
208+
/**
209+
* Initializes the Glean internal user-lifetime metrics.
210+
*/
211+
private initializeUserLifetimeMetrics(): void {
212+
this.initializeClientId();
213+
this.initializeFirstRunDate();
214+
}
215+
216+
/**
217+
* Migrates the core metrics from the old IDB storage to LocalStorage
218+
* on first launch IF the client had used previous versions of Glean.js -
219+
* pre LocalStorage.
220+
*
221+
* There is no way to access IDB data synchronously, so we rely on listeners
222+
* for when specific actions complete. This means that we need to be careful
223+
* and ensure that the clientId and firstRunDate are always set.
224+
*
225+
* Once the migration is complete, running the initialize functions for the
226+
* clientId and firstRunDate equate to no-ops. If there is an error anywhere
227+
* along the way and the migration is not complete, then the initialize the
228+
* two metrics as usual.
229+
*/
230+
private migrateCoreMetricsFromIdb(): void {
231+
const dbRequest = window.indexedDB.open("Glean");
232+
dbRequest.onerror = () => {
233+
this.initializeUserLifetimeMetrics();
234+
};
235+
236+
dbRequest.onsuccess = () => {
237+
try {
238+
const db = dbRequest.result;
239+
const transaction = db?.transaction("Main", "readwrite");
240+
const store = transaction.objectStore("Main");
241+
242+
// All the core metrics are stored in the userLifetimeMetrics object.
243+
const req = store.get("userLifetimeMetrics");
244+
req.onsuccess = () => {
245+
// Pull and set the existing clientId.
246+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
247+
const clientId = req.result?.["glean_client_info"]?.["uuid"]?.["client_id"] as string;
248+
if (!!clientId) {
249+
this.clientId.set(clientId);
250+
} else {
251+
this.initializeClientId();
252+
}
253+
254+
// Pull and set the existing firstRunDate.
255+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
256+
const firstRunDate = req.result?.["glean_client_info"]?.["datetime"]?.[
257+
"first_run_date"
258+
] as {
259+
date: string;
260+
timezone: number;
261+
timeUnit: TimeUnit;
262+
};
263+
if (!!firstRunDate) {
264+
this.firstRunDate.setSyncRaw(
265+
firstRunDate.date,
266+
firstRunDate.timezone,
267+
firstRunDate.timeUnit
268+
);
269+
} else {
270+
this.initializeFirstRunDate();
271+
}
272+
};
273+
274+
req.onerror = () => {
275+
this.initializeUserLifetimeMetrics();
276+
};
277+
} catch (e) {
278+
// Fail-safe in case any generic error occurs.
279+
this.initializeUserLifetimeMetrics();
280+
}
281+
};
282+
}
190283
}

glean/src/core/metrics/types/datetime.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export type DatetimeInternalRepresentation = {
3535
// The time unit of the metric type at the time of recording.
3636
timeUnit: TimeUnit;
3737
// This timezone should be the exact output of `Date.getTimezoneOffset`
38-
// and as such it should alwaye be in minutes.
38+
// and as such it should always be in minutes.
3939
timezone: number;
4040
// This date string should be the exact output of `Date.toISOString`
4141
// and as such it is always in UTC.
@@ -59,6 +59,18 @@ export class DatetimeMetric extends Metric<DatetimeInternalRepresentation, strin
5959
});
6060
}
6161

62+
static fromRawDatetime(
63+
isoString: string,
64+
timezoneOffset: number,
65+
timeUnit: TimeUnit
66+
): DatetimeMetric {
67+
return new DatetimeMetric({
68+
timeUnit,
69+
timezone: timezoneOffset,
70+
date: isoString
71+
});
72+
}
73+
6274
/**
6375
* Gets the datetime data as a Date object.
6476
*
@@ -282,6 +294,32 @@ export class InternalDatetimeMetricType extends MetricType {
282294
}
283295
}
284296

297+
/**
298+
* Set a datetime metric from raw values.
299+
*
300+
* # Important
301+
* This method should **never** be exposed to users. This is used solely
302+
* for migrating IDB data to LocalStorage.
303+
*
304+
* @param isoString Raw isoString.
305+
* @param timezone Raw timezone.
306+
* @param timeUnit Raw timeUnit.
307+
*/
308+
setSyncRaw(isoString: string, timezone: number, timeUnit: TimeUnit) {
309+
if (!this.shouldRecord(Context.uploadEnabled)) {
310+
return;
311+
}
312+
313+
try {
314+
const metric = DatetimeMetric.fromRawDatetime(isoString, timezone, timeUnit);
315+
(Context.metricsDatabase as MetricsDatabaseSync).record(this, metric);
316+
} catch (e) {
317+
if (e instanceof MetricValidationError) {
318+
e.recordErrorSync(this);
319+
}
320+
}
321+
}
322+
285323
/// TESTING ///
286324
/**
287325
* Test-only API

0 commit comments

Comments
 (0)