-
Notifications
You must be signed in to change notification settings - Fork 577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New analytics #4163
New analytics #4163
Conversation
de67270
to
aeaab9b
Compare
295c009
to
5082a9d
Compare
Creating test packageTo verify that the script is working as expected, I have modified
After creating a NPM package (as
React Native
node.js
Electron
|
5082a9d
to
efa4a2f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, just some minor comments
scripts/submit-analytics.js
Outdated
class Webhook { | ||
/** | ||
* Path and credentials required to submit analytics through the webhook (production mode). | ||
*/ | ||
constructor() { | ||
this.urlPrefix = | ||
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?ip=1&data="; | ||
} | ||
|
||
/** | ||
* Constructs the full URL that will submit analytics to the webhook. | ||
* @param {Object} payload Information that will be submitted through the webhook. | ||
* @returns {string} Complete analytics submission URL | ||
*/ | ||
buildRequest(payload) { | ||
const request = this.urlPrefix + Buffer.from(JSON.stringify(payload.webHook), "utf8").toString("base64"); | ||
return request; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it might make sense to simplify this to just a constant + function rather than a class, as it doesn't do anything stateful. I don't have very strong feelings either way though so I'm happy for it to stay as it is equally!
class Webhook { | |
/** | |
* Path and credentials required to submit analytics through the webhook (production mode). | |
*/ | |
constructor() { | |
this.urlPrefix = | |
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?ip=1&data="; | |
} | |
/** | |
* Constructs the full URL that will submit analytics to the webhook. | |
* @param {Object} payload Information that will be submitted through the webhook. | |
* @returns {string} Complete analytics submission URL | |
*/ | |
buildRequest(payload) { | |
const request = this.urlPrefix + Buffer.from(JSON.stringify(payload.webHook), "utf8").toString("base64"); | |
return request; | |
} | |
} | |
/** | |
* Path and credentials required to submit analytics through the webhook (production mode). | |
*/ | |
const ANALYTICS_BASE_URL = "https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?ip=1&data="; | |
/** | |
* Constructs the full URL that will submit analytics to the webhook. | |
* @param {Object} payload Information that will be submitted through the webhook. | |
* @returns {string} Complete analytics submission URL | |
*/ | |
const getAnalyticsRequestUrl = (payload) => ANALYTICS_BASE_URL + Buffer.from(JSON.stringify(payload.webHook), "utf8").toString("base64"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest something to the art of:
const ANALYTICS_BASE_URL = "https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric";
const requestUrl = `${ANALYTICS_BASE_URL}?ip=1&data=${getAnalyticsData()}`;
This makes the request more maintainable in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of splitting it.
scripts/submit-analytics.js
Outdated
return; | ||
} | ||
|
||
const context = require("../../../package.json"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder whether there are any strange case where this require
could not work as expected (e.g. a user has changed their NODE_PATH
to somewhere else, or some new npm
replacement decides to store packages somewhere else...) and what the impact would be if it does?
Perhaps it is worth wrapping this block in try/catch
just in case.
Maybe there's an alternative strategy (e.g. walk up the directory tree looking for package.json
), but perhaps that is overkill and it's OK to fail silently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we added this to @realm/react
then this would be one directory deeper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use the npm_config_local_prefix
environment to get the path of the package we're getting installed into. We also have fs-extra
as a dependency already, we might as well use readJsonSync
here.
Also isn't it a biiiit excessive to send off the contents of the entire package.json? Isn't the dependencies
object enough for what we want here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dob't think we are sending off the entire package.json
unless I misread the payload?
realm-js/scripts/submit-analytics.js
Line 93 in fe84d68
const payloads = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh - my bad. Read it too fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it is save to assume that we are interested in package.json
in the same directory as node_modules
. If so, we can use process.cwd()
and chop off from node_modules
and down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The working directory is set by NPM to our package root, so that won't work.
I really think we use something like the npm_config_local_prefix
environment variable.
scripts/submit-analytics.js
Outdated
frameworkVersion = context.dependencies["react-native"]; | ||
try { | ||
const podfile = fs.readFileSync("../../ios/Podfile", "utf8"); | ||
if (podfile.includes("hermes_enabled => true")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels a little brittle – maybe a regex match would be better to allow for formatting/syntax differences?
if (podfile.includes("hermes_enabled => true")) { | |
if (/hermes_enabled.*true/.test(podfile)) { |
scripts/submit-analytics.js
Outdated
framework = "react-native"; | ||
frameworkVersion = context.dependencies["react-native"]; | ||
try { | ||
const podfile = fs.readFileSync("../../ios/Podfile", "utf8"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's much we can do about this, but just pointing out that this path is not necessarily fixed, e.g. in my old project we kept the Podfile somewhere else for reasons
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to try to detect if they have Hermes enabled on Android too or is iOS enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to see the Hermes vs. Android/JSC statistics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I picked iOS since a Podfile is easier to parse 😄
scripts/submit-analytics.js
Outdated
frameworkVersion = context.dependencies["electron"]; | ||
} | ||
|
||
const payloads = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const payloads = { | |
const payload = { |
scripts/submit-analytics.js
Outdated
const https = require("https"); | ||
|
||
return new Promise((resolve, reject) => { | ||
const webhookRequest = new Webhook().buildRequest(payload); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If my suggestion at the top is useful, then
const webhookRequest = new Webhook().buildRequest(payload); | |
const webhookRequest = getAnalyticsRequestUrl(payload); |
scripts/submit-analytics.js
Outdated
} | ||
|
||
await Promise.all([ | ||
// send in analytics in the newer S3 format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment still useful as there's no mention of the older format?
// send in analytics in the newer S3 format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, it is mostly a comment for old-timers.
scripts/submit-analytics.js
Outdated
// eslint-disable-next-line no-unused-vars | ||
doLog = (_) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or could this just be
// eslint-disable-next-line no-unused-vars | |
doLog = (_) => { | |
doLog = () => { |
@@ -66,7 +66,6 @@ if (global.enableSyncTests) { | |||
TESTS.SetSyncTests = node_require("./set-sync-tests"); | |||
TESTS.DictionarySyncTests = node_require("./dictionary-sync-tests"); | |||
TESTS.MixedSyncTests = node_require("./mixed-sync-tests"); | |||
TESTS.AnalyticsTests = require("./analytics-tests"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's worth having some kind of integration test for the new way of doing analytics? Maybe one for a follow up in any case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above about the staging. If we don't have access to such a bucket, I don't think we can meaningfully do integration tests on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it could still be interesting to have, for example, a set of skeleton "test projects" with the various different Podfile/package.json permutations that we know of (RN Hermes iOS, RN Hermes Android, RN JSC iOS, RN JSC Android, Electron etc...), then run the script on them with a mocked https
library and assert that (parts of) the payload match what we expect for each project.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree - having tests for this would be great.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the moment, we will skip it all if CI
is set - which Github Actions does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Just a few suggestions and comments. I'm wondering if we can repurpose this to be used in any of our packages
? Probably as a part of the re-packaging effort.
scripts/submit-analytics.js
Outdated
class Webhook { | ||
/** | ||
* Path and credentials required to submit analytics through the webhook (production mode). | ||
*/ | ||
constructor() { | ||
this.urlPrefix = | ||
"https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric?ip=1&data="; | ||
} | ||
|
||
/** | ||
* Constructs the full URL that will submit analytics to the webhook. | ||
* @param {Object} payload Information that will be submitted through the webhook. | ||
* @returns {string} Complete analytics submission URL | ||
*/ | ||
buildRequest(payload) { | ||
const request = this.urlPrefix + Buffer.from(JSON.stringify(payload.webHook), "utf8").toString("base64"); | ||
return request; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to suggest something to the art of:
const ANALYTICS_BASE_URL = "https://webhooks.mongodb-realm.com/api/client/v2.0/app/realmsdkmetrics-zmhtm/service/metric_webhook/incoming_webhook/metric";
const requestUrl = `${ANALYTICS_BASE_URL}?ip=1&data=${getAnalyticsData()}`;
This makes the request more maintainable in the future.
scripts/submit-analytics.js
Outdated
return; | ||
} | ||
|
||
const context = require("../../../package.json"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we added this to @realm/react
then this would be one directory deeper.
scripts/submit-analytics.js
Outdated
@@ -0,0 +1,201 @@ | |||
//////////////////////////////////////////////////////////////////////////// | |||
// | |||
// Copyright 2021 Realm Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Copyright 2021 Realm Inc. | |
// Copyright 2022 Realm Inc. |
scripts/submit-analytics.js
Outdated
|
||
class Webhook { | ||
/** | ||
* Path and credentials required to submit analytics through the webhook (production mode). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we no longer have the option to submit to a staging bucket for testing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so but I can check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review comments.
If my concerns are wrong: LGTM :)
d41cd23
to
90464f3
Compare
scripts/submit-analytics.js
Outdated
} | ||
|
||
async function submitAnalytics(dryRun) { | ||
const wd = process.cwd(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still feel it's less brittle if we relied on process.env.npm_config_local_prefix
instead.
scripts/submit-analytics.js
Outdated
} | ||
|
||
const optionDefinitions = [ | ||
{ name: "dryRun", type: Boolean, multiple: false, description: "If true, don't submit analytics" }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure these objects take a default value, making your next couple of lines obsolete.
scripts/submit-analytics.js
Outdated
(async function () { | ||
await submitAnalytics(dryRun); | ||
})(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might result on an unhandled promise rejection.
(async function () { | |
await submitAnalytics(dryRun); | |
})(); | |
submitAnalytics(dryRun).catch(console.error); |
.github/workflows/pr-analytics.yml
Outdated
cd integration-tests/tests | ||
tmpfile=$(mktemp) | ||
node ../../scripts/submit-analytics.js --test > $tmpfile | ||
cat $tmpfile | grep ^payload | cut -f2- -d' ' | jq '.webHook.properties' | jq 'keys' | jq 'join(",")' | grep -c 'Anonymized Application ID,Anonymized Machine Identifier,Binding,Framework,Framework Version,Host OS Type,Host OS Version,JS Analytics Version,JavaScript Engine,Language,Node.js version,Version,distinct_id,token' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like the analytics code to be imported from the JS (integration) tests and the fetchPlatformData
called with different contexts and asserted accordingly. I feel this bash assertion is a bit hard to maintain and it doesn't answer anything about the actual values in the object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point and we can expand the testing.
The approach here is valuable as it is testing the script as such (including the package.json
discovery parts).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
including the package.json discovery parts.
But it doesn't test the values of the payload properties?
951da7b
to
4f696dc
Compare
3cd5145
to
fafe4fa
Compare
* Suggested changes to the submit-analytics script * Scaffolding Node.js specific analytics tests
@kneth With this change, can we remove the analytics from |
What, How & Why?
☑️ ToDos
[ ] 📝Compatibility
label is updated or copied from previous entry[ ] 📱 Check the React Native/other sample apps work if necessary[ ] 📝 Public documentation PR created or is not necessary[ ] 💥Breaking
label has been applied or is not necessaryIf this PR adds or changes public API's:
[ ] typescript definitions file is updated[ ] jsdoc files updated[ ] Chrome debug API is updated if API is available on React Native