Conversation
bf52457 to
4de6737
Compare
This commit centralizes how feature flags are handled. All feature flags must now add an entry in the `featureFlagConfig` dictionary. This dictionary associates the flag with an environment variable name and optionally a minimum version for CodeQL. The new logic is: - if the environment variable is set to false: disabled - if the minimum version requirement specified and met: disabled - if the environment variable is set to true: enable - Otherwise check feature flag enablement from the server
4de6737 to
e5c3375
Compare
henrymercer
left a comment
There was a problem hiding this comment.
Thanks for doing this. As a general piece of feedback, I wonder whether it would be appropriate to rename FeatureFlags to Features. This would reflect a distinction between "feature flags", which are flippers on the monolith, and "features", which are functions in the CodeQL Action whose enablement depends on environment variables, CLI versions, and feature flags.
src/feature-flags.test.ts
Outdated
| // feature flag should be disabled when an old CLI version is set | ||
| let codeql = mockCodeQLVersion("2.0.0"); | ||
| t.assert( | ||
| !(await featureFlags.getValue(featureFlag as FeatureFlag, codeql)) | ||
| ); | ||
|
|
||
| // even setting the env var to true should not enable the feature flag if | ||
| // the minimum CLI version is not met | ||
| process.env[featureFlagConfig[featureFlag].envVar] = "true"; | ||
| t.assert( | ||
| !(await featureFlags.getValue(featureFlag as FeatureFlag, codeql)) | ||
| ); | ||
|
|
||
| // feature flag should be enabled when a new CLI version is set | ||
| // and env var is not set | ||
| process.env[featureFlagConfig[featureFlag].envVar] = ""; | ||
| codeql = mockCodeQLVersion( | ||
| featureFlagConfig[featureFlag].minimumVersion | ||
| ); | ||
| t.assert( | ||
| await featureFlags.getValue(featureFlag as FeatureFlag, codeql) | ||
| ); | ||
|
|
||
| // set env var to false and check that the feature flag is now disabled | ||
| process.env[featureFlagConfig[featureFlag].envVar] = "false"; | ||
| t.assert( | ||
| !(await featureFlags.getValue(featureFlag as FeatureFlag, codeql)) | ||
| ); |
There was a problem hiding this comment.
Should each of these be separate tests?
Sure, that makes sense. Maybe |
Sure, that works too. |
|
Actually, I like |
- Change env var name for `MlPoweredQueriesEnabled` - Throw error if minimumVersion is specified, but CodeQL argument is not supplied. - Fix failing tests. Note that I removed a config-utils test because it is no longer relevant since we handle codeql minimum versions in the `getValue` function.
|
Addressed all comments. Please look at the two commits individually since the second commit adds a lot of noise through a renaming. |
src/feature-flags.ts
Outdated
| export interface FeatureFlags { | ||
| getValue(flag: FeatureFlag): Promise<boolean>; | ||
| getValue(flag: Feature, codeql?: CodeQL): Promise<boolean>; | ||
| } |
There was a problem hiding this comment.
I think we probably ought to rename this too to Features.
There was a problem hiding this comment.
This is a little bit of bike shedding, but I thought this should remain FeatureFlags since it really is an interface that checks whether a Feature is turned on or off. Also, the concrete instantiation of this is GitHubFeatureFlags.
There was a problem hiding this comment.
My thinking was that FeatureFlags.getValue(x) tells us whether the feature x should be enabled, which as of this PR is no longer the same as whether the feature flag on the monolith is enabled for x.
There was a problem hiding this comment.
Agree we're in bikeshedding territory here, but this interface is also public facing and is really about feature enablement, not feature flags. Couple of suggestions:
- Rename this interface to
Features, and the class that implements it toGithubFeatures - Rename this interface to
FeatureEnablement
There was a problem hiding this comment.
All right. A slightly more complex structure now, but I think it is better:
- Interface is
FeatureEnablement - The public facing class is
Features - This delegates to another internal class
GitHubFeatureFlagsfor the portion of the enablement checking that needs to get information from github.
0a4f3d3 to
6de05e4
Compare
aa1634e to
730228d
Compare
Internal refactoring so that `GitHubFeatureFlags` is private only. The public facing class is `Features`.
730228d to
b27aed7
Compare
henrymercer
left a comment
There was a problem hiding this comment.
This is looking generally in good shape to me. Some follow up suggestions, some of which are optional.
src/feature-flags.ts
Outdated
| export interface FeatureFlags { | ||
| getValue(flag: FeatureFlag): Promise<boolean>; | ||
| getValue(flag: Feature, codeql?: CodeQL): Promise<boolean>; | ||
| } |
There was a problem hiding this comment.
Agree we're in bikeshedding territory here, but this interface is also public facing and is really about feature enablement, not feature flags. Couple of suggestions:
- Rename this interface to
Features, and the class that implements it toGithubFeatures - Rename this interface to
FeatureEnablement
| // Bypassing the toolcache is disabled in test mode. | ||
| if (flag === Feature.BypassToolcacheEnabled && util.isInTestMode()) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Optional: Make this a property of the featureConfig record.
src/feature-flags.ts
Outdated
| return false; | ||
| } | ||
| return flagValue; | ||
| return flagValue || false; |
There was a problem hiding this comment.
For my understanding: When would we need the || here? We already tackled the undefined case above.
There was a problem hiding this comment.
We are getting this information from an API. I just wanted to ensure the value was coerced into a boolean. I should have put:
return !!flagValue;
and I will change.
src/feature-flags.ts
Outdated
| // Do nothing when not running against github.com | ||
| if (this.gitHubVersion.type !== util.GitHubVariant.DOTCOM) { | ||
| this.logger.debug( | ||
| "Not running against github.com. Disabling all feature flags." |
There was a problem hiding this comment.
| "Not running against github.com. Disabling all feature flags." | |
| "Not running against github.com. Feature flags will only be enabled if they are enabled explicitly, for example through an environment variable." |
There was a problem hiding this comment.
Going to use toggleable features instead of feature flags since that seems more appropriate. "Feature flags" is only when they are coming from github. "Features" is the more general concept.
However, here you are suggesting we mention environment variables, but we don't have any place where we actually document them, so it might be confusing. So, I think it would be better if we just do:
Not running against github.com. Disabling all toggleable features.
src/feature-flags.ts
Outdated
| * This should be only used within tests. | ||
| */ | ||
| export function createFeatureFlags(enabledFlags: FeatureFlag[]): FeatureFlags { | ||
| export function createFeatureFlags(enabledFlags: Feature[]): FeatureFlags { |
There was a problem hiding this comment.
Rename this if you go with my suggestion to rename the FeatureFlags interface
src/init-action.ts
Outdated
| ); | ||
|
|
||
| const featureFlags = new GitHubFeatureFlags( | ||
| const featureFlags = new Features( |
There was a problem hiding this comment.
These variable assignments ought to be renamed too. It's a shame it's quite a lot of noise.
src/init-action.ts
Outdated
| if (trapCaching !== undefined) { | ||
| return trapCaching === "true"; | ||
| } | ||
| return await featureFlags.getValue(Feature.TrapCachingEnabled); |
There was a problem hiding this comment.
Optional
| if (trapCaching !== undefined) { | |
| return trapCaching === "true"; | |
| } | |
| return await featureFlags.getValue(Feature.TrapCachingEnabled); | |
| return trapCaching === "true" || (await featureFlags.getValue(Feature.TrapCachingEnabled)); |
Avoid usage of "Feature Flag" unless we are talking specifically about the response from github features api. Otherwise, use terms like "Toggleable features". Note both "toggleable" and "togglable" appear to be valid spellings of the word. I chose the first for no good reason.
9a002a0 to
919e4ca
Compare
|
From PR body —
Should the above be |
angelapwen
left a comment
There was a problem hiding this comment.
Just small comment typo nits 😸
Thanks for the code comments + clear variable naming!
src/feature-flags.ts
Outdated
| export interface FeatureFlags { | ||
| getValue(flag: FeatureFlag): Promise<boolean>; | ||
| export interface FeatureEnablement { | ||
| getValue(feaature: Feature, codeql?: CodeQL): Promise<boolean>; |
There was a problem hiding this comment.
| getValue(feaature: Feature, codeql?: CodeQL): Promise<boolean>; | |
| getValue(feature: Feature, codeql?: CodeQL): Promise<boolean>; |
(+autogenerated code)
src/feature-flags.ts
Outdated
| `please ensure the Action has the 'security-events: write' permission. Details: ${e}` | ||
| ); | ||
| } else { | ||
| // Some feature, such as `ml_powered_queries_enabled` affect the produced alerts. |
There was a problem hiding this comment.
| // Some feature, such as `ml_powered_queries_enabled` affect the produced alerts. | |
| // Some features, such as `ml_powered_queries_enabled` affect the produced alerts. |
src/feature-flags.ts
Outdated
| ); | ||
| } else { | ||
| // Some feature, such as `ml_powered_queries_enabled` affect the produced alerts. | ||
| // Considering these feature disabled in the event of a transient error could |
There was a problem hiding this comment.
| // Considering these feature disabled in the event of a transient error could | |
| // Considering these features disabled in the event of a transient error could |
src/feature-flags.test.ts
Outdated
| // specify a minimum version, then we will have a bunch of code no longer being | ||
| // tested. This is unlikely, and this test will fail if that happens. | ||
| // If we do end up in that situation, then we should consider adding a synthetic | ||
| // feature flag with a minium version that is only used for tests. |
There was a problem hiding this comment.
| // feature flag with a minium version that is only used for tests. | |
| // feature flag with a minimum version that is only used for tests. |
|
Done! |
src/codeql.test.ts
Outdated
| }: { | ||
| apiDetails?: GitHubApiDetails; | ||
| featureFlags?: FeatureFlags; | ||
| featureFlags?: FeatureEnablement; |
There was a problem hiding this comment.
This should be features or featureEnablement
src/codeql.test.ts
Outdated
| "", | ||
| undefined, | ||
| undefined, | ||
| createFeatureFlags([Feature.CliConfigFileEnabled]), |
There was a problem hiding this comment.
This should be createFeatures or createFeatureEnablement
src/feature-flags.test.ts
Outdated
| ); | ||
| } | ||
|
|
||
| // Alls flags should be false except the one we're testing |
There was a problem hiding this comment.
| // Alls flags should be false except the one we're testing | |
| // All flags should be false except the one we're testing |
src/feature-flags.test.ts
Outdated
| testApiDetails, | ||
| testRepositoryNwo, | ||
| getRecordingLogger(loggedMessages) | ||
| const featureFlags = setUpTests( |
There was a problem hiding this comment.
I think this should be features / featureEnablement throughout.
src/feature-flags.ts
Outdated
|
|
||
| /** | ||
| * | ||
| * @param feature The feature flag to check. |
There was a problem hiding this comment.
| * @param feature The feature flag to check. | |
| * @param feature The feature to check. |
| } | ||
| } | ||
|
|
||
| class GitHubFeatureFlags implements FeatureEnablement { |
There was a problem hiding this comment.
Perhaps add a doc comment here saying something like "Feature enablement based solely on the GitHub API. Most of the time you'll want to use Features instead."
| @@ -1,115 +1,200 @@ | |||
| import { getApiClient, GitHubApiDetails } from "./api-client"; | |||
| import { CodeQL } from "./codeql"; | |||
There was a problem hiding this comment.
Consider renaming this file to features or feature-enablement.
|
Let's just get it all done now. |
Sounds good to me! I've pushed a commit to another branch with some more renaming suggestions to avoid needing to make them all in the UI :) https://github.com/github/codeql-action/compare/aeisenberg/ff-refactoring...henrymercer/more-ff-renaming?expand=1. |
henrymercer
left a comment
There was a problem hiding this comment.
Partial review up to the penultimate commit, since I authored the last one.
|
I reviewed the last commit. 🤠 |
|
@henrymercer, can you give another approval? Not sure why your previous one was dismissed. |
This commit centralizes how feature flags are handled. All feature flags must now add an entry in the
featureFlagConfigdictionary. This dictionary associates the flag with an environment variable name and optionally a minimum version for CodeQL.The new logic is:
Merge / deployment checklist