Skip to content

Commit 378b871

Browse files
authored
[Konflux] Fix Latest Releases not shown when applications field is omitted or uses wildcard patterns. (#2873)
* refactor(konflux): move glob pattern matching to konflux-common Move globToRegex and matchesApplicationPattern from konflux-backend to konflux-common so both frontend and backend can reuse them. * fix(konflux): show releases when applications field is omitted or uses wildcards The frontend was requiring the applications field to be present and non-empty, silently discarding configs when it was omitted. It was also doing literal string matching instead of glob pattern matching. * chore(konflux): add changesets Add changesets with the changes made for konflux and konflux-backend plugins. * chore(konflux): generate api reference Run 'yarn build:api-reports:only' to update api references.
1 parent 948d224 commit 378b871

8 files changed

Lines changed: 166 additions & 33 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-konflux-backend': patch
3+
'@red-hat-developer-hub/backstage-plugin-konflux-common': patch
4+
'@red-hat-developer-hub/backstage-plugin-konflux': patch
5+
---
6+
7+
Bump Backstage dependencies from 1.45.2 to 1.49.4 to align with RHDH 1.10. Fix Latest Releases not shown when applications field is omitted or uses wildcard patterns.

workspaces/konflux/plugins/konflux-backend/src/helpers/kubernetes.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,9 @@
1616
import {
1717
getApplicationFromResource,
1818
K8sResourceCommonWithClusterInfo,
19+
matchesApplicationPattern,
1920
} from '@red-hat-developer-hub/backstage-plugin-konflux-common';
2021

21-
/**
22-
* Convert a glob pattern (e.g. "app-*", "*api*") to a RegExp
23-
*/
24-
const globToRegex = (pattern: string): RegExp => {
25-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
26-
const regexStr = escaped.replace(/\*/g, '.*');
27-
return new RegExp(`^${regexStr}$`);
28-
};
29-
30-
/**
31-
* Check if a name matches any of the given application patterns.
32-
* Supports exact matches and glob patterns with "*".
33-
*/
34-
export const matchesApplicationPattern = (
35-
name: string,
36-
patterns: string[],
37-
): boolean => {
38-
return patterns.some(pattern =>
39-
pattern.includes('*') ? globToRegex(pattern).test(name) : pattern === name,
40-
);
41-
};
42-
4322
/**
4423
* Filter resources by application names or glob patterns
4524
*/

workspaces/konflux/plugins/konflux-common/report.api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export function getSubcomponentsWithFallback(
8787
mainEntity: Entity,
8888
): Entity[];
8989

90+
// @public
91+
export const globToRegex: (pattern: string) => RegExp;
92+
9093
// @public
9194
export type GroupVersionKind = GroupVersionKind_2;
9295

@@ -180,6 +183,12 @@ export const konfluxResourceModels: {
180183
[key: string]: GroupVersionKind_2;
181184
};
182185

186+
// @public
187+
export const matchesApplicationPattern: (
188+
name: string,
189+
patterns: string[],
190+
) => boolean;
191+
183192
// @public (undocumented)
184193
export enum ModelsPlural {
185194
// (undocumented)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { globToRegex, matchesApplicationPattern } from '../patterns';
18+
19+
describe('globToRegex', () => {
20+
it('should match exact string when no wildcard', () => {
21+
const regex = globToRegex('my-app');
22+
expect(regex.test('my-app')).toBe(true);
23+
expect(regex.test('my-app-extra')).toBe(false);
24+
expect(regex.test('prefix-my-app')).toBe(false);
25+
});
26+
27+
it('should match prefix wildcard', () => {
28+
const regex = globToRegex('my-app-*');
29+
expect(regex.test('my-app-frontend')).toBe(true);
30+
expect(regex.test('my-app-backend')).toBe(true);
31+
expect(regex.test('my-app-')).toBe(true);
32+
expect(regex.test('other-app')).toBe(false);
33+
});
34+
35+
it('should match suffix wildcard', () => {
36+
const regex = globToRegex('*-backend');
37+
expect(regex.test('my-app-backend')).toBe(true);
38+
expect(regex.test('other-backend')).toBe(true);
39+
expect(regex.test('backend')).toBe(false);
40+
expect(regex.test('my-app-frontend')).toBe(false);
41+
});
42+
43+
it('should match contains wildcard', () => {
44+
const regex = globToRegex('*-api-*');
45+
expect(regex.test('my-api-service')).toBe(true);
46+
expect(regex.test('test-api-backend')).toBe(true);
47+
expect(regex.test('my-app')).toBe(false);
48+
});
49+
50+
it('should match all with single wildcard', () => {
51+
const regex = globToRegex('*');
52+
expect(regex.test('anything')).toBe(true);
53+
expect(regex.test('')).toBe(true);
54+
});
55+
56+
it('should escape regex special characters', () => {
57+
const regex = globToRegex('my.app*');
58+
expect(regex.test('my.app-frontend')).toBe(true);
59+
expect(regex.test('myXapp-frontend')).toBe(false);
60+
});
61+
});
62+
63+
describe('matchesApplicationPattern', () => {
64+
it('should match exact names', () => {
65+
expect(matchesApplicationPattern('app1', ['app1', 'app2'])).toBe(true);
66+
expect(matchesApplicationPattern('app3', ['app1', 'app2'])).toBe(false);
67+
});
68+
69+
it('should match glob patterns', () => {
70+
expect(matchesApplicationPattern('my-app-frontend', ['my-app-*'])).toBe(
71+
true,
72+
);
73+
expect(matchesApplicationPattern('other-app', ['my-app-*'])).toBe(false);
74+
});
75+
76+
it('should match mix of exact and glob patterns', () => {
77+
expect(
78+
matchesApplicationPattern('special-app', ['my-app-*', 'special-app']),
79+
).toBe(true);
80+
expect(
81+
matchesApplicationPattern('my-app-backend', ['my-app-*', 'special-app']),
82+
).toBe(true);
83+
expect(
84+
matchesApplicationPattern('other-app', ['my-app-*', 'special-app']),
85+
).toBe(false);
86+
});
87+
88+
it('should return false for empty patterns', () => {
89+
expect(matchesApplicationPattern('app1', [])).toBe(false);
90+
});
91+
92+
it('should be case sensitive', () => {
93+
expect(matchesApplicationPattern('App1', ['app1'])).toBe(false);
94+
});
95+
});

workspaces/konflux/plugins/konflux-common/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,5 @@ export { PipelineRunLabel } from './pipeline-runs';
8585
export { getApplicationFromResource } from './resources';
8686

8787
export { getSubcomponentsWithFallback, getSubcomponentNames } from './entities';
88+
89+
export { globToRegex, matchesApplicationPattern } from './patterns';
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Convert a glob pattern (e.g. "app-*", "*api*") to a RegExp.
19+
* Only supports `*` as a wildcard character.
20+
*
21+
* @public
22+
*/
23+
export const globToRegex = (pattern: string): RegExp => {
24+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
25+
const regexStr = escaped.replace(/\*/g, '.*');
26+
return new RegExp(`^${regexStr}$`);
27+
};
28+
29+
/**
30+
* Check if a name matches any of the given application patterns.
31+
* Supports exact matches and glob patterns with "*".
32+
*
33+
* @public
34+
*/
35+
export const matchesApplicationPattern = (
36+
name: string,
37+
patterns: string[],
38+
): boolean => {
39+
return patterns.some(pattern =>
40+
pattern.includes('*') ? globToRegex(pattern).test(name) : pattern === name,
41+
);
42+
};

workspaces/konflux/plugins/konflux/src/components/List/LatestReleasesList/utils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import {
1818
getApplicationFromResource,
19+
matchesApplicationPattern,
1920
ReleaseResource,
2021
SubcomponentClusterConfig,
2122
} from '@red-hat-developer-hub/backstage-plugin-konflux-common';
@@ -40,16 +41,19 @@ export const getLatestRelease = (
4041
config.namespace === namespace,
4142
);
4243

43-
// get all applications from matching configs
44-
const appNames = new Set(
45-
matchingConfigs.flatMap(config => config.applications),
44+
// get all application patterns from matching configs
45+
const appPatterns = matchingConfigs.flatMap(
46+
config => config.applications ?? [],
4647
);
48+
const fetchesAll = appPatterns.length === 0 || appPatterns.includes('*');
4749

4850
const filteredReleases = releases.filter(release => {
4951
const applicationName = getApplicationFromResource(release) || '';
52+
const matchesApp =
53+
fetchesAll || matchesApplicationPattern(applicationName, appPatterns);
5054
return (
5155
release.subcomponent?.name === subcomponent &&
52-
appNames.has(applicationName) &&
56+
matchesApp &&
5357
release.cluster.name === cluster &&
5458
release.metadata?.namespace === namespace
5559
);

workspaces/konflux/plugins/konflux/src/hooks/useEntitiesKonfluxConfig.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,17 +50,12 @@ export const useEntitiesKonfluxConfig = ():
5050
const subcomponentName = e.metadata.name;
5151
clustersParsedYaml.forEach(clusterConfig => {
5252
// filter out invalid configs (missing required fields)
53-
if (
54-
clusterConfig.cluster &&
55-
clusterConfig.namespace &&
56-
clusterConfig.applications &&
57-
clusterConfig.applications.length > 0
58-
) {
53+
if (clusterConfig.cluster && clusterConfig.namespace) {
5954
subcomponentConfigs.push({
6055
subcomponent: subcomponentName,
6156
cluster: clusterConfig.cluster,
6257
namespace: clusterConfig.namespace,
63-
applications: clusterConfig.applications,
58+
applications: clusterConfig.applications || [],
6459
});
6560
}
6661
});

0 commit comments

Comments
 (0)