-
Notifications
You must be signed in to change notification settings - Fork 98
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
[eas-cli] Use wrapper instead of telemetry subscriber for metadata #1169
Conversation
/changelog-entry chore Refactor metadata telemetry with opt-out |
* With that ID, we can look up the failed requests and help solve the issue. | ||
*/ | ||
export class MetadataTelemetryError extends Error { | ||
public constructor(public readonly originalError: Error, public readonly executionId: string) { |
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 not a big fan of this, since it kind of obfuscates the original error. But I don't see a "safe" way of exposing the executionId
to the user outside withTelemetryAsync
.
We can always do something like the snippet below, but that's not super safe. And adding an intermediate Error class also feels a bit weird (e.g. MetadataDownloadError < TelemetryError < Error
). But I'm open to other suggestions.
try {
...
} catch (error: any) {
error.executionId = ...
throw error;
}
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.
You could make withTelemetryAsync
return whatever passed callback returns
const errors = await withTelemetryAsync(MetadataEvent.APPLE_METADATA_DOWNLOAD, { app, auth }, async () => {
...
return errors;
})
if (errors.length > 0) {
throw new MetadataDownloadError(errors);
}
or handle that error inside withTelemetryAsync (or inside callback that you are passing to withTelemetryAsync)
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 against returning errors as if they were regular data. I like the other suggestion better.
Size Change: -629 B (0%) Total Size: 25.1 MB
|
7c8fc45
to
530649f
Compare
Codecov Report
@@ Coverage Diff @@
## main #1169 +/- ##
==========================================
+ Coverage 51.01% 51.10% +0.09%
==========================================
Files 371 371
Lines 13219 13221 +2
Branches 2543 2694 +151
==========================================
+ Hits 6743 6755 +12
+ Misses 6464 5953 -511
- Partials 12 513 +501
Continue to review full report at Codecov.
|
errors.push(error); | ||
for (const task of tasks) { | ||
try { | ||
await task.downloadAsync({ config, context: taskCtx as AppleData }); |
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.
Can we get rid of the as AppleData
cast?
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.
It's the same as: #1169 (comment) We could relax the types, but we would need to add more checks in the tasks.
* With that ID, we can look up the failed requests and help solve the issue. | ||
*/ | ||
export class MetadataTelemetryError extends Error { | ||
public constructor(public readonly originalError: Error, public readonly executionId: string) { |
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 against returning errors as if they were regular data. I like the other suggestion better.
await withTelemetryAsync(MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth }, async () => { | ||
const errors: Error[] = []; | ||
const config = createAppleReader(fileData); | ||
const tasks = createAppleTasks(metadataCtx); | ||
const taskCtx = { app }; | ||
|
||
for (const task of tasks) { | ||
try { | ||
await task.prepareAsync({ context: taskCtx }); | ||
} catch (error: any) { | ||
errors.push(error); | ||
} | ||
} | ||
} | ||
|
||
for (const task of tasks) { | ||
try { | ||
await task.uploadAsync({ config, context: taskCtx as AppleData }); | ||
} catch (error: any) { | ||
errors.push(error); | ||
for (const task of tasks) { | ||
try { | ||
await task.uploadAsync({ config, context: taskCtx as AppleData }); | ||
} catch (error: any) { | ||
errors.push(error); | ||
} | ||
} | ||
} | ||
|
||
unsubscribeTelemetry(); | ||
|
||
if (errors.length > 0) { | ||
throw new MetadataUploadError(errors, executionId); | ||
} | ||
if (errors.length > 0) { | ||
throw new MetadataUploadError(errors); | ||
} | ||
}); |
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 looks almost like the other function in this PR (downloadMetadataAsync
). Maybe it's worth writing some util to extract the common code?
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.
That's right, they are both the "task runners". The main difference is the task it's executing and the error it could be throwing. I can create a task runner, but I didn't want to abstract too much in the first version.
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.
Let's create a follow-up task to refactor this.
const stub: TelemetryContext = { | ||
// Only the App ID is considered to be sensitive | ||
app: new App({} as any, 'SECRET_APP_ID', {} as any), | ||
// Only the token properties and user credentials are considered to be sensitive | ||
auth: { | ||
username: 'SECRET_USERNAME', | ||
password: 'SECRET_PASSWORD', | ||
context: { | ||
token: 'SECRET_TOKEN', | ||
teamId: 'SECRET_TEAM_ID', | ||
providerId: 1337, | ||
}, | ||
} as any, | ||
}; |
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.
You could use ts-mockito
instead of using as any
. We use it in www.
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.
Oh nice! TIL, I used the any
to only pass a partial version of the original type (only the types that we need in the tests). I'll look into ts-mockito
try { | ||
await withTelemetryAsync(MetadataEvent.APPLE_METADATA_DOWNLOAD, stub, async () => { | ||
throw new Error('testing rejected telemetry action'); | ||
}); | ||
} catch { | ||
// intentional failure | ||
} | ||
|
||
expect(ejectInterceptor).toBeCalledWith(expect.any(Number)); |
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.
await expect(...).rejects.toThrow(...)
try { | ||
await withTelemetryAsync(MetadataEvent.APPLE_METADATA_DOWNLOAD, stub, async () => { | ||
throw new Error('testing exectution ID'); | ||
}); | ||
} catch (error: any) { | ||
caughtError = error; | ||
} | ||
|
||
expect(caughtError).toBeInstanceOf(MetadataTelemetryError); | ||
expect(caughtError).toHaveProperty('executionId', expect.any(String)); | ||
expect(caughtError).toHaveProperty('originalError', expect.any(Error)); |
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.
await expect(...).rejects.toThrow(...)
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 had this at first, but in we also need to test the "executionId". I don't think we can do such a thing with just that line, right?
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 but you're just checking if executionId
and originalError
were set to anything. If we're using typescript for all files in this repo then it's not possible to create an instance of MetadataTelemetryError
without specifying those two fields.
I'm not feeling too strong about this though.
@byCedric is this PR something we still want to merge? If no can we close it? |
Inactive for a long time, plus a lot of changes in the underlying code, so I'm closing it. |
Checklist
/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]
and CHANGELOG.md will be updated automatically.Why
Fixes ENG-5291
See discussion in #1147 (comment)
How
EXPO_NO_TELEMETRY
foreas metadata
subscribeTelemetry
towithTelemetryAsync
withTelemetryAsync
executionId
with the thrown errorTest Plan
$ yarn test telemetry