-
Notifications
You must be signed in to change notification settings - Fork 794
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
feat(cli): [STENCIL-12] Adding Telemetry and CLI features. #2964
Conversation
0ec5bbd
to
276a2fb
Compare
5e24abc
to
50b2792
Compare
@splitinfinities setting a reminder for both of us to reset the target branch on Monday. Have a great weekend! |
50b2792
to
628c0c7
Compare
8aee630
to
d52a7d1
Compare
const config = await readConfig(); | ||
await writeConfig(Object.assign(config, newOptions)); | ||
return await writeConfig(Object.assign(config, newOptions)); |
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 please add tests on the return value now that we're returning a primitive? We should be able to assert that we return true
when writeConfig
is successful, and false
when it fails
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, great point! Yes.
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.
@splitinfinities did we get this under test?
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.
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 this is fine ATM - normally I'd like us to get a little more granular there with respect to what we're testing (calling the ionic-config
exposed functions directly) and splitting the tests out based on the assertion, but I think we can leave that for a future day
src/cli/task-help.ts
Outdated
|
||
export const taskHelp = (sys: CompilerSystem, logger: Logger) => { | ||
const p = logger.dim(sys.details.platform === 'windows' ? '>' : '$'); | ||
export const taskHelp = async (config: Config) => { |
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 there a need to pass Config
as an argument here, could we instead call getCompilerSystem()
and getLogger()
on stencil-cli-config.ts
? What's the preferred interface to use for this singleton?
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 would love to migrate to use the singleton in more cases like what you point out - I just didn't want to change too much. I personally enjoy the ergonomics of function calls without any props, as you can tell from the code I added.
For the singleton, my thought process was, as we come in and make modifications, let's refactor code to use the singleton more often. I'm happy to go farther, but want to keep this PR scoped closer to Telemetry than general cleanup and ergonomics.
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 #2964 (comment) - I switched this out.
src/cli/telemetry/telemetry.ts
Outdated
arguments: flags.args, | ||
task: flags.task, |
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 it possible that flags
can be undefined
? I thought that's why we used optional chaining here. Maybe I'm missing something (likely)? Otherwise I'd be concerned the compiler is missing something there 🤔
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've removed that optional chain.
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.
Does that solve the issue though? We define _flags
as:
export default class StencilCLIConfig {
static instance: StencilCLIConfig;
private _flags: ConfigFlags | undefined;
private constructor(options: StencilCLIConfigArgs) {
this._flags = options?.flags || undefined;
}
public static getInstance(options?: StencilCLIConfigArgs): StencilCLIConfig {
if (!StencilCLIConfig.instance) {
StencilCLIConfig.instance = new StencilCLIConfig(options);
}
return StencilCLIConfig.instance;
}
public get flags() {
return this._flags;
}
public set flags(flags: ConfigFlags) {
this._flags = flags;
}
so it's possible we called the ctor for the first time with an empty object literal
const baz = getInstance({});
which sets _flags
to undefined
right? Barring setting that somewhere else, this would be an unsafe access
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.
Considering that use case you mentioned couldn't be possible as the code is written today, I don't think we'll experience that issue.
Co-authored-by: Ryan Waskiewicz <ryanwaskiewicz@gmail.com>
Co-authored-by: Ryan Waskiewicz <ryanwaskiewicz@gmail.com>
Co-authored-by: Ryan Waskiewicz <ryanwaskiewicz@gmail.com>
Co-authored-by: Ryan Waskiewicz <ryanwaskiewicz@gmail.com>
Co-authored-by: Lars Mikkelsen <lars@ionic.io>
Co-authored-by: Lars Mikkelsen <lars@ionic.io>
…clobber other fetch definitions.
Co-authored-by: Lars Mikkelsen <lars@ionic.io>
src/cli/telemetry/telemetry.ts
Outdated
if (type === 'www' && (!!target?.serviceWorker || target.baseUrl !== '/')) { | ||
type = `${type} (PWA/App)`; | ||
} |
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.
Rather than overloading the output targets, I'd add a separate property (e.g. has_app_target
) which you might be able to implement as:
function hasAppTarget(config: Config): boolean {
return config.outputTargets.some(target => target.type === 'www' && (!!target.serviceWorker || target.baseUrl !== '/'));
}
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.
Ohhh, that's a great idea!
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.
Added it as has_app_pwa_config
on TrackableData.
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 c251962
Co-authored-by: Lars Mikkelsen <lars@ionic.io>
…rget return value.
src/cli/ionic-config.ts
Outdated
@@ -21,6 +21,10 @@ export async function readConfig(): Promise<TelemetryConfig> { | |||
}; | |||
|
|||
await writeConfig(config); | |||
} else if (!!config && !config['tokens.telemetry'].match(UUID_REGEX)) { | |||
const newUuid = uuidv4(); | |||
await writeConfig(Object.assign(config, { 'tokens.telemetry': newUuid })); |
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.
FYI/no action required - from a TypeScript typing perspective, there is a subtle difference between using Object.assign
and the spread operator. Object.assign
is typed such that the return type is the intersection of the two args:
assign<T, U>(target: T, source: U): T & U;
if we were to mess up the name of the key in U
, the compiler wouldn't catch the error:
// compiles just fine
await writeConfig(Object.assign(config, { 'tokens.teleasdf': newUuid }));
whereas spreading the object will catch an error
// errors! we can't assign to type `TelemetryConfig`
await writeConfig({ ...config, 'tokens.teleasdf': newUuid });
There are some subtle differences between the two (see 'Differences Versus Object.assign()') but I don't think they apply 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.
Thank you. I did this because I wanted to modify the object in place, but the TypeScript side effect is news to me... Would you like to add prefer-object-spread: error
to our ESLint rules? Open to either option.
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.
Maybe WRT prefer-object-spread
, I need to start having that conversation with Lars this week about long term linting & formatting plans
src/cli/telemetry/telemetry.ts
Outdated
@@ -141,21 +144,18 @@ export async function telemetryAction(action?: TelemetryCallback) { | |||
} | |||
} | |||
|
|||
function hasAppTarget(): boolean { |
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 this under test please?
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 under the impression that you only wanted exported members while we're getting started on coverage. Is that your goal?
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 true in that I don't believe we should be exposing functions solely for the sake of testing (there's always exceptions to the rule 😛). But! The data we're sending has now changed: we have this has_app_pwa_config
field whose value is predicated on the evaluation of this function body:
getStencilCLIConfig().validatedConfig.config.outputTargets.some(
target => target.type === 'www' && (!!target.serviceWorker || target.baseUrl !== '/'),
);
So we should be able to assert that given some outputTargets
on the config, that this function's various branches set the desired result in has_app_pwa_config
. e.g.
false
when the output target list is emptyfalse
when there is no target type of 'www'false
when there is a target type of 'www' & bothtarget.serviceWorker
is false andbaseUrl
is '/'true
when there is a target type of 'www' & bothtarget.serviceWorker
is true andbaseUrl
is '/'true
when there is a target type of 'www' & bothtarget.serviceWorker
is false andbaseUrl
is not '/'
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.
Approved with a few minor asks
src/cli/telemetry/telemetry.ts
Outdated
export const prepareData = async (duration_ms: number, component_count: number = undefined): Promise<TrackableData> => { | ||
const { flags, sys } = getStencilCLIConfig(); | ||
const { typescript, rollup } = getCoreCompiler()?.versions || { typescript: 'unknown', rollup: 'unknown' }; | ||
const packages = await getInstalledPackages() || []; |
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 this always returns an array, and don't think we need the || []
here
}); | ||
}); | ||
|
||
describe('hasAppTarget', () => { |
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 outer level describe matches the one on line 80. Did you mean 'prepareData'?
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 did!
expect(telemetry.hasAppTarget()).toBe(true) | ||
}); | ||
|
||
it('Result is correct when outputTargets contains www with serviceWorker', () => { |
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 this test name needs to be updated, there's no service worker field set in this test
expect(telemetry.hasAppTarget()).toBe(true) | ||
}); | ||
|
||
it('Result is correct when outputTargets contains www with serviceWorker', () => { |
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 this test name needs to be updated, it mimics the two previous test names
* users may opt-out at any time * provide a CLI interface for checking current telemetry status, toggling participation * measure tasks that the Stencil CLI performs today, anonymize data, and send to Ionic for aggregation
Adds Telemetry options to the CLI. Try it out by running any of these commands in your linked project:
npx stencil telemetry
npx stencil telemetry on|off
The debug flag will print the telemetry results, e.g:
npx stencil generate a-component --debug --verbose
Satisfies ADR-1