Skip to content

Commit

Permalink
feat: retrieve and store snykCodeInlineIgnore feature flag [IDE-440][…
Browse files Browse the repository at this point in the history
…IDE-441] (#492)

* feat: add feature flag snykCodeInlineIgnore

* feat: add feature flag retrieval to extension startup

* fix: only add inline ignore if feature flag is enabled

* refactor: fetch concurrently feature flags

---------

Co-authored-by: Catalina Oyaneder <[email protected]>
  • Loading branch information
bastiandoetsch and Catalina Oyaneder authored Jul 19, 2024
1 parent f10f698 commit a81fded
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 11 deletions.
33 changes: 31 additions & 2 deletions src/snyk/base/modules/snykLib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,37 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib {
}

async setupFeatureFlags(): Promise<void> {
const isEnabled = await this.featureFlagService.fetchFeatureFlag(FEATURE_FLAGS.consistentIgnores);
configuration.setFeatureFlag(FEATURE_FLAGS.consistentIgnores, isEnabled);
const flags = [
{ flag: FEATURE_FLAGS.consistentIgnores, fallback: false },
{ flag: FEATURE_FLAGS.snykCodeInlineIgnore, fallback: true },
];

const featureFlagResults = await Promise.allSettled(
flags.map(({ flag, fallback }) => this.fetchFeatureFlagStatus(flag, fallback)),
);

const fulfilledResults = featureFlagResults.filter(
(result): result is PromiseFulfilledResult<{ flag: string; isEnabled: boolean }> => result.status === 'fulfilled',
);

fulfilledResults.forEach(({ value }) => {
const { flag, isEnabled } = value;
configuration.setFeatureFlag(flag, isEnabled);
Logger.info(`Feature flag ${flag} is ${isEnabled ? 'enabled' : 'disabled'}`);
});

const rejectedResults = featureFlagResults.filter(
(result): result is PromiseRejectedResult => result.status === 'rejected',
);

rejectedResults.forEach(({ reason }) => {
Logger.warn(`Failed to fetch feature flag: ${reason}`);
});
}

private async fetchFeatureFlagStatus(flag: string, fallback: boolean): Promise<{ flag: string; isEnabled: boolean }> {
const isEnabled = await this.featureFlagService.fetchFeatureFlag(flag, fallback);
return { flag, isEnabled };
}

protected async setWorkspaceContext(workspacePaths: string[]): Promise<void> {
Expand Down
1 change: 1 addition & 0 deletions src/snyk/common/constants/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const FEATURE_FLAGS = {
consistentIgnores: 'snykCodeConsistentIgnores',
snykCodeInlineIgnore: 'snykCodeInlineIgnore',
};
7 changes: 4 additions & 3 deletions src/snyk/common/services/featureFlagService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import { SNYK_FEATURE_FLAG_COMMAND } from '../constants/commands';
export class FeatureFlagService {
constructor(private commandExecutor: IVSCodeCommands) {}

async fetchFeatureFlag(flagName: string): Promise<boolean> {
async fetchFeatureFlag(flagName: string, fallbackValue = false): Promise<boolean> {
try {
const ffStatus = await this.commandExecutor.executeCommand<FeatureFlagStatus>(
SNYK_FEATURE_FLAG_COMMAND,
flagName,
);
console.log(`[FeatureFlagService] ${flagName} is ${ffStatus?.ok ? 'enabled' : 'disabled'}`);
return ffStatus?.ok ?? false;
} catch (error) {
console.warn(`Failed to fetch feature flag ${flagName}: ${error}`);
return false;
console.warn(`[FeatureFlagService] Failed to fetch feature flag ${flagName}: ${error}`);
return fallbackValue;
}
}
}
11 changes: 10 additions & 1 deletion src/snyk/snykCode/codeActions/codeIssuesActionsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import { IVSCodeLanguages } from '../../common/vscode/languages';
import { FILE_IGNORE_ACTION_NAME, IGNORE_ISSUE_ACTION_NAME } from '../constants/analysis';
import { IssueUtils } from '../utils/issueUtils';
import { CodeIssueCommandArg } from '../views/interfaces';
import { IConfiguration } from '../../common/configuration/configuration';
import { FEATURE_FLAGS } from '../../common/constants/featureFlags';

export class SnykCodeActionsProvider extends CodeActionsProvider<CodeIssueData> {
constructor(
issues: Readonly<ProductResult<CodeIssueData>>,
private readonly codeActionAdapter: ICodeActionAdapter,
codeActionKindAdapter: ICodeActionKindAdapter,
private readonly languages: IVSCodeLanguages,
private readonly configuration: IConfiguration,
) {
super(issues, codeActionKindAdapter);
}
Expand All @@ -26,8 +29,14 @@ export class SnykCodeActionsProvider extends CodeActionsProvider<CodeIssueData>
const ignoreIssueAction = this.createIgnoreIssueAction(document, issue, range, false);
const fileIgnoreIssueAction = this.createIgnoreIssueAction(document, issue, range, true);

const actions = [openIssueAction, fileIgnoreIssueAction];

if (this.configuration.getFeatureFlag(FEATURE_FLAGS.snykCodeInlineIgnore)) {
actions.push(ignoreIssueAction);
}

// returns list of actions, all new actions should be added to this list
return [openIssueAction, ignoreIssueAction, fileIgnoreIssueAction];
return actions;
}

getIssueRange(issue: Issue<CodeIssueData>): Range {
Expand Down
2 changes: 1 addition & 1 deletion src/snyk/snykCode/codeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class SnykCodeService extends ProductService<CodeIssueData> {
);

this.registerCodeActionsProvider(
new SnykCodeActionsProvider(this.result, codeActionAdapter, codeActionKindAdapter, languages),
new SnykCodeActionsProvider(this.result, codeActionAdapter, codeActionKindAdapter, languages, config),
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import { IVSCodeLanguages } from '../../../../snyk/common/vscode/languages';
import { CodeActionContext, CodeActionKind, Range, TextDocument } from '../../../../snyk/common/vscode/types';
import { SnykCodeActionsProvider } from '../../../../snyk/snykCode/codeActions/codeIssuesActionsProvider';
import { IssueUtils } from '../../../../snyk/snykCode/utils/issueUtils';
import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
import { FEATURE_FLAGS } from '../../../../snyk/common/constants/featureFlags';

suite('Snyk Code actions provider', () => {
let issuesActionsProvider: SnykCodeActionsProvider;
let configuration: IConfiguration;
let codeResults: Map<string, WorkspaceFolderResult<CodeIssueData>>;
let codeActionAdapter: ICodeActionAdapter;
let codeActionKindAdapter: ICodeActionKindAdapter;

setup(() => {
const codeResults = new Map<string, WorkspaceFolderResult<CodeIssueData>>();
codeResults = new Map<string, WorkspaceFolderResult<CodeIssueData>>();
codeResults.set('folderName', [
{
filePath: '//folderName//test.js',
Expand All @@ -23,13 +29,13 @@ suite('Snyk Code actions provider', () => {
} as unknown as Issue<CodeIssueData>,
]);

const codeActionAdapter = {
codeActionAdapter = {
create: (_: string, _kind?: CodeActionKind) => ({
command: {},
}),
} as ICodeActionAdapter;

const codeActionKindAdapter = {
codeActionKindAdapter = {
getQuickFix: sinon.fake(),
} as ICodeActionKindAdapter;

Expand All @@ -39,19 +45,55 @@ suite('Snyk Code actions provider', () => {

sinon.stub(IssueUtils, 'createVsCodeRange').returns(rangeMock);

configuration = {
getFeatureFlag(_: string): boolean {
return true;
},
} as IConfiguration;

issuesActionsProvider = new SnykCodeActionsProvider(
codeResults,
codeActionAdapter,
codeActionKindAdapter,
{} as IVSCodeLanguages,
configuration,
);
});

teardown(() => {
sinon.restore();
});

test('Provides code actions', () => {
test('Provides code actions, inline ignores disabled', () => {
// arrange
const document = {
uri: {
fsPath: '//folderName//test.js',
},
} as unknown as TextDocument;

issuesActionsProvider = new SnykCodeActionsProvider(
codeResults,
codeActionAdapter,
codeActionKindAdapter,
{} as IVSCodeLanguages,
{
getFeatureFlag(_: string): boolean {
return false;
},
} as IConfiguration,
);

// act
const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext);

// verify
strictEqual(codeActions?.length, 2);
strictEqual(codeActions[0].command?.command, SNYK_OPEN_ISSUE_COMMAND);
strictEqual(codeActions[1].command?.command, SNYK_IGNORE_ISSUE_COMMAND);
});

test('Provides code actions, inline ignores enabled', () => {
// arrange
const document = {
uri: {
Expand Down

0 comments on commit a81fded

Please sign in to comment.