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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[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.
* [#253](https://github.com/mozilla/glean.js/pull/253): Implement the timespan metric type.

# v0.10.2 (2021-04-26)

Expand Down
3 changes: 1 addition & 2 deletions glean/src/core/glean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { isUndefined, sanitizeApplicationId } from "./utils.js";
import { CoreMetrics } from "./internal_metrics.js";
import EventsDatabase from "./metrics/events_database.js";
import UUIDMetricType from "./metrics/types/uuid.js";
import DatetimeMetricType from "./metrics/types/datetime.js";
import { DatetimeMetric } from "./metrics/types/datetime_metric.js";
import DatetimeMetricType, { DatetimeMetric } from "./metrics/types/datetime.js";
import CorePings from "./internal_pings.js";
import { registerPluginToEvent, testResetEvents } from "./events/utils.js";

Expand Down
6 changes: 3 additions & 3 deletions glean/src/core/metrics/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
import type Store from "../storage/index.js";
import type { MetricType } from "./index.js";
import type { Metric } from "./metric.js";
import { createMetric, validateMetricInternalRepresentation } from "./utils.js";
import type { StorageBuilder } from "../../platform/index.js";
import type { Metrics } from "./index.js";
import type { JSONObject, JSONValue } from "../utils.js";
import { createMetric, validateMetricInternalRepresentation } from "./utils.js";
import { isObject, isUndefined } from "../utils.js";
import type { StorageBuilder } from "../../platform/index.js";
import type { Metrics } from "./metrics_interface";
import { Lifetime } from "./lifetime.js";

/**
Expand Down
36 changes: 9 additions & 27 deletions glean/src/core/metrics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import type { JSONValue } from "../utils.js";
import { isUndefined } from "../utils.js";
import { Metric } from "./metric.js";
import type { Lifetime } from "./lifetime.js";
import type MetricsDatabase from "./database.js";
import { getValidDynamicLabel } from "./types/labeled_utils.js";
import { isUndefined } from "../utils.js";
import { getValidDynamicLabel } from "./types/labeled.js";

export interface Metrics {
[aMetricType: string]: {
[aMetricIdentifier: string]: JSONValue;
};
}


/**
* The common set of data shared across all different metric types.
Expand Down Expand Up @@ -103,27 +109,3 @@ export abstract class MetricType implements CommonMetricData {
return (uploadEnabled && !this.disabled);
}
}

/**
* This is an internal metric representation for labeled metrics.
*
* This can be used to instruct the validators to simply report
* whatever is stored internally, without performing any specific
* validation.
*
* This needs to live here, instead of labeled.ts, in order to avoid
* a cyclic dependency.
*/
export class LabeledMetric extends Metric<JSONValue, JSONValue> {
constructor(v: unknown) {
super(v);
}

validate(v: unknown): v is JSONValue {
return true;
}

payload(): JSONValue {
return this._inner;
}
}
11 changes: 0 additions & 11 deletions glean/src/core/metrics/metrics_interface.ts

This file was deleted.

17 changes: 16 additions & 1 deletion glean/src/core/metrics/types/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,23 @@

import type { CommonMetricData } from "../index.js";
import { MetricType } from "../index.js";
import { BooleanMetric } from "./boolean_metric.js";
import { Context } from "../../context.js";
import { Metric } from "../metric.js";
import { isBoolean } from "../../utils.js";

export class BooleanMetric extends Metric<boolean, boolean> {
constructor(v: unknown) {
super(v);
}

validate(v: unknown): v is boolean {
return isBoolean(v);
}
payload(): boolean {
return this._inner;
}
}


/**
* A boolean metric.
Expand Down
19 changes: 0 additions & 19 deletions glean/src/core/metrics/types/boolean_metric.ts

This file was deleted.

28 changes: 25 additions & 3 deletions glean/src/core/metrics/types/counter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,33 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import type { CommonMetricData } from "../index.js";
import { MetricType } from "../index.js";
import type { JSONValue } from "../../utils.js";
import { isUndefined } from "../../utils.js";
import { CounterMetric } from "./counter_metric.js";
import { MetricType } from "../index.js";
import { isUndefined, isNumber } from "../../utils.js";
import { Context } from "../../context.js";
import { Metric } from "../metric.js";

export class CounterMetric extends Metric<number, number> {
constructor(v: unknown) {
super(v);
}

validate(v: unknown): v is number {
if (!isNumber(v)) {
return false;
}

if (v <= 0) {
return false;
}

return true;
}

payload(): number {
return this._inner;
}
}

/**
* A counter metric.
Expand Down
28 changes: 0 additions & 28 deletions glean/src/core/metrics/types/counter_metric.ts

This file was deleted.

152 changes: 150 additions & 2 deletions glean/src/core/metrics/types/datetime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,157 @@
import type { CommonMetricData } from "../index.js";
import { MetricType } from "../index.js";
import TimeUnit from "../../metrics/time_unit.js";
import type { DatetimeInternalRepresentation} from "./datetime_metric.js";
import { DatetimeMetric } from "./datetime_metric.js";
import { Context } from "../../context.js";
import { Metric } from "../metric.js";
import { isNumber, isObject, isString } from "../../utils.js";

/**
* Builds the formatted timezone offset string frim a given timezone.
*
* The format of the resulting string is `+02:00`.
*
* @param timezone A number representing the timezone offset to format,
* this is expected to be in minutes.
*
* @returns The formatted timezone offset string.
*/
export function formatTimezoneOffset(timezone: number): string {
const offset = (timezone / 60) * -1;
const sign = offset > 0 ? "+" : "-";
const hours = Math.abs(offset).toString().padStart(2, "0");
return `${sign}${hours}:00`;
}

export type DatetimeInternalRepresentation = {
// The time unit of the metric type at the time of recording.
timeUnit: TimeUnit,
// This timezone should be the exact output of `Date.getTimezoneOffset`
// and as such it should alwaye be in minutes.
timezone: number,
// This date string should be the exact output of `Date.toISOString`
// and as such it is always in UTC.
date: string,
};

export class DatetimeMetric extends Metric<DatetimeInternalRepresentation, string> {
constructor(v: unknown) {
super(v);
}

static fromDate(v: Date, timeUnit: TimeUnit): DatetimeMetric {
return new DatetimeMetric({
timeUnit,
timezone: v.getTimezoneOffset(),
date: v.toISOString()
});
}

/**
* Gets the datetime data as a Date object.
*
* # Note
*
* The object created here will be relative to local time.
* If the timezone at the time of recording is different,
* the timezone offset will be applied before transforming to an object.
*
* @returns A date object.
*/
get date(): Date {
return new Date(this._inner.date);
}

private get timezone(): number {
return this._inner.timezone;
}

private get timeUnit(): TimeUnit {
return this._inner.timeUnit;
}

private get dateISOString(): string {
return this._inner.date;
}

validate(v: unknown): v is DatetimeInternalRepresentation {
if (!isObject(v) || Object.keys(v).length !== 3) {
return false;
}

const timeUnitVerification = "timeUnit" in v && isString(v.timeUnit) && Object.values(TimeUnit).includes(v.timeUnit as TimeUnit);
const timezoneVerification = "timezone" in v && isNumber(v.timezone);
const dateVerification = "date" in v && isString(v.date) && v.date.length === 24 && !isNaN(Date.parse(v.date));
if (!timeUnitVerification || !timezoneVerification || !dateVerification) {
return false;
}

return true;
}

/**
* Gets this metrics value in its payload representation.
*
* For this metric, the payload is the timezone aware ISO date string truncated to the time unit
* given at the time of recording.
*
* # Note
*
* The timezone of the final string is the timezone at the time of recording.
*
* @returns The metric value.
*/
payload(): string {
const extractedDateInfo = this.dateISOString.match(/\d+/g);
if (!extractedDateInfo || extractedDateInfo.length < 0) {
// This is impossible because the date is always validated before setting
throw new Error("IMPOSSIBLE: Unable to extract date information from DatetimeMetric.");
}
const correctedDate = new Date(
/* year */ parseInt(extractedDateInfo[0]),
/* month */ parseInt(extractedDateInfo[1]) - 1,
/* day */ parseInt(extractedDateInfo[2]),
/* hour */ parseInt(extractedDateInfo[3]) - (this.timezone / 60),
/* minute */ parseInt(extractedDateInfo[4]),
/* second */ parseInt(extractedDateInfo[5]),
/* millisecond */ parseInt(extractedDateInfo[6])
);

const timezone = formatTimezoneOffset(this.timezone);
const year = correctedDate.getFullYear().toString().padStart(2, "0");
// `Date.prototype.getMonth` returns the month starting at 0.
const month = (correctedDate.getMonth() + 1).toString().padStart(2, "0");
const day = correctedDate.getDate().toString().padStart(2, "0");
if (this.timeUnit === TimeUnit.Day) {
return `${year}-${month}-${day}${timezone}`;
}

const hours = correctedDate.getHours().toString().padStart(2, "0");
if (this.timeUnit === TimeUnit.Hour) {
return `${year}-${month}-${day}T${hours}${timezone}`;
}

const minutes = correctedDate.getMinutes().toString().padStart(2, "0");
if (this.timeUnit === TimeUnit.Minute) {
return `${year}-${month}-${day}T${hours}:${minutes}${timezone}`;
}

const seconds = correctedDate.getSeconds().toString().padStart(2, "0");
if (this.timeUnit === TimeUnit.Second) {
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${timezone}`;
}

const milliseconds = correctedDate.getMilliseconds().toString().padStart(3, "0");
if (this.timeUnit === TimeUnit.Millisecond) {
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}${timezone}`;
}

if (this.timeUnit === TimeUnit.Microsecond) {
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}000${timezone}`;
}

return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}000000${timezone}`;
}
}

/**
* A datetime metric.
Expand Down
Loading