feat(shorebird_code_push_protocol): patch install/download metric envelope (#3824)

This commit is contained in:
Mac
2026-06-23 12:25:09 -06:00
committed by GitHub
parent e7aa48009b
commit 851f1148f3
27 changed files with 1269 additions and 0 deletions
@@ -31,6 +31,7 @@ export 'package:shorebird_code_push_protocol/src/messages/get_organization_apps/
export 'package:shorebird_code_push_protocol/src/messages/get_organization_users/get_organization_users_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_organizations_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_patch_adoption/get_patch_adoption_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_patch_metric_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_release/get_release_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_release_artifacts/get_release_artifacts_response.dart';
export 'package:shorebird_code_push_protocol/src/messages/get_release_patches/get_release_patches_response.dart';
@@ -48,7 +49,15 @@ export 'package:shorebird_code_push_protocol/src/models/app.dart';
export 'package:shorebird_code_push_protocol/src/models/app_collaborator_role.dart';
export 'package:shorebird_code_push_protocol/src/models/app_metadata.dart';
export 'package:shorebird_code_push_protocol/src/models/channel.dart';
export 'package:shorebird_code_push_protocol/src/models/get_app_patch_downloads_parameter2.dart';
export 'package:shorebird_code_push_protocol/src/models/get_app_patch_downloads_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/get_app_patch_installs_parameter2.dart';
export 'package:shorebird_code_push_protocol/src/models/get_app_patch_installs_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/get_patch_adoption_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/get_release_patch_downloads_parameter2.dart';
export 'package:shorebird_code_push_protocol/src/models/get_release_patch_downloads_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/get_release_patch_installs_parameter2.dart';
export 'package:shorebird_code_push_protocol/src/models/get_release_patch_installs_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/get_unique_users_parameter2.dart';
export 'package:shorebird_code_push_protocol/src/models/get_unique_users_parameter3.dart';
export 'package:shorebird_code_push_protocol/src/models/latest_release.dart';
@@ -62,6 +71,10 @@ export 'package:shorebird_code_push_protocol/src/models/patch_adoption_entry.dar
export 'package:shorebird_code_push_protocol/src/models/patch_adoption_point.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_artifact.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_check_metadata.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_metric_breakdown_entry.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_metric_current_window.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_metric_time_series_entry.dart';
export 'package:shorebird_code_push_protocol/src/models/patch_metric_window.dart';
export 'package:shorebird_code_push_protocol/src/models/pending_release.dart';
export 'package:shorebird_code_push_protocol/src/models/private_user.dart';
export 'package:shorebird_code_push_protocol/src/models/public_user.dart';
@@ -0,0 +1,104 @@
import 'package:meta/meta.dart';
import 'package:shorebird_code_push_protocol/model_helpers.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_current_window.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_window.dart';
/// {@template get_patch_metric_response}
/// The response body for the patch-installs and patch-downloads metric
/// endpoints (app- and release-scoped): a current/previous envelope.
/// `previous` covers the equal-length window immediately preceding
/// `current`; period-over-period deltas are client display logic over the
/// two totals. `previous` is omitted only when the prior window predates
/// the data floor (no comparison data exists). When it reaches past the
/// plan's metrics-history horizon, `previous` is still present with its
/// total — the delta renders — but without a `time_series` (no
/// prior-window overlay): granular history is the resolution the horizon
/// gates, the scalar comparison is not.
/// {@endtemplate}
@immutable
class GetPatchMetricResponse {
/// {@macro get_patch_metric_response}
const GetPatchMetricResponse({
required this.asOf,
required this.granularity,
required this.current,
this.previous,
});
/// Converts a `Map<String, dynamic>` to a [GetPatchMetricResponse].
factory GetPatchMetricResponse.fromJson(Map<String, dynamic> json) {
return parseFromJson(
'GetPatchMetricResponse',
json,
() => GetPatchMetricResponse(
asOf: DateTime.parse(json['as_of'] as String),
granularity: checkedKey(json, 'granularity') as String?,
current: PatchMetricCurrentWindow.fromJson(
json['current'] as Map<String, dynamic>,
),
previous: PatchMetricWindow.maybeFromJson(
json['previous'] as Map<String, dynamic>?,
),
),
);
}
/// Convenience to create a nullable type from a nullable json object.
/// Useful when parsing optional fields.
static GetPatchMetricResponse? maybeFromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
return GetPatchMetricResponse.fromJson(json);
}
/// Server's UTC timestamp at the moment the response was constructed.
/// Not a freshness indicator for the underlying data, which is
/// refreshed by an hourly scheduled job and may lag by up to ~1 hour.
final DateTime asOf;
/// The time-series bucket resolution (`hour`, `day`, or `week`), or
/// null when no time series was requested. Applies to both windows.
final String? granularity;
/// The `current` window of the patch-metric envelope: the base window atom
/// plus the optional `breakdown`. Only `current` carries a breakdown — no
/// chart renders a previous-window breakdown, so the asymmetry is declared
/// in the contract rather than left as an optional-but-never-populated
/// field.
final PatchMetricCurrentWindow current;
/// One window of the patch-metric envelope: the summed count over the
/// window's effective range, with a per-bucket series when a `granularity`
/// was requested. This base atom is the full shape of `previous`;
/// `current` extends it (see PatchMetricCurrentWindow).
final PatchMetricWindow? previous;
/// Converts a [GetPatchMetricResponse] to a `Map<String, dynamic>`.
Map<String, dynamic> toJson() {
return {
'as_of': asOf.toIso8601String(),
'granularity': granularity,
'current': current.toJson(),
'previous': previous?.toJson(),
};
}
@override
int get hashCode => Object.hashAll([
asOf,
granularity,
current,
previous,
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is GetPatchMetricResponse &&
asOf == other.asOf &&
granularity == other.granularity &&
current == other.current &&
previous == other.previous;
}
}
@@ -0,0 +1,37 @@
enum GetAppPatchDownloadsParameter2 {
hour._('hour'),
day._('day'),
week._('week');
const GetAppPatchDownloadsParameter2._(this.value);
/// Creates a GetAppPatchDownloadsParameter2 from a json value.
factory GetAppPatchDownloadsParameter2.fromJson(String json) {
return GetAppPatchDownloadsParameter2.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetAppPatchDownloadsParameter2 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetAppPatchDownloadsParameter2? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetAppPatchDownloadsParameter2.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,35 @@
enum GetAppPatchDownloadsParameter3 {
release._('release');
const GetAppPatchDownloadsParameter3._(this.value);
/// Creates a GetAppPatchDownloadsParameter3 from a json value.
factory GetAppPatchDownloadsParameter3.fromJson(String json) {
return GetAppPatchDownloadsParameter3.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetAppPatchDownloadsParameter3 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetAppPatchDownloadsParameter3? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetAppPatchDownloadsParameter3.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,37 @@
enum GetAppPatchInstallsParameter2 {
hour._('hour'),
day._('day'),
week._('week');
const GetAppPatchInstallsParameter2._(this.value);
/// Creates a GetAppPatchInstallsParameter2 from a json value.
factory GetAppPatchInstallsParameter2.fromJson(String json) {
return GetAppPatchInstallsParameter2.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetAppPatchInstallsParameter2 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetAppPatchInstallsParameter2? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetAppPatchInstallsParameter2.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,35 @@
enum GetAppPatchInstallsParameter3 {
release._('release');
const GetAppPatchInstallsParameter3._(this.value);
/// Creates a GetAppPatchInstallsParameter3 from a json value.
factory GetAppPatchInstallsParameter3.fromJson(String json) {
return GetAppPatchInstallsParameter3.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetAppPatchInstallsParameter3 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetAppPatchInstallsParameter3? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetAppPatchInstallsParameter3.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,37 @@
enum GetReleasePatchDownloadsParameter2 {
hour._('hour'),
day._('day'),
week._('week');
const GetReleasePatchDownloadsParameter2._(this.value);
/// Creates a GetReleasePatchDownloadsParameter2 from a json value.
factory GetReleasePatchDownloadsParameter2.fromJson(String json) {
return GetReleasePatchDownloadsParameter2.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetReleasePatchDownloadsParameter2 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetReleasePatchDownloadsParameter2? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetReleasePatchDownloadsParameter2.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,35 @@
enum GetReleasePatchDownloadsParameter3 {
patch._('patch');
const GetReleasePatchDownloadsParameter3._(this.value);
/// Creates a GetReleasePatchDownloadsParameter3 from a json value.
factory GetReleasePatchDownloadsParameter3.fromJson(String json) {
return GetReleasePatchDownloadsParameter3.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetReleasePatchDownloadsParameter3 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetReleasePatchDownloadsParameter3? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetReleasePatchDownloadsParameter3.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,37 @@
enum GetReleasePatchInstallsParameter2 {
hour._('hour'),
day._('day'),
week._('week');
const GetReleasePatchInstallsParameter2._(this.value);
/// Creates a GetReleasePatchInstallsParameter2 from a json value.
factory GetReleasePatchInstallsParameter2.fromJson(String json) {
return GetReleasePatchInstallsParameter2.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetReleasePatchInstallsParameter2 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetReleasePatchInstallsParameter2? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetReleasePatchInstallsParameter2.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,35 @@
enum GetReleasePatchInstallsParameter3 {
patch._('patch');
const GetReleasePatchInstallsParameter3._(this.value);
/// Creates a GetReleasePatchInstallsParameter3 from a json value.
factory GetReleasePatchInstallsParameter3.fromJson(String json) {
return GetReleasePatchInstallsParameter3.values.firstWhere(
(value) => value.value == json,
orElse: () => throw FormatException(
'Unknown GetReleasePatchInstallsParameter3 value: $json',
),
);
}
/// Convenience to create a nullable type from a nullable json value.
/// Useful when parsing optional fields.
static GetReleasePatchInstallsParameter3? maybeFromJson(String? json) {
if (json == null) {
return null;
}
return GetReleasePatchInstallsParameter3.fromJson(json);
}
/// The value of the enum. This is the exact value
/// from the OpenAPI spec and will be used for network transport.
final String value;
/// Converts the enum to its json value.
String toJson() => value;
/// Returns the string form of the enum.
@override
String toString() => value;
}
@@ -0,0 +1,90 @@
import 'package:meta/meta.dart';
import 'package:shorebird_code_push_protocol/model_helpers.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_time_series_entry.dart';
/// {@template patch_metric_breakdown_entry}
/// A patch metric for one value of the `group_by` dimension (one release
/// at app scope, or one patch at release scope), optionally with its own
/// time series.
/// {@endtemplate}
@immutable
class PatchMetricBreakdownEntry {
/// {@macro patch_metric_breakdown_entry}
const PatchMetricBreakdownEntry({
required this.groupBy,
required this.groupValue,
required this.count,
this.timeSeries,
});
/// Converts a `Map<String, dynamic>` to a [PatchMetricBreakdownEntry].
factory PatchMetricBreakdownEntry.fromJson(Map<String, dynamic> json) {
return parseFromJson(
'PatchMetricBreakdownEntry',
json,
() => PatchMetricBreakdownEntry(
groupBy: json['group_by'] as String,
groupValue: json['group_value'] as String,
count: json['count'] as int,
timeSeries: (json['time_series'] as List?)
?.map<PatchMetricTimeSeriesEntry>(
(e) => PatchMetricTimeSeriesEntry.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
),
);
}
/// Convenience to create a nullable type from a nullable json object.
/// Useful when parsing optional fields.
static PatchMetricBreakdownEntry? maybeFromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
return PatchMetricBreakdownEntry.fromJson(json);
}
/// The dimension this entry breaks down by ("release" or "patch").
final String groupBy;
/// The value within `group_by`: the release version, or the patch
/// number as a string.
final String groupValue;
/// The summed count for this group over the window.
final int count;
/// Per-bucket series for this group, present only when a `granularity`
/// was requested; otherwise null.
final List<PatchMetricTimeSeriesEntry>? timeSeries;
/// Converts a [PatchMetricBreakdownEntry] to a `Map<String, dynamic>`.
Map<String, dynamic> toJson() {
return {
'group_by': groupBy,
'group_value': groupValue,
'count': count,
'time_series': timeSeries?.map((e) => e.toJson()).toList(),
};
}
@override
int get hashCode => Object.hashAll([
groupBy,
groupValue,
count,
listHash(timeSeries),
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PatchMetricBreakdownEntry &&
groupBy == other.groupBy &&
groupValue == other.groupValue &&
count == other.count &&
listsEqual(timeSeries, other.timeSeries);
}
}
@@ -0,0 +1,108 @@
import 'package:meta/meta.dart';
import 'package:shorebird_code_push_protocol/model_helpers.dart';
import 'package:shorebird_code_push_protocol/src/models/metrics_range.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_breakdown_entry.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_time_series_entry.dart';
/// {@template patch_metric_current_window}
/// The `current` window of the patch-metric envelope: the base window atom
/// plus the optional `breakdown`. Only `current` carries a breakdown — no
/// chart renders a previous-window breakdown, so the asymmetry is declared
/// in the contract rather than left as an optional-but-never-populated
/// field.
/// {@endtemplate}
@immutable
class PatchMetricCurrentWindow {
/// {@macro patch_metric_current_window}
const PatchMetricCurrentWindow({
required this.count,
required this.range,
this.timeSeries,
this.breakdown,
});
/// Converts a `Map<String, dynamic>` to a [PatchMetricCurrentWindow].
factory PatchMetricCurrentWindow.fromJson(Map<String, dynamic> json) {
return parseFromJson(
'PatchMetricCurrentWindow',
json,
() => PatchMetricCurrentWindow(
count: json['count'] as int,
range: MetricsRange.fromJson(json['range'] as Map<String, dynamic>),
timeSeries: (json['time_series'] as List?)
?.map<PatchMetricTimeSeriesEntry>(
(e) => PatchMetricTimeSeriesEntry.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
breakdown: (json['breakdown'] as List?)
?.map<PatchMetricBreakdownEntry>(
(e) =>
PatchMetricBreakdownEntry.fromJson(e as Map<String, dynamic>),
)
.toList(),
),
);
}
/// Convenience to create a nullable type from a nullable json object.
/// Useful when parsing optional fields.
static PatchMetricCurrentWindow? maybeFromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
return PatchMetricCurrentWindow.fromJson(json);
}
/// The summed count (installs or downloads) over this window. Unlike
/// the HLL-based metrics, per-bucket `time_series` values sum to this
/// total.
final int count;
/// The effective (post-default, post-clamp) window a metrics response —
/// or one window of a metrics envelope — covers. Always echoed by the
/// server; clients must treat it as authoritative rather than reusing
/// the requested range.
final MetricsRange range;
/// Per-bucket series for this window, present only when a
/// `granularity` was requested; otherwise null. On `previous`, also
/// null when the prior window reaches past the plan's metrics-history
/// horizon (the total is still present — only the granular overlay is
/// withheld). Empty buckets are omitted — gap-fill against this
/// window's `range`.
final List<PatchMetricTimeSeriesEntry>? timeSeries;
/// Per-group counts for this window, present only when a
/// `group_by` was requested; otherwise null.
final List<PatchMetricBreakdownEntry>? breakdown;
/// Converts a [PatchMetricCurrentWindow] to a `Map<String, dynamic>`.
Map<String, dynamic> toJson() {
return {
'count': count,
'range': range.toJson(),
'time_series': timeSeries?.map((e) => e.toJson()).toList(),
'breakdown': breakdown?.map((e) => e.toJson()).toList(),
};
}
@override
int get hashCode => Object.hashAll([
count,
range,
listHash(timeSeries),
listHash(breakdown),
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PatchMetricCurrentWindow &&
count == other.count &&
range == other.range &&
listsEqual(timeSeries, other.timeSeries) &&
listsEqual(breakdown, other.breakdown);
}
}
@@ -0,0 +1,64 @@
import 'package:meta/meta.dart';
import 'package:shorebird_code_push_protocol/model_helpers.dart';
/// {@template patch_metric_time_series_entry}
/// One bucket of a patch-metric time series: the summed count (installs or
/// downloads, per the endpoint) in the bucket starting at `period`.
/// {@endtemplate}
@immutable
class PatchMetricTimeSeriesEntry {
/// {@macro patch_metric_time_series_entry}
const PatchMetricTimeSeriesEntry({
required this.period,
required this.count,
});
/// Converts a `Map<String, dynamic>` to a [PatchMetricTimeSeriesEntry].
factory PatchMetricTimeSeriesEntry.fromJson(Map<String, dynamic> json) {
return parseFromJson(
'PatchMetricTimeSeriesEntry',
json,
() => PatchMetricTimeSeriesEntry(
period: DateTime.parse(json['period'] as String),
count: json['count'] as int,
),
);
}
/// Convenience to create a nullable type from a nullable json object.
/// Useful when parsing optional fields.
static PatchMetricTimeSeriesEntry? maybeFromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
return PatchMetricTimeSeriesEntry.fromJson(json);
}
/// The bucket start (UTC).
final DateTime period;
/// The summed count in this bucket.
final int count;
/// Converts a [PatchMetricTimeSeriesEntry] to a `Map<String, dynamic>`.
Map<String, dynamic> toJson() {
return {
'period': period.toIso8601String(),
'count': count,
};
}
@override
int get hashCode => Object.hashAll([
period,
count,
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PatchMetricTimeSeriesEntry &&
period == other.period &&
count == other.count;
}
}
@@ -0,0 +1,92 @@
import 'package:meta/meta.dart';
import 'package:shorebird_code_push_protocol/model_helpers.dart';
import 'package:shorebird_code_push_protocol/src/models/metrics_range.dart';
import 'package:shorebird_code_push_protocol/src/models/patch_metric_time_series_entry.dart';
/// {@template patch_metric_window}
/// One window of the patch-metric envelope: the summed count over the
/// window's effective range, with a per-bucket series when a `granularity`
/// was requested. This base atom is the full shape of `previous`;
/// `current` extends it (see PatchMetricCurrentWindow).
/// {@endtemplate}
@immutable
class PatchMetricWindow {
/// {@macro patch_metric_window}
const PatchMetricWindow({
required this.count,
required this.range,
this.timeSeries,
});
/// Converts a `Map<String, dynamic>` to a [PatchMetricWindow].
factory PatchMetricWindow.fromJson(Map<String, dynamic> json) {
return parseFromJson(
'PatchMetricWindow',
json,
() => PatchMetricWindow(
count: json['count'] as int,
range: MetricsRange.fromJson(json['range'] as Map<String, dynamic>),
timeSeries: (json['time_series'] as List?)
?.map<PatchMetricTimeSeriesEntry>(
(e) => PatchMetricTimeSeriesEntry.fromJson(
e as Map<String, dynamic>,
),
)
.toList(),
),
);
}
/// Convenience to create a nullable type from a nullable json object.
/// Useful when parsing optional fields.
static PatchMetricWindow? maybeFromJson(Map<String, dynamic>? json) {
if (json == null) {
return null;
}
return PatchMetricWindow.fromJson(json);
}
/// The summed count (installs or downloads) over this window. Unlike
/// the HLL-based metrics, per-bucket `time_series` values sum to this
/// total.
final int count;
/// The effective (post-default, post-clamp) window a metrics response —
/// or one window of a metrics envelope — covers. Always echoed by the
/// server; clients must treat it as authoritative rather than reusing
/// the requested range.
final MetricsRange range;
/// Per-bucket series for this window, present only when a
/// `granularity` was requested; otherwise null. On `previous`, also
/// null when the prior window reaches past the plan's metrics-history
/// horizon (the total is still present — only the granular overlay is
/// withheld). Empty buckets are omitted — gap-fill against this
/// window's `range`.
final List<PatchMetricTimeSeriesEntry>? timeSeries;
/// Converts a [PatchMetricWindow] to a `Map<String, dynamic>`.
Map<String, dynamic> toJson() {
return {
'count': count,
'range': range.toJson(),
'time_series': timeSeries?.map((e) => e.toJson()).toList(),
};
}
@override
int get hashCode => Object.hashAll([
count,
range,
listHash(timeSeries),
]);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PatchMetricWindow &&
count == other.count &&
range == other.range &&
listsEqual(timeSeries, other.timeSeries);
}
}
@@ -0,0 +1,35 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetPatchMetricResponse', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetPatchMetricResponse(
asOf: DateTime.utc(2024),
granularity: 'example',
current: PatchMetricCurrentWindow(
count: 0,
range: MetricsRange(
start: DateTime.utc(2024),
end: DateTime.utc(2024),
),
),
);
final parsed = GetPatchMetricResponse.maybeFromJson(instance.toJson());
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetPatchMetricResponse.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetPatchMetricResponse.maybeFromJson(<String, dynamic>{}),
throwsFormatException,
);
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetAppPatchDownloadsParameter2', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetAppPatchDownloadsParameter2.values.first;
final parsed = GetAppPatchDownloadsParameter2.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetAppPatchDownloadsParameter2.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetAppPatchDownloadsParameter2.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetAppPatchDownloadsParameter2.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetAppPatchDownloadsParameter2.values) {
expect(
GetAppPatchDownloadsParameter2.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetAppPatchDownloadsParameter3', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetAppPatchDownloadsParameter3.values.first;
final parsed = GetAppPatchDownloadsParameter3.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetAppPatchDownloadsParameter3.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetAppPatchDownloadsParameter3.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetAppPatchDownloadsParameter3.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetAppPatchDownloadsParameter3.values) {
expect(
GetAppPatchDownloadsParameter3.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetAppPatchInstallsParameter2', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetAppPatchInstallsParameter2.values.first;
final parsed = GetAppPatchInstallsParameter2.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetAppPatchInstallsParameter2.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetAppPatchInstallsParameter2.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetAppPatchInstallsParameter2.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetAppPatchInstallsParameter2.values) {
expect(
GetAppPatchInstallsParameter2.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetAppPatchInstallsParameter3', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetAppPatchInstallsParameter3.values.first;
final parsed = GetAppPatchInstallsParameter3.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetAppPatchInstallsParameter3.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetAppPatchInstallsParameter3.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetAppPatchInstallsParameter3.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetAppPatchInstallsParameter3.values) {
expect(
GetAppPatchInstallsParameter3.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetReleasePatchDownloadsParameter2', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetReleasePatchDownloadsParameter2.values.first;
final parsed = GetReleasePatchDownloadsParameter2.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetReleasePatchDownloadsParameter2.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetReleasePatchDownloadsParameter2.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetReleasePatchDownloadsParameter2.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetReleasePatchDownloadsParameter2.values) {
expect(
GetReleasePatchDownloadsParameter2.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetReleasePatchDownloadsParameter3', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetReleasePatchDownloadsParameter3.values.first;
final parsed = GetReleasePatchDownloadsParameter3.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetReleasePatchDownloadsParameter3.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetReleasePatchDownloadsParameter3.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetReleasePatchDownloadsParameter3.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetReleasePatchDownloadsParameter3.values) {
expect(
GetReleasePatchDownloadsParameter3.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetReleasePatchInstallsParameter2', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetReleasePatchInstallsParameter2.values.first;
final parsed = GetReleasePatchInstallsParameter2.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetReleasePatchInstallsParameter2.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetReleasePatchInstallsParameter2.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetReleasePatchInstallsParameter2.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetReleasePatchInstallsParameter2.values) {
expect(
GetReleasePatchInstallsParameter2.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,44 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('GetReleasePatchInstallsParameter3', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = GetReleasePatchInstallsParameter3.values.first;
final parsed = GetReleasePatchInstallsParameter3.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(GetReleasePatchInstallsParameter3.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => GetReleasePatchInstallsParameter3.maybeFromJson(
'__invalid_enum_value__',
),
throwsFormatException,
);
});
test('toString matches toJson for every value', () {
for (final value in GetReleasePatchInstallsParameter3.values) {
expect(value.toString(), equals(value.toJson()));
}
});
test('fromJson round-trips every value', () {
for (final value in GetReleasePatchInstallsParameter3.values) {
expect(
GetReleasePatchInstallsParameter3.fromJson(value.toJson()),
equals(value),
);
}
});
});
}
@@ -0,0 +1,31 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('PatchMetricBreakdownEntry', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = PatchMetricBreakdownEntry(
groupBy: 'example',
groupValue: 'example',
count: 0,
);
final parsed = PatchMetricBreakdownEntry.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(PatchMetricBreakdownEntry.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => PatchMetricBreakdownEntry.maybeFromJson(<String, dynamic>{}),
throwsFormatException,
);
});
});
}
@@ -0,0 +1,31 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('PatchMetricCurrentWindow', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = PatchMetricCurrentWindow(
count: 0,
range: MetricsRange(
start: DateTime.utc(2024),
end: DateTime.utc(2024),
),
);
final parsed = PatchMetricCurrentWindow.maybeFromJson(instance.toJson());
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(PatchMetricCurrentWindow.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => PatchMetricCurrentWindow.maybeFromJson(<String, dynamic>{}),
throwsFormatException,
);
});
});
}
@@ -0,0 +1,30 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('PatchMetricTimeSeriesEntry', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = PatchMetricTimeSeriesEntry(
period: DateTime.utc(2024),
count: 0,
);
final parsed = PatchMetricTimeSeriesEntry.maybeFromJson(
instance.toJson(),
);
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(PatchMetricTimeSeriesEntry.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => PatchMetricTimeSeriesEntry.maybeFromJson(<String, dynamic>{}),
throwsFormatException,
);
});
});
}
@@ -0,0 +1,31 @@
// GENERATED — do not hand-edit.
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
import 'package:test/test.dart';
void main() {
group('PatchMetricWindow', () {
test('round-trips via maybeFromJson/toJson', () {
final instance = PatchMetricWindow(
count: 0,
range: MetricsRange(
start: DateTime.utc(2024),
end: DateTime.utc(2024),
),
);
final parsed = PatchMetricWindow.maybeFromJson(instance.toJson());
expect(parsed, equals(instance));
expect(parsed.hashCode, equals(instance.hashCode));
});
test('maybeFromJson returns null on null input', () {
expect(PatchMetricWindow.maybeFromJson(null), isNull);
});
test('maybeFromJson throws FormatException on invalid input', () {
expect(
() => PatchMetricWindow.maybeFromJson(<String, dynamic>{}),
throwsFormatException,
);
});
});
}