Skip to content
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

Closed
wants to merge 5 commits into from

Conversation

byCedric
Copy link
Member

@byCedric byCedric commented Jun 16, 2022

Checklist

  • I've added an entry to CHANGELOG.md if necessary. You can comment this pull request with /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

  • Added EXPO_NO_TELEMETRY for eas metadata
  • Refactored subscribeTelemetry to withTelemetryAsync
  • Added more tests for withTelemetryAsync
  • Added Error container to safely wrap executionId with the thrown error
  • Updated shared error handler to use the Error container

Test Plan

  • $ yarn test telemetry

@byCedric byCedric requested a review from wkozyra95 June 16, 2022 12:23
@byCedric
Copy link
Member Author

/changelog-entry chore Refactor metadata telemetry with opt-out

@byCedric byCedric requested a review from dsokal as a code owner June 16, 2022 12:24
* 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) {
Copy link
Member Author

@byCedric byCedric Jun 16, 2022

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;
}

Copy link
Contributor

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)

Copy link
Contributor

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.

@github-actions
Copy link

github-actions bot commented Jun 16, 2022

Size Change: -629 B (0%)

Total Size: 25.1 MB

Filename Size Change
./packages/eas-cli/dist/eas-linux-x64.tar.gz 25.1 MB -629 B (0%)

compressed-size-action

@byCedric byCedric force-pushed the @bycedric/metadata/refactor-telemetry branch from 7c8fc45 to 530649f Compare June 16, 2022 12:33
@codecov
Copy link

codecov bot commented Jun 16, 2022

Codecov Report

Merging #1169 (97c361f) into main (db338b2) will increase coverage by 0.09%.
The diff coverage is 26.67%.

@@            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     
Impacted Files Coverage Δ
packages/eas-cli/src/metadata/download.ts 29.73% <6.25%> (+1.53%) ⬆️
packages/eas-cli/src/metadata/upload.ts 27.03% <6.25%> (+0.72%) ⬆️
packages/eas-cli/src/metadata/errors.ts 25.00% <57.15%> (+6.25%) ⬆️
packages/eas-cli/src/metadata/utils/telemetry.ts 77.09% <100.00%> (+18.39%) ⬆️
packages/eas-cli/src/project/publish.ts 93.66% <0.00%> (-0.04%) ⬇️
packages/eas-cli/src/prompts.ts 43.34% <0.00%> (ø)
packages/eas-cli/src/vcs/git.ts 27.28% <0.00%> (ø)
packages/eas-json/src/reader.ts 80.44% <0.00%> (ø)
packages/eas-cli/src/platform.ts 50.00% <0.00%> (ø)
packages/eas-cli/src/vcs/local.ts 79.49% <0.00%> (ø)
... and 130 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3e224f9...97c361f. Read the comment docs.

@linear
Copy link

linear bot commented Jun 16, 2022

ENG-5291 Implement `EXPO_NO_TELEMETRY`

Allow users to disable it

errors.push(error);
for (const task of tasks) {
try {
await task.downloadAsync({ config, context: taskCtx as AppleData });
Copy link
Contributor

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?

Copy link
Member Author

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) {
Copy link
Contributor

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.

Comment on lines 35 to 60
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);
}
});
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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.

Comment on lines +7 to +20
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,
};
Copy link
Contributor

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.

Copy link
Member Author

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

Comment on lines +42 to +50
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await expect(...).rejects.toThrow(...)

Comment on lines +56 to +66
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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await expect(...).rejects.toThrow(...)

Copy link
Member Author

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?

Copy link
Contributor

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 byCedric marked this pull request as draft July 18, 2022 11:51
@dsokal
Copy link
Contributor

dsokal commented Apr 25, 2023

@byCedric is this PR something we still want to merge? If no can we close it?

@wkozyra95
Copy link
Contributor

Inactive for a long time, plus a lot of changes in the underlying code, so I'm closing it.

@wkozyra95 wkozyra95 closed this Jun 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants