From 8f51cb379f2c652e1770b00bb9cf52f561de8d4d Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 21:23:57 +0000 Subject: [PATCH 1/8] chore: basic skeleton for generating metrics reports --- .../metrics/generateMetricsReport.test.ts | 23 +++++++++++++++++++ src/frontend/metrics/generateMetricsReport.ts | 5 ++++ 2 files changed, 28 insertions(+) create mode 100644 src/frontend/metrics/generateMetricsReport.test.ts create mode 100644 src/frontend/metrics/generateMetricsReport.ts diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts new file mode 100644 index 000000000..c338778b4 --- /dev/null +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -0,0 +1,23 @@ +import generateMetricsReport from './generateMetricsReport'; + +describe('generateMetricsReport', () => { + it('can be serialized and deserialized as JSON', () => { + const report = generateMetricsReport(); + const actual = JSON.parse(JSON.stringify(report)); + const expected = removeUndefinedEntries(report); + expect(actual).toEqual(expected); + }); + + it('includes a report type', () => { + expect(generateMetricsReport().type).toBe('metrics-v1'); + }); +}); + +function removeUndefinedEntries( + obj: Record, +): Record { + const definedEntries = Object.entries(obj).filter( + entry => entry[1] !== undefined, + ); + return Object.fromEntries(definedEntries); +} diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts new file mode 100644 index 000000000..0d36e1a65 --- /dev/null +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -0,0 +1,5 @@ +export default function generateMetricsReport() { + return { + type: 'metrics-v1', + }; +} From ea4d36b709522e99ef58a80d904b5d44c81e642f Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 21:27:28 +0000 Subject: [PATCH 2/8] chore: add app version to metric reports --- src/frontend/metrics/generateMetricsReport.test.ts | 12 ++++++++++-- src/frontend/metrics/generateMetricsReport.ts | 9 ++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index c338778b4..e347fdb40 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -1,15 +1,23 @@ import generateMetricsReport from './generateMetricsReport'; describe('generateMetricsReport', () => { + const defaultOptions: Parameters[0] = { + packageJson: {version: '1.2.3'}, + }; + it('can be serialized and deserialized as JSON', () => { - const report = generateMetricsReport(); + const report = generateMetricsReport(defaultOptions); const actual = JSON.parse(JSON.stringify(report)); const expected = removeUndefinedEntries(report); expect(actual).toEqual(expected); }); it('includes a report type', () => { - expect(generateMetricsReport().type).toBe('metrics-v1'); + expect(generateMetricsReport(defaultOptions).type).toBe('metrics-v1'); + }); + + it('includes the app version', () => { + expect(generateMetricsReport(defaultOptions).appVersion).toBe('1.2.3'); }); }); diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts index 0d36e1a65..c1342f527 100644 --- a/src/frontend/metrics/generateMetricsReport.ts +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -1,5 +1,12 @@ -export default function generateMetricsReport() { +import type {ReadonlyDeep} from 'type-fest'; + +export default function generateMetricsReport({ + packageJson, +}: ReadonlyDeep<{ + packageJson: {version: string}; +}>) { return { type: 'metrics-v1', + appVersion: packageJson.version, }; } From 852696b6f3190376d3737354394ef31e68ffbde2 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 21:31:41 +0000 Subject: [PATCH 3/8] chore: include OS information in metrics report --- .../metrics/generateMetricsReport.test.ts | 28 +++++++++++++++++-- src/frontend/metrics/generateMetricsReport.ts | 6 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index e347fdb40..a18e7793d 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -3,6 +3,8 @@ import generateMetricsReport from './generateMetricsReport'; describe('generateMetricsReport', () => { const defaultOptions: Parameters[0] = { packageJson: {version: '1.2.3'}, + os: 'android', + osVersion: 123, }; it('can be serialized and deserialized as JSON', () => { @@ -13,11 +15,33 @@ describe('generateMetricsReport', () => { }); it('includes a report type', () => { - expect(generateMetricsReport(defaultOptions).type).toBe('metrics-v1'); + const report = generateMetricsReport(defaultOptions); + expect(report.type).toBe('metrics-v1'); }); it('includes the app version', () => { - expect(generateMetricsReport(defaultOptions).appVersion).toBe('1.2.3'); + const report = generateMetricsReport(defaultOptions); + expect(report.appVersion).toBe('1.2.3'); + }); + + it('includes the OS (Android style)', () => { + const report = generateMetricsReport(defaultOptions); + expect(report.os).toBe('android'); + expect(report.osVersion).toBe(123); + }); + + it('includes the OS (iOS style)', () => { + const options = {...defaultOptions, os: 'ios', osVersion: '1.2.3'}; + const report = generateMetricsReport(options); + expect(report.os).toBe('ios'); + expect(report.osVersion).toBe('1.2.3'); + }); + + it('includes the OS (desktop style)', () => { + const options = {...defaultOptions, os: 'win32', osVersion: '1.2.3'}; + const report = generateMetricsReport(options); + expect(report.os).toBe('win32'); + expect(report.osVersion).toBe('1.2.3'); }); }); diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts index c1342f527..feec9a1ad 100644 --- a/src/frontend/metrics/generateMetricsReport.ts +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -2,11 +2,17 @@ import type {ReadonlyDeep} from 'type-fest'; export default function generateMetricsReport({ packageJson, + os, + osVersion, }: ReadonlyDeep<{ packageJson: {version: string}; + os: string; + osVersion: number | string; }>) { return { type: 'metrics-v1', appVersion: packageJson.version, + os, + osVersion, }; } From 2148eb6de2c2a2af69c452ece4a91c209457f160 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 21:37:10 +0000 Subject: [PATCH 4/8] chore: add screen size to metrics report --- src/frontend/metrics/generateMetricsReport.test.ts | 6 ++++++ src/frontend/metrics/generateMetricsReport.ts | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index a18e7793d..29e1e96f6 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -5,6 +5,7 @@ describe('generateMetricsReport', () => { packageJson: {version: '1.2.3'}, os: 'android', osVersion: 123, + screen: {width: 12, height: 34}, }; it('can be serialized and deserialized as JSON', () => { @@ -43,6 +44,11 @@ describe('generateMetricsReport', () => { expect(report.os).toBe('win32'); expect(report.osVersion).toBe('1.2.3'); }); + + it('includes screen dimensions', () => { + const report = generateMetricsReport(defaultOptions); + expect(report.screen).toEqual({width: 12, height: 34}); + }); }); function removeUndefinedEntries( diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts index feec9a1ad..48985692a 100644 --- a/src/frontend/metrics/generateMetricsReport.ts +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -4,15 +4,18 @@ export default function generateMetricsReport({ packageJson, os, osVersion, + screen, }: ReadonlyDeep<{ packageJson: {version: string}; os: string; osVersion: number | string; + screen: {width: number; height: number}; }>) { return { type: 'metrics-v1', appVersion: packageJson.version, os, osVersion, + screen, }; } From 028fd3148dbe24c4bb7106ff60176cbe8242f447 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 22:33:02 +0000 Subject: [PATCH 5/8] chore: add list of countries to metrics report --- package-lock.json | 74 ++++++++++++++++++- package.json | 3 + .../metrics/generateMetricsReport.test.ts | 25 +++++++ src/frontend/metrics/generateMetricsReport.ts | 17 +++++ .../metrics/positionToCountries.test.ts | 27 +++++++ src/frontend/metrics/positionToCountries.ts | 29 ++++++++ .../types/geojson-geometries-lookup.d.ts | 15 ++++ .../types/osm_borders__maritime_10000m.d.ts | 6 ++ 8 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/frontend/metrics/positionToCountries.test.ts create mode 100644 src/frontend/metrics/positionToCountries.ts create mode 100644 src/frontend/types/geojson-geometries-lookup.d.ts create mode 100644 src/frontend/types/osm_borders__maritime_10000m.d.ts diff --git a/package-lock.json b/package-lock.json index d877d02be..8be95e34d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@formatjs/intl-relativetimeformat": "^11.2.4", "@gorhom/bottom-sheet": "^4.5.1", "@mapeo/ipc": "^0.2.0", + "@osm_borders/maritime_10000m": "^1.1.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", "@react-native-picker/picker": "2.6.1", @@ -36,6 +37,7 @@ "expo-location": "~16.5.4", "expo-secure-store": "~12.8.1", "expo-sensors": "~12.9.1", + "geojson-geometries-lookup": "^0.5.0", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", @@ -84,6 +86,7 @@ "@react-native/typescript-config": "^0.74.0", "@testing-library/react-native": "^12.4.3", "@types/debug": "^4.1.7", + "@types/geojson": "^7946.0.14", "@types/jest": "^29.5.12", "@types/lodash.isequal": "^4.5.6", "@types/node": "^20.8.4", @@ -6278,6 +6281,11 @@ "node": ">=14" } }, + "node_modules/@osm_borders/maritime_10000m": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@osm_borders/maritime_10000m/-/maritime_10000m-1.1.0.tgz", + "integrity": "sha512-0gbQoi3ITsqVGMXU5hm6s0TN8MHqA2xQSZ6GeIHZcCuf5f462iP1fMV4JZHNIretwqq9BN9F+4r3hjIHsj7RJw==" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "license": "MIT", @@ -8548,6 +8556,45 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/boolean-contains": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz", + "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz", + "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@turf/destination": { "version": "6.5.0", "license": "MIT", @@ -8720,8 +8767,9 @@ } }, "node_modules/@types/geojson": { - "version": "7946.0.11", - "license": "MIT" + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" }, "node_modules/@types/graceful-fs": { "version": "4.1.6", @@ -13498,6 +13546,28 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-geometries": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/geojson-geometries/-/geojson-geometries-2.0.0.tgz", + "integrity": "sha512-HKljxKnbJrdkr7ijg5/nvcr1b81HP+C/rS48cJwRb3xEqiEynlGkRVkQ/msopw+EE3gf7DjEKdZyEGMck0bOpw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/geojson-geometries-lookup": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson-geometries-lookup/-/geojson-geometries-lookup-0.5.0.tgz", + "integrity": "sha512-AfadxaBda6VTwwX4USLiVofFaz0HIjubC7ZC15hGTvc8K0di3pmCNWGFotKJeURSxYPbJLFoet34X3JrIKFX4A==", + "dependencies": { + "@turf/bbox": "^6.0.1", + "@turf/boolean-contains": "^6.0.1", + "geojson-geometries": "^2.0.0", + "rbush": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/geojson-rbush": { "version": "3.2.0", "license": "MIT", diff --git a/package.json b/package.json index a81acffd2..0c6033b99 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@formatjs/intl-relativetimeformat": "^11.2.4", "@gorhom/bottom-sheet": "^4.5.1", "@mapeo/ipc": "^0.2.0", + "@osm_borders/maritime_10000m": "^1.1.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", "@react-native-picker/picker": "2.6.1", @@ -49,6 +50,7 @@ "expo-location": "~16.5.4", "expo-secure-store": "~12.8.1", "expo-sensors": "~12.9.1", + "geojson-geometries-lookup": "^0.5.0", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", @@ -97,6 +99,7 @@ "@react-native/typescript-config": "^0.74.0", "@testing-library/react-native": "^12.4.3", "@types/debug": "^4.1.7", + "@types/geojson": "^7946.0.14", "@types/jest": "^29.5.12", "@types/lodash.isequal": "^4.5.6", "@types/node": "^20.8.4", diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index 29e1e96f6..614403dd4 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -6,6 +6,18 @@ describe('generateMetricsReport', () => { os: 'android', osVersion: 123, screen: {width: 12, height: 34}, + observations: [ + // Middle of the Atlantic + {lat: 10, lon: -33}, + // Mexico City + {lat: 19.419914, lon: -99.088059}, + // Machias Seal Island, disputed territory + {lat: 44.5, lon: -67.101111}, + // To be ignored + {}, + {lat: 12}, + {lon: 34}, + ], }; it('can be serialized and deserialized as JSON', () => { @@ -49,6 +61,19 @@ describe('generateMetricsReport', () => { const report = generateMetricsReport(defaultOptions); expect(report.screen).toEqual({width: 12, height: 34}); }); + + it("doesn't include countries if no observations are provided", () => { + const options = {...defaultOptions, observations: []}; + const report = generateMetricsReport(options); + expect(report.countries).toBe(undefined); + }); + + it('includes countries where observations are found', () => { + const report = generateMetricsReport(defaultOptions); + + expect(report.countries).toHaveLength(new Set(report.countries).size); + expect(new Set(report.countries)).toEqual(new Set(['MEX', 'CAN', 'USA'])); + }); }); function removeUndefinedEntries( diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts index 48985692a..9c2842805 100644 --- a/src/frontend/metrics/generateMetricsReport.ts +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -1,21 +1,38 @@ import type {ReadonlyDeep} from 'type-fest'; +import type {Observation} from '@mapeo/schema'; +import positionToCountries from './positionToCountries'; export default function generateMetricsReport({ packageJson, os, osVersion, screen, + observations, }: ReadonlyDeep<{ packageJson: {version: string}; os: string; osVersion: number | string; screen: {width: number; height: number}; + observations: ReadonlyArray>; }>) { + const countries = new Set(); + + for (const {lat, lon} of observations) { + if (typeof lat === 'number' && typeof lon === 'number') { + addToSet(countries, positionToCountries(lat, lon)); + } + } + return { type: 'metrics-v1', appVersion: packageJson.version, os, osVersion, screen, + ...(countries.size ? {countries: Array.from(countries)} : {}), }; } + +function addToSet(set: Set, toAdd: Iterable): void { + for (const item of toAdd) set.add(item); +} diff --git a/src/frontend/metrics/positionToCountries.test.ts b/src/frontend/metrics/positionToCountries.test.ts new file mode 100644 index 000000000..87fc028b9 --- /dev/null +++ b/src/frontend/metrics/positionToCountries.test.ts @@ -0,0 +1,27 @@ +import positionToCountries from './positionToCountries'; + +describe('positionToCountries', () => { + it('returns nothing for invalid values', () => { + expect(positionToCountries(-91, 181)).toEqual(new Set()); + expect(positionToCountries(Infinity, -Infinity)).toEqual(new Set()); + expect(positionToCountries(NaN, NaN)).toEqual(new Set()); + }); + + it('returns nothing for the middle of the Atlantic ocean', () => { + expect(positionToCountries(10, -33)).toEqual(new Set()); + }); + + it('returns Mexico for a point in Mexico City', () => { + expect(positionToCountries(19.419914, -99.088059)).toEqual( + new Set(['MEX']), + ); + }); + + it('returns multiple countries for disputed territories', () => { + // [Machias Seal Island][0] is a disputed territory. + // [0]: https://en.wikipedia.org/wiki/Machias_Seal_Island + expect(positionToCountries(44.5, -67.101111)).toEqual( + new Set(['CAN', 'USA']), + ); + }); +}); diff --git a/src/frontend/metrics/positionToCountries.ts b/src/frontend/metrics/positionToCountries.ts new file mode 100644 index 000000000..238cc84fa --- /dev/null +++ b/src/frontend/metrics/positionToCountries.ts @@ -0,0 +1,29 @@ +import borders from '@osm_borders/maritime_10000m'; +import GeojsonGeometriesLookup from 'geojson-geometries-lookup'; + +let lookup: undefined | GeojsonGeometriesLookup; + +export default function positionToCountries( + latitude: number, + longitude: number, +): Set { + lookup ??= new GeojsonGeometriesLookup(borders); + + const result = new Set(); + + const {features} = lookup.getContainers({ + type: 'Point', + coordinates: [longitude, latitude], + }); + for (const {properties} of features) { + if ( + properties && + 'isoA3' in properties && + typeof properties.isoA3 === 'string' + ) { + result.add(properties.isoA3); + } + } + + return result; +} diff --git a/src/frontend/types/geojson-geometries-lookup.d.ts b/src/frontend/types/geojson-geometries-lookup.d.ts new file mode 100644 index 000000000..7d73efd6b --- /dev/null +++ b/src/frontend/types/geojson-geometries-lookup.d.ts @@ -0,0 +1,15 @@ +declare module 'geojson-geometries-lookup' { + import type { + GeoJSON, + Point, + LineString, + Polygon, + FeatureCollection, + } from '@types/geojson'; + + export default class GeojsonGeometriesLookup { + constructor(geoJson: GeoJSON); + + getContainers(geometry: Point | LineString | Polygon): FeatureCollection; + } +} diff --git a/src/frontend/types/osm_borders__maritime_10000m.d.ts b/src/frontend/types/osm_borders__maritime_10000m.d.ts new file mode 100644 index 000000000..b328ce97b --- /dev/null +++ b/src/frontend/types/osm_borders__maritime_10000m.d.ts @@ -0,0 +1,6 @@ +declare module '@osm_borders/maritime_10000m' { + import {GeoJSON} from 'geojson'; + + const data: GeoJSON; + export default data; +} From 1b072861aa98e22e54ee012c5d112d635a0686cb Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Thu, 18 Apr 2024 22:50:32 +0000 Subject: [PATCH 6/8] chore: remove a newline (minor) --- src/frontend/metrics/generateMetricsReport.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index 614403dd4..54a19e289 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -70,7 +70,6 @@ describe('generateMetricsReport', () => { it('includes countries where observations are found', () => { const report = generateMetricsReport(defaultOptions); - expect(report.countries).toHaveLength(new Set(report.countries).size); expect(new Set(report.countries)).toEqual(new Set(['MEX', 'CAN', 'USA'])); }); From 9b3b7c09f656aec124cbac6844ea6a2992e60358 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Tue, 23 Apr 2024 17:58:32 +0000 Subject: [PATCH 7/8] Use "real" package JSON in generateMetricsReport test --- .../metrics/generateMetricsReport.test.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index 54a19e289..1faed17fa 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -1,8 +1,12 @@ +import * as path from 'node:path'; +import * as fs from 'node:fs'; import generateMetricsReport from './generateMetricsReport'; describe('generateMetricsReport', () => { + const packageJson = readPackageJson(); + const defaultOptions: Parameters[0] = { - packageJson: {version: '1.2.3'}, + packageJson, os: 'android', osVersion: 123, screen: {width: 12, height: 34}, @@ -34,7 +38,7 @@ describe('generateMetricsReport', () => { it('includes the app version', () => { const report = generateMetricsReport(defaultOptions); - expect(report.appVersion).toBe('1.2.3'); + expect(report.appVersion).toBe(packageJson.version); }); it('includes the OS (Android style)', () => { @@ -75,6 +79,18 @@ describe('generateMetricsReport', () => { }); }); +function readPackageJson() { + const packageJsonPath = path.resolve( + __dirname, + '..', + '..', + '..', + 'package.json', + ); + const packageJsonData = fs.readFileSync(packageJsonPath, 'utf8'); + return JSON.parse(packageJsonData); +} + function removeUndefinedEntries( obj: Record, ): Record { From c6be4982e9f41ee2327876ac24de7948d5cd7e53 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Tue, 23 Apr 2024 18:03:27 +0000 Subject: [PATCH 8/8] Use stricter type for `os` --- src/frontend/metrics/generateMetricsReport.test.ts | 8 ++++++-- src/frontend/metrics/generateMetricsReport.ts | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/frontend/metrics/generateMetricsReport.test.ts b/src/frontend/metrics/generateMetricsReport.test.ts index 1faed17fa..ff319b655 100644 --- a/src/frontend/metrics/generateMetricsReport.test.ts +++ b/src/frontend/metrics/generateMetricsReport.test.ts @@ -48,14 +48,18 @@ describe('generateMetricsReport', () => { }); it('includes the OS (iOS style)', () => { - const options = {...defaultOptions, os: 'ios', osVersion: '1.2.3'}; + const options = {...defaultOptions, os: 'ios' as const, osVersion: '1.2.3'}; const report = generateMetricsReport(options); expect(report.os).toBe('ios'); expect(report.osVersion).toBe('1.2.3'); }); it('includes the OS (desktop style)', () => { - const options = {...defaultOptions, os: 'win32', osVersion: '1.2.3'}; + const options = { + ...defaultOptions, + os: 'win32' as const, + osVersion: '1.2.3', + }; const report = generateMetricsReport(options); expect(report.os).toBe('win32'); expect(report.osVersion).toBe('1.2.3'); diff --git a/src/frontend/metrics/generateMetricsReport.ts b/src/frontend/metrics/generateMetricsReport.ts index 9c2842805..ca440deda 100644 --- a/src/frontend/metrics/generateMetricsReport.ts +++ b/src/frontend/metrics/generateMetricsReport.ts @@ -10,7 +10,7 @@ export default function generateMetricsReport({ observations, }: ReadonlyDeep<{ packageJson: {version: string}; - os: string; + os: 'android' | 'ios' | NodeJS.Platform; osVersion: number | string; screen: {width: number; height: number}; observations: ReadonlyArray>;