Skip to content

Commit 409f8e7

Browse files
authored
feat: allow filtering cdk flags feature flags by substring (#776)
This allows users to run `cdk flags` with a substring and all matching feature flags will be displayed to them. When a user runs `cdk flags #substring#` and there is only one matching flag, a description of the flag will be displayed. If there are multiple matching flags, all matching flags will be displayed in a table. If a user runs `cdk flags` with multiple substrings, all matching flags that contain any of the substrings will be displayed in a table. --- By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
1 parent 168016b commit 409f8e7

File tree

2 files changed

+128
-39
lines changed

2 files changed

+128
-39
lines changed

packages/aws-cdk/lib/commands/flag-operations.ts

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ interface FlagOperationsParams {
2222
flagData: FeatureFlag[];
2323
toolkit: Toolkit;
2424
ioHelper: IoHelper;
25+
26+
/** User ran --recommended option */
2527
recommended?: boolean;
28+
29+
/** User ran --all option */
2630
all?: boolean;
31+
32+
/** User provided --value field */
2733
value?: string;
34+
35+
/** User provided FLAGNAME field */
2836
flagName?: string[];
37+
38+
/** User ran --default option */
2939
default?: boolean;
40+
41+
/** User ran --unconfigured option */
3042
unconfigured?: boolean;
3143
}
3244

@@ -392,57 +404,35 @@ function formatTable(headers: string[], rows: string[][]): string {
392404
return table;
393405
}
394406

395-
export async function displayFlags(params: FlagOperationsParams): Promise<void> {
396-
const { flagData, ioHelper, flagName, all } = params;
397-
if (flagName && flagName.length > 0) {
398-
const flag = flagData.find(f => f.name === flagName![0]);
399-
if (!flag) {
400-
await ioHelper.defaults.error('Flag not found.');
401-
return;
402-
}
403-
404-
await ioHelper.defaults.info(`Description: ${flag.explanation}`);
405-
await ioHelper.defaults.info(`Recommended value: ${flag.recommendedValue}`);
406-
await ioHelper.defaults.info(`User value: ${flag.userValue}`);
407-
return;
407+
function getFlagSortOrder(flag: FeatureFlag): number {
408+
if (flag.userValue === undefined) {
409+
return 3;
410+
} else if (isUserValueEqualToRecommended(flag)) {
411+
return 1;
412+
} else {
413+
return 2;
408414
}
415+
}
409416

417+
async function displayFlagTable(flags: FeatureFlag[], ioHelper: IoHelper): Promise<void> {
410418
const headers = ['Feature Flag Name', 'Recommended Value', 'User Value'];
411-
const rows: string[][] = [];
412-
413-
const getFlagPriority = (flag: FeatureFlag): number => {
414-
if (flag.userValue === undefined) {
415-
return 3;
416-
} else if (isUserValueEqualToRecommended(flag)) {
417-
return 1;
418-
} else {
419-
return 2;
420-
}
421-
};
422-
423-
let flagsToDisplay: FeatureFlag[];
424-
if (all) {
425-
flagsToDisplay = flagData;
426-
} else {
427-
flagsToDisplay = flagData.filter(flag =>
428-
flag.userValue === undefined || !isUserValueEqualToRecommended(flag),
429-
);
430-
}
431419

432-
const sortedFlags = [...flagsToDisplay].sort((a, b) => {
433-
const priorityA = getFlagPriority(a);
434-
const priorityB = getFlagPriority(b);
420+
const sortedFlags = [...flags].sort((a, b) => {
421+
const orderA = getFlagSortOrder(a);
422+
const orderB = getFlagSortOrder(b);
435423

436-
if (priorityA !== priorityB) {
437-
return priorityA - priorityB;
424+
if (orderA !== orderB) {
425+
return orderA - orderB;
438426
}
439427
if (a.module !== b.module) {
440428
return a.module.localeCompare(b.module);
441429
}
442430
return a.name.localeCompare(b.name);
443431
});
444432

433+
const rows: string[][] = [];
445434
let currentModule = '';
435+
446436
sortedFlags.forEach((flag) => {
447437
if (flag.module !== currentModule) {
448438
rows.push([chalk.bold(`Module: ${flag.module}`), '', '']);
@@ -459,6 +449,45 @@ export async function displayFlags(params: FlagOperationsParams): Promise<void>
459449
await ioHelper.defaults.info(formattedTable);
460450
}
461451

452+
export async function displayFlags(params: FlagOperationsParams): Promise<void> {
453+
const { flagData, ioHelper, flagName, all } = params;
454+
455+
if (flagName && flagName.length > 0) {
456+
const matchingFlags = flagData.filter(f =>
457+
flagName.some(searchTerm => f.name.toLowerCase().includes(searchTerm.toLowerCase())),
458+
);
459+
460+
if (matchingFlags.length === 0) {
461+
await ioHelper.defaults.error(`Flag matching "${flagName.join(', ')}" not found.`);
462+
return;
463+
}
464+
465+
if (matchingFlags.length === 1) {
466+
const flag = matchingFlags[0];
467+
await ioHelper.defaults.info(`Flag name: ${flag.name}`);
468+
await ioHelper.defaults.info(`Description: ${flag.explanation}`);
469+
await ioHelper.defaults.info(`Recommended value: ${flag.recommendedValue}`);
470+
await ioHelper.defaults.info(`User value: ${flag.userValue}`);
471+
return;
472+
}
473+
474+
await ioHelper.defaults.info(`Found ${matchingFlags.length} flags matching "${flagName.join(', ')}":`);
475+
await displayFlagTable(matchingFlags, ioHelper);
476+
return;
477+
}
478+
479+
let flagsToDisplay: FeatureFlag[];
480+
if (all) {
481+
flagsToDisplay = flagData;
482+
} else {
483+
flagsToDisplay = flagData.filter(flag =>
484+
flag.userValue === undefined || !isUserValueEqualToRecommended(flag),
485+
);
486+
}
487+
488+
await displayFlagTable(flagsToDisplay, ioHelper);
489+
}
490+
462491
function isUserValueEqualToRecommended(flag: FeatureFlag): boolean {
463492
return String(flag.userValue) === String(flag.recommendedValue);
464493
}

packages/aws-cdk/test/commands/flag-operations.test.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,66 @@ describe('displayFlags', () => {
180180
expect(plainTextOutput).toContain('aws-cdk-lib');
181181
expect(plainTextOutput).toContain('different-module');
182182
});
183+
184+
test('displays single flag details when only one substring match is found', async () => {
185+
const params = {
186+
flagData: mockFlagsData,
187+
toolkit: createMockToolkit(),
188+
ioHelper,
189+
flagName: ['s3'],
190+
};
191+
await displayFlags(params);
192+
193+
const plainTextOutput = output();
194+
expect(plainTextOutput).toContain('Description: Another test flag');
195+
expect(plainTextOutput).toContain('Recommended value: false');
196+
expect(plainTextOutput).toContain('User value: undefined');
197+
expect(plainTextOutput).not.toContain('Found');
198+
expect(plainTextOutput).not.toContain('matching');
199+
});
200+
201+
test('returns "Flag not found" if user enters non-matching substring', async () => {
202+
const params = {
203+
flagData: mockFlagsData,
204+
toolkit: createMockToolkit(),
205+
ioHelper,
206+
flagName: ['qwerty'],
207+
};
208+
await displayFlags(params);
209+
210+
const plainTextOutput = output();
211+
expect(plainTextOutput).toContain('Flag matching \"qwerty\" not found.');
212+
});
213+
214+
test('returns all matching flags if user enters common substring', async () => {
215+
const params = {
216+
flagData: mockFlagsData,
217+
toolkit: createMockToolkit(),
218+
ioHelper,
219+
flagName: ['flag'],
220+
};
221+
await displayFlags(params);
222+
223+
const plainTextOutput = output();
224+
expect(plainTextOutput).toContain('@aws-cdk/core:testFlag');
225+
expect(plainTextOutput).toContain('@aws-cdk/s3:anotherFlag');
226+
expect(plainTextOutput).toContain('@aws-cdk/core:matchingFlag');
227+
});
228+
229+
test('returns all matching flags if user enters multiple substrings', async () => {
230+
const params = {
231+
flagData: mockFlagsData,
232+
toolkit: createMockToolkit(),
233+
ioHelper,
234+
flagName: ['matching', 'test'],
235+
};
236+
await displayFlags(params);
237+
238+
const plainTextOutput = output();
239+
expect(plainTextOutput).toContain('@aws-cdk/core:testFlag');
240+
expect(plainTextOutput).toContain('@aws-cdk/core:matchingFlag');
241+
expect(plainTextOutput).not.toContain('@aws-cdk/s3:anotherFlag');
242+
});
183243
});
184244

185245
describe('handleFlags', () => {
@@ -228,7 +288,7 @@ describe('handleFlags', () => {
228288
await handleFlags(mockFlagsData, ioHelper, options, mockToolkit);
229289

230290
const plainTextOutput = output();
231-
expect(plainTextOutput).toContain('Flag not found.');
291+
expect(plainTextOutput).toContain('Flag matching \"@aws-cdk/core:nonExistentFlag\" not found.');
232292
});
233293

234294
test('calls prototypeChanges when set option is true with valid flag', async () => {

0 commit comments

Comments
 (0)