Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions packages/@aws-cdk/toolkit-lib/lib/api/notices/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ function normalizeComponents(xs: Array<Component | Component[]>): Component[][]
return xs.map(x => Array.isArray(x) ? x : [x]);
}

/**
* Returns the DNF components for a notice.
* Uses componentsV2 when schemaVersion is '2', otherwise falls back to components.
*/
function dnfComponents(notice: Notice): Component[][] {
if (notice.schemaVersion === '2' && notice.componentsV2) {
return normalizeComponents(notice.componentsV2);
}
return normalizeComponents(notice.components);
}

function renderComponent(c: Component): string {
if (c.name.startsWith('language:')) {
return `${languageDisplayName(c.name.slice('language:'.length))} apps`;
Expand Down Expand Up @@ -142,7 +153,7 @@ export class NoticesFilter {
*/
private findForNamedComponents(data: Notice[], actualComponents: ActualComponent[]): FilteredNotice[] {
return data.flatMap(notice => {
const ors = this.resolveAliases(normalizeComponents(notice.components));
const ors = this.resolveAliases(dnfComponents(notice));

// Find the first set of the disjunctions of which all components match against the actual components.
// Return the actual components we found so that we can inject their dynamic values. A single filter
Expand Down Expand Up @@ -255,7 +266,7 @@ export class FilteredNotice {
}

public format(): string {
const componentsValue = normalizeComponents(this.notice.components).map(renderConjunction).join(', ');
const componentsValue = dnfComponents(this.notice).map(renderConjunction).join(', ');
return this.resolveDynamicValues([
`${this.notice.issueNumber}\t${this.notice.title}`,
this.formatOverview(),
Expand Down
19 changes: 11 additions & 8 deletions packages/@aws-cdk/toolkit-lib/lib/api/notices/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ export interface Notice {
issueNumber: number;
overview: string;
/**
* A set of affected components
* A flat list of affected components, evaluated as an OR.
*
* The canonical form of a list of components is in Disjunctive Normal Form
* (i.e., an OR of ANDs). This is the form when the list of components is a
* doubly nested array: the notice matches if all components of at least one
* of the top-level array matches.
* The notice matches if any single component matches.
*/
components: Array<Component>;
/**
* A list of affected components in Disjunctive Normal Form (OR of ANDs).
*
* The outer array is an OR, the inner arrays are ANDs. The notice matches
* if all components of at least one inner array match.
*
* If the `components` is a single-level array, it is evaluated as an OR; it
* matches if any of the components matches.
* Only available when `schemaVersion` is `'2'`.
*/
components: Array<Component | Component[]>;
componentsV2?: Array<Component | Component[]>;
schemaVersion: string;
severity?: string;
}
Expand Down
80 changes: 76 additions & 4 deletions packages/@aws-cdk/toolkit-lib/test/api/notices.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,9 @@ describe(NoticesFilter, () => {
title: 'combined',
overview: 'combined issue',
issueNumber: 1,
schemaVersion: '1',
components: [[
schemaVersion: '2',
components: [],
componentsV2: [[
{ name: 'language:typescript', version: '*' },
{ name: 'cli', version: '<=1.0.0' },
]],
Expand Down Expand Up @@ -591,8 +592,9 @@ describe(NoticesFilter, () => {
title: 'match',
overview: 'match',
issueNumber: 1,
schemaVersion: '1',
components: components.map((ands) => ands.map(parseTestComponent)),
schemaVersion: '2',
components: [],
componentsV2: components.map((ands) => ands.map(parseTestComponent)),
},
] satisfies Notice[],
cliVersion,
Expand All @@ -603,6 +605,76 @@ describe(NoticesFilter, () => {
// THEN
expect((await filtered).map((f) => f.notice.title)).toEqual(shouldMatch ? ['match'] : []);
});

test('schemaVersion 1 ignores componentsV2', async () => {
const outDir = path.join(fixtures, 'built-with-2_12_0');

const filtered = await noticesFilter.filter({
data: [
{
title: 'v1 notice',
overview: 'overview',
issueNumber: 1,
schemaVersion: '1',
components: [{ name: 'cli', version: '>=999.0.0' }],
componentsV2: [{ name: 'cli', version: '<=1.0.0' }],
},
] satisfies Notice[],
cliVersion: '1.0.0',
outDir,
bootstrappedEnvironments: [],
});

// Should NOT match because schemaVersion 1 uses components (>=999.0.0), not componentsV2
expect(filtered.map((f) => f.notice.title)).toEqual([]);
});

test('schemaVersion 2 falls back to components when componentsV2 is absent', async () => {
const outDir = path.join(fixtures, 'built-with-2_12_0');

const filtered = await noticesFilter.filter({
data: [
{
title: 'v2 fallback',
overview: 'overview',
issueNumber: 1,
schemaVersion: '2',
components: [{ name: 'cli', version: '<=1.0.0' }],
},
] satisfies Notice[],
cliVersion: '1.0.0',
outDir,
bootstrappedEnvironments: [],
});

expect(filtered.map((f) => f.notice.title)).toEqual(['v2 fallback']);
});

test('schemaVersion 2 uses componentsV2 for DNF matching', async () => {
const outDir = path.join(fixtures, 'built-with-2_12_0');

const filtered = await noticesFilter.filter({
data: [
{
title: 'v2 dnf',
overview: 'overview',
issueNumber: 1,
schemaVersion: '2',
components: [{ name: 'cli', version: '>=999.0.0' }],
componentsV2: [[
{ name: 'cli', version: '<=1.0.0' },
{ name: 'node', version: '>=14.x' },
]],
},
] satisfies Notice[],
cliVersion: '1.0.0',
outDir,
bootstrappedEnvironments: [],
});

// Should match via componentsV2 (AND of cli + node), ignoring components
expect(filtered.map((f) => f.notice.title)).toEqual(['v2 dnf']);
});
});
});

Expand Down
Loading