Skip to content

Commit faf1cbd

Browse files
fix(homepage): return optional response when no default widgets are configured (#3137)
When no `homepage.defaultWidgets` config is set, return an empty response instead of an empty items array, allowing the frontend to distinguish "unconfigured" from "configured but all filtered out." Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 326f2cb commit faf1cbd

7 files changed

Lines changed: 27 additions & 14 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-homepage-backend': patch
3+
'@red-hat-developer-hub/backstage-plugin-homepage-common': patch
4+
---
5+
6+
Make default widgets response optional when no widgets are configured, so that no widget is shown when no conditional widget can be shown.

workspaces/homepage/plugins/homepage-backend/src/defaultWidgets/loadDefaultWidgets.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import {
2222
import { DefaultWidgetNode } from './types';
2323

2424
describe('loadDefaultWidgets', () => {
25-
it('returns an empty array when homepage.defaultWidgets is absent', () => {
25+
it('returns undefined when homepage.defaultWidgets is absent', () => {
2626
const config = mockServices.rootConfig({ data: {} });
27-
expect(loadDefaultWidgets(config)).toEqual([]);
27+
expect(loadDefaultWidgets(config)).toBeUndefined();
2828
});
2929

3030
it('parses a valid tree', () => {

workspaces/homepage/plugins/homepage-backend/src/defaultWidgets/loadDefaultWidgets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ export const defaultWidgetsSchema = z.array(defaultWidgetNodeSchema);
7575
*/
7676
export function loadDefaultWidgets(
7777
config: RootConfigService,
78-
): DefaultWidgetNode[] {
78+
): DefaultWidgetNode[] | undefined {
7979
const raw = config.getOptional('homepage.defaultWidgets');
80-
if (raw === undefined) return [];
80+
if (raw === undefined) return undefined;
8181
const parsed = defaultWidgetsSchema.safeParse(raw);
8282
if (!parsed.success) {
8383
throw new Error(

workspaces/homepage/plugins/homepage-backend/src/plugin.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,15 +147,15 @@ describe('homepagePlugin', () => {
147147
});
148148
});
149149

150-
it('returns an empty list when no default widgets are configured', async () => {
150+
it('returns no items when no default widgets are configured', async () => {
151151
const { server } = await startTestBackend({
152152
features: [homepagePlugin],
153153
});
154154

155155
const res = await request(server).get('/api/homepage/default-widgets');
156156

157157
expect(res.status).toBe(200);
158-
expect(res.body).toEqual({ items: [] });
158+
expect(res.body).toEqual({});
159159
});
160160

161161
it('rejects unauthenticated requests', async () => {

workspaces/homepage/plugins/homepage-backend/src/services/DefaultWidgetsService.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export interface DefaultWidgetsService {
4343
}
4444

4545
export class DefaultWidgetsServiceImpl implements DefaultWidgetsService {
46-
readonly #tree: DefaultWidgetNode[];
46+
readonly #tree: DefaultWidgetNode[] | undefined;
4747
readonly #referencedPermissions: Set<string>;
4848
readonly #catalog: typeof catalogServiceRef.T;
4949
readonly #permissions: PermissionsService;
@@ -56,10 +56,14 @@ export class DefaultWidgetsServiceImpl implements DefaultWidgetsService {
5656
logger: LoggerService;
5757
}): DefaultWidgetsServiceImpl {
5858
const tree = loadDefaultWidgets(options.config);
59-
const referencedPermissions = collectReferencedPermissions(tree);
60-
options.logger.info(
61-
`Loaded ${tree.length} default homepage card root node(s) referencing ${referencedPermissions.size} permission(s)`,
62-
);
59+
const referencedPermissions = collectReferencedPermissions(tree ?? []);
60+
if (tree) {
61+
options.logger.info(
62+
`Loaded ${tree.length} default homepage card root node(s) referencing ${referencedPermissions.size} permission(s)`,
63+
);
64+
} else {
65+
options.logger.info('No default homepage widgets configured');
66+
}
6367
return new DefaultWidgetsServiceImpl(
6468
tree,
6569
referencedPermissions,
@@ -70,7 +74,7 @@ export class DefaultWidgetsServiceImpl implements DefaultWidgetsService {
7074
}
7175

7276
private constructor(
73-
tree: DefaultWidgetNode[],
77+
tree: DefaultWidgetNode[] | undefined,
7478
referencedPermissions: Set<string>,
7579
catalog: typeof catalogServiceRef.T,
7680
permissions: PermissionsService,
@@ -88,6 +92,9 @@ export class DefaultWidgetsServiceImpl implements DefaultWidgetsService {
8892
}: {
8993
credentials: BackstageCredentials<BackstageUserPrincipal>;
9094
}): Promise<DefaultWidgetsResponse> {
95+
if (!this.#tree) {
96+
return {};
97+
}
9198
const ctx = await buildUserContext({
9299
credentials,
93100
catalog: this.#catalog,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface DefaultWidgetNode {
2424
// @public (undocumented)
2525
export interface DefaultWidgetsResponse {
2626
// (undocumented)
27-
items: VisibleDefaultWidget[];
27+
items?: VisibleDefaultWidget[];
2828
}
2929

3030
// @public (undocumented)

workspaces/homepage/plugins/homepage-common/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,5 @@ export interface VisibleDefaultWidget {
4949
* @public
5050
*/
5151
export interface DefaultWidgetsResponse {
52-
items: VisibleDefaultWidget[];
52+
items?: VisibleDefaultWidget[];
5353
}

0 commit comments

Comments
 (0)