Skip to content

Commit

Permalink
Add --cleanup option to flags script to show groups of flags by status (
Browse files Browse the repository at this point in the history
#31762)

`yarn flags --cleanup` will categorize flags to make it more clear which
ones may need to be cleaned up, experiments checked on, or are blocked
by internal rollouts.

Alternative to #31760

<img width="787" alt="Screenshot 2024-12-13 at 2 31 30 PM"
src="https://github.com/user-attachments/assets/452aee7e-9caf-4210-a621-53941d59cb2b"
/>
  • Loading branch information
jackpope authored Dec 13, 2024
1 parent 08dfd0b commit 982cf95
Showing 1 changed file with 174 additions and 43 deletions.
217 changes: 174 additions & 43 deletions scripts/flags/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ const argv = yargs
'experimental',
],
},
cleanup: {
describe: 'output flags by cleanup category.',
requiresArg: false,
type: 'boolean',
default: false,
},
}).argv;

// Load ReactFeatureFlags with __NEXT_MAJOR__ replaced with 'next'.
Expand Down Expand Up @@ -375,53 +381,86 @@ const FLAG_CONFIG = {

const FLAG_COLUMNS = Object.keys(FLAG_CONFIG);

const INTERNAL_VARIANTS = ['WWW Classic', 'WWW Modern', 'RN FB'];
const OSS_VARIANTS = [
'OSS Next Major',
'OSS Canary',
'OSS Experimental',
'RN OSS',
'RN Next Major',
];

// Build the table with the value for each flag.
const isDiff = argv.diff != null && argv.diff.length > 1;
const table = {};
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const flag of allFlagsUniqueFlags) {
const values = FLAG_COLUMNS.reduce((acc, key) => {
acc[key] = FLAG_CONFIG[key](flag);
return acc;
}, {});

if (!isDiff) {
table[flag] = values;
continue;
}
function buildTable(filterFn) {
const isDiff = argv.diff != null && argv.diff.length > 1;
const table = {};
const filteredFlags = filterFn
? allFlagsUniqueFlags.filter(filterFn)
: allFlagsUniqueFlags;
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const flag of filteredFlags) {
const values = FLAG_COLUMNS.reduce((acc, key) => {
acc[key] = FLAG_CONFIG[key](flag);
return acc;
}, {});

if (!isDiff) {
table[flag] = values;
continue;
}

const subset = argv.diff.map(argToHeader).reduce((acc, key) => {
if (key in values) {
acc[key] = values[key];
}
return acc;
}, {});

const subset = argv.diff.map(argToHeader).reduce((acc, key) => {
if (key in values) {
acc[key] = values[key];
if (new Set(Object.values(subset)).size !== 1) {
table[flag] = subset;
}
return acc;
}, {});
}

if (new Set(Object.values(subset)).size !== 1) {
table[flag] = subset;
// Sort the table
let sorted = table;
if (isDiff || argv.sort) {
const sortChannel = argToHeader(isDiff ? argv.diff[0] : argv.sort);
const sortBy =
sortChannel === 'flag'
? ([flagA], [flagB]) => {
return flagA.localeCompare(flagB);
}
: ([, rowA], [, rowB]) => {
return rowB[sortChannel]
.toString()
.localeCompare(rowA[sortChannel]);
};
sorted = Object.fromEntries(Object.entries(table).sort(sortBy));
}

return sorted;
}

// Sort the table
let sorted = table;
if (isDiff || argv.sort) {
const sortChannel = argToHeader(isDiff ? argv.diff[0] : argv.sort);
const sortBy =
sortChannel === 'flag'
? ([flagA], [flagB]) => {
return flagA.localeCompare(flagB);
}
: ([, rowA], [, rowB]) => {
return rowB[sortChannel].toString().localeCompare(rowA[sortChannel]);
};
sorted = Object.fromEntries(Object.entries(table).sort(sortBy));
function formatTable(tableData) {
// left align the flag names.
const maxLength = Math.max(
...Object.keys(tableData).map(item => item.length)
);
const padded = {};
Object.keys(tableData).forEach(key => {
const newKey = key.padEnd(maxLength, ' ');
padded[newKey] = tableData[key];
});

return padded;
}

if (argv.csv) {
const table = buildTable();
const csvRows = [
`Flag name, ${FLAG_COLUMNS.join(', ')}`,
...Object.keys(table).map(flag => {
const row = sorted[flag];
const row = table[flag];
return `${flag}, ${FLAG_COLUMNS.map(col => row[col]).join(', ')}`;
}),
];
Expand All @@ -433,16 +472,108 @@ if (argv.csv) {
});
}

// left align the flag names.
const maxLength = Math.max(...Object.keys(sorted).map(item => item.length));
const padded = {};
Object.keys(sorted).forEach(key => {
const newKey = key.padEnd(maxLength, ' ');
padded[newKey] = sorted[key];
});
if (argv.cleanup) {
const allPassingFlags = [];
const allFailingFlags = [];
const needsShippedExperimentFlags = [];
const earlyExperimentationFlags = [];
const internalOnlyFlags = [];

const diffedFlagColumns =
argv.diff[0] != null ? argv.diff.map(argToHeader) : FLAG_COLUMNS;

// eslint-disable-next-line no-for-of-loops/no-for-of-loops
for (const flag of allFlagsUniqueFlags) {
const values = diffedFlagColumns.reduce((acc, key) => {
acc[key] = FLAG_CONFIG[key](flag);
return acc;
}, {});

const uniqueValues = new Set(Object.values(values));

if (
uniqueValues.size === 1 &&
(uniqueValues.has('✅') ||
typeof uniqueValues.values().next().value === 'number')
) {
allPassingFlags.push(flag);
}

if (uniqueValues.size === 1 && uniqueValues.has('❌')) {
allFailingFlags.push(flag);
}

const internalVariantValues = INTERNAL_VARIANTS.filter(value =>
diffedFlagColumns.includes(value)
).map(v => values[v]);
const ossVariantValues = OSS_VARIANTS.filter(value =>
diffedFlagColumns.includes(value)
).map(v => values[v]);

if (
internalVariantValues.some(v => v === '✅') &&
ossVariantValues.every(v => v === '❌')
) {
internalOnlyFlags.push(flag);
}

if (
internalVariantValues.some(v => v === '🧪') &&
(ossVariantValues.every(v => v === '❌') ||
(ossVariantValues.some(v => v === '❌') &&
values['OSS Experimental'] === '✅'))
) {
earlyExperimentationFlags.push(flag);
}

if (
internalVariantValues.some(v => v === '🧪' || v === '❌') &&
ossVariantValues.every(v => v === '✅')
) {
needsShippedExperimentFlags.push(flag);
}
}

if (allPassingFlags.length > 0) {
console.log('ALL VARIANTS PASS (✅)');
console.table(
formatTable(buildTable(flag => allPassingFlags.includes(flag)))
);
}

if (allFailingFlags.length > 0) {
console.log('ALL VARIANTS FAIL (❌)');
console.table(
formatTable(buildTable(flag => allFailingFlags.includes(flag)))
);
}

if (internalOnlyFlags.length > 0) {
console.log('INTERNAL ONLY (✅)');
console.table(
formatTable(buildTable(flag => internalOnlyFlags.includes(flag)))
);
}

if (earlyExperimentationFlags.length > 0) {
console.log('WAITING ON RESULTS (🧪)');
console.table(
formatTable(buildTable(flag => earlyExperimentationFlags.includes(flag)))
);
}

if (needsShippedExperimentFlags.length > 0) {
console.log('WAITING ON ROLLOUT (🧪)');
console.table(
formatTable(
buildTable(flag => needsShippedExperimentFlags.includes(flag))
)
);
}
} else {
console.table(formatTable(buildTable()));
}

// print table with formatting
console.table(padded);
console.log(`
Legend:
✅ On
Expand Down

0 comments on commit 982cf95

Please sign in to comment.