Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

[Full changelog](https://github.com/mozilla/glean.js/compare/v0.10.2...main)

* [#202](https://github.com/mozilla/glean.js/pull/202): Add a testing API for the ping type.

# v0.10.2 (2021-04-26)

[Full changelog](https://github.com/mozilla/glean.js/compare/v0.10.1...v0.10.2)
Expand Down
73 changes: 70 additions & 3 deletions glean/src/core/pings/ping_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import collectAndStorePing from "../pings/maker.js";
import type CommonPingData from "./common_ping_data.js";
import { Context } from "../context.js";

type ValidatorFunction = (reason?: string) => Promise<void>;
type PromiseCallback = (value: void | PromiseLike<void>) => void;

/**
* Stores information about a ping.
*
Expand All @@ -20,6 +23,12 @@ class PingType implements CommonPingData {
readonly sendIfEmpty: boolean;
readonly reasonCodes: string[];

// The functions and promises required for the test API to
// execute and synchronize with the submission API.
private resolveTestPromiseFunction?: PromiseCallback;
private rejectTestPromiseFunction?: PromiseCallback;
private testCallback?: ValidatorFunction;

constructor (meta: CommonPingData) {
this.name = meta.name;
this.includeClientId = meta.includeClientId;
Expand All @@ -44,26 +53,84 @@ class PingType implements CommonPingData {
* `ping_info.reason` part of the payload.
*/
submit(reason?: string): void {
// **** Read this before changing the following code! ****
//
// The Dispatcher does not allow dispatched tasks to await on
// other dispatched tasks. Unfortunately, this causes a deadlock.
// In order to work around that problem, we kick off validation
// right before the actual submission takes place, through another
// async function (and not through the dispatcher). After validation
// is complete, regardless of the outcome, the ping submission is
// finally triggered.
if (this.testCallback) {
this.testCallback(reason)
.then(() => {
PingType._private_internalSubmit(this, reason, this.resolveTestPromiseFunction);
})
.catch(e => {
console.error(`There was an error validating "${this.name}" (${reason ?? "no reason"}):`, e);
PingType._private_internalSubmit(this, reason, this.rejectTestPromiseFunction);
});
} else {
PingType._private_internalSubmit(this, reason);
}
}

private static _private_internalSubmit(instance: PingType, reason?: string, testResolver?: PromiseCallback): void {
Context.dispatcher.launch(async () => {
if (!Context.initialized) {
console.info("Glean must be initialized before submitting pings.");
return;
}

if (!Context.uploadEnabled && !this.isDeletionRequest()) {
if (!Context.uploadEnabled && !instance.isDeletionRequest()) {
console.info("Glean disabled: not submitting pings. Glean may still submit the deletion-request ping.");
return;
}

let correctedReason = reason;
if (reason && !this.reasonCodes.includes(reason)) {
if (reason && !instance.reasonCodes.includes(reason)) {
console.error(`Invalid reason code ${reason} from ${this.name}. Ignoring.`);
correctedReason = undefined;
}

const identifier = generateUUIDv4();
await collectAndStorePing(identifier, this, correctedReason);
await collectAndStorePing(identifier, instance, correctedReason);

if (testResolver) {
testResolver();

// Finally clean up!
instance.resolveTestPromiseFunction = undefined;
instance.rejectTestPromiseFunction = undefined;
instance.testCallback = undefined;
}
});
}

/**
* **Test-only API**
*
* Runs a validation function before the ping is collected.
*
* TODO: Only allow this function to be called on test mode (depends on Bug 1682771).
*
* @param callbackFn The asynchronous validation function to run in order to validate
* the ping content.
*
* @returns A `Promise` resolved when the ping is collected and the validation function
* is executed.
*/
async testBeforeNextSubmit(callbackFn: ValidatorFunction): Promise<void> {
if (this.testCallback) {
console.error(`There is an existing test call for ping "${this.name}". Ignoring.`);
return;
}

return new Promise((resolve, reject) => {
this.resolveTestPromiseFunction = resolve;
this.rejectTestPromiseFunction = reject;
this.testCallback = callbackFn;
});
}
}
Expand Down
110 changes: 0 additions & 110 deletions glean/tests/core/pings/index.spec.ts

This file was deleted.

Loading