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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@red-hat-developer-hub/backstage-plugin-app-defaults': minor
'@red-hat-developer-hub/backstage-plugin-app-auth': patch
'@red-hat-developer-hub/backstage-plugin-app-integrations': patch
'@red-hat-developer-hub/backstage-plugin-app-react': patch
---

Add the Learning Paths NFS module (`learningPathsModule`) with a `/learning-paths` page, Developer Hub proxy-backed data, static JSON fallback, and localized page and nav titles. Also exports `translationRef` and documents the `developerHub.proxyPath` config key.

Updated Backstage version to 1.54.6
1 change: 1 addition & 0 deletions workspaces/app-defaults/.eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
playwright.config.ts
e2e-tests/
28 changes: 21 additions & 7 deletions workspaces/app-defaults/.lintstagedrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,31 @@ function skipLintStagedEslintPrettier(file) {
return base === '.eslintrc.js' || base === '.lintstagedrc.cjs';
}

function skipLintStagedEslint(file) {
const normalized = file.replace(/\\/g, '/');
if (skipLintStagedEslintPrettier(file)) {
return true;
}
return (
normalized.includes('/e2e-tests/') ||
normalized.endsWith('/playwright.config.ts')
);
}

module.exports = {
'*.{js,jsx,ts,tsx,mjs,cjs}': filenames => {
const filtered = filenames.filter(f => !skipLintStagedEslintPrettier(f));
if (!filtered.length) {
const forPrettier = filenames.filter(f => !skipLintStagedEslintPrettier(f));
const forEslint = forPrettier.filter(f => !skipLintStagedEslint(f));
if (!forPrettier.length) {
return [];
}
const quoted = filtered.map(f => JSON.stringify(f));
return [
`eslint --fix ${quoted.join(' ')}`,
`prettier --write ${quoted.join(' ')}`,
];
const quotedPrettier = forPrettier.map(f => JSON.stringify(f));
const commands = [`prettier --write ${quotedPrettier.join(' ')}`];
if (forEslint.length) {
const quotedEslint = forEslint.map(f => JSON.stringify(f));
commands.unshift(`eslint --fix ${quotedEslint.join(' ')}`);
}
return commands;
},
'*.{json,md}': ['prettier --write'],
};
8 changes: 8 additions & 0 deletions workspaces/app-defaults/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ app:
- page:catalog:
config:
path: /
title: 'Home'
- api:app/app-language:
config:
availableLanguages: ['en', 'de', 'es', 'fr', 'it', 'ja']
defaultLanguage: 'en'
- nav-item:user-settings: false
- nav-item:search: false
- nav-item:home: false

organization:
name: My Company
Expand Down
53 changes: 53 additions & 0 deletions workspaces/app-defaults/e2e-tests/learning-path-page.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { expect, test } from '@playwright/test';

import { switchToLocale } from './utils/locale';
import { SidebarPage } from './utils/sidebar-page';
import { getLearningPathsTranslations } from './utils/translations';

test.describe('Learning Paths', () => {
test.beforeEach(async ({ page, locale }) => {
test.info().annotations.push({
type: 'component',
description: 'app-defaults',
});

await page.goto('/');
await page.getByRole('button', { name: 'Enter' }).click();
await switchToLocale(page, locale);
});

test('learning path cards link to external resources in a new tab', async ({
page,
locale,
}) => {
const sidebarPage = new SidebarPage(page, locale);
const translations = getLearningPathsTranslations(locale);

await sidebarPage.openLearningPaths();

await expect(page).toHaveURL(/\/learning-paths\/?$/);
await expect(
page.getByRole('navigation', { name: 'sidebar nav' }).getByRole('link', {
name: translations.menuItem.learningPaths,
}),
).toBeVisible();

await sidebarPage.verifyLearningPathLinksOpenInNewTab();
});
});
52 changes: 52 additions & 0 deletions workspaces/app-defaults/e2e-tests/utils/locale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Page } from '@playwright/test';

export const LOCALES = ['en', 'de', 'es', 'fr', 'it', 'ja'] as const;

const LOCALE_DISPLAY_NAMES: Record<string, string> = {
en: 'English',
de: 'Deutsch',
es: 'Español',
fr: 'Français',
it: 'Italiano',
ja: '日本語',
};

function getLocaleDisplayName(locale: string): string {
const baseLocale = locale.split('-')[0];
return LOCALE_DISPLAY_NAMES[baseLocale] ?? locale;
}

export async function switchToLocale(
page: Page,
locale: string,
): Promise<void> {
const baseLocale = locale.split('-')[0];
if (baseLocale === 'en') {
return;
}

const displayName = getLocaleDisplayName(locale);
const settingsLink = page.getByRole('link', { name: 'Settings' });

await settingsLink.waitFor({ state: 'visible', timeout: 10_000 });
await settingsLink.click();
await page.getByRole('button', { name: 'English' }).click();
await page.getByRole('option', { name: displayName }).click();
await page.goto('/');
}
53 changes: 53 additions & 0 deletions workspaces/app-defaults/e2e-tests/utils/sidebar-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { expect, Page } from '@playwright/test';

import {
getLearningPathsTranslations,
LearningPathsE2eMessages,
} from './translations';

export class SidebarPage {
private readonly translations: LearningPathsE2eMessages;

constructor(private readonly page: Page, locale = 'en') {
this.translations = getLearningPathsTranslations(locale);
}

async openLearningPaths(): Promise<void> {
await this.page
.getByRole('navigation', { name: 'sidebar nav' })
.getByRole('link', {
name: this.translations.menuItem.learningPaths,
})
.click();

await this.page.waitForURL(/\/learning-paths\/?$/);
}

async verifyLearningPathLinksOpenInNewTab(): Promise<void> {
const learningPathLinks = this.page.getByRole('article').getByRole('link');

await expect(learningPathLinks.first()).toBeVisible({ timeout: 20_000 });

for (const learningPathLink of await learningPathLinks.all()) {
await expect(learningPathLink).toBeVisible();
await expect(learningPathLink).toHaveAttribute('target', '_blank');
await expect(learningPathLink).not.toHaveAttribute('href', '');
}
}
}
64 changes: 64 additions & 0 deletions workspaces/app-defaults/e2e-tests/utils/translations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export type LearningPathsE2eMessages = {
menuItem: {
learningPaths: string;
};
learningPaths: {
title: string;
};
};

const enMessages: LearningPathsE2eMessages = {
menuItem: {
learningPaths: 'Learning Paths',
},
learningPaths: {
title: 'Learning Paths',
},
};

const localeMessages: Record<string, LearningPathsE2eMessages> = {
en: enMessages,
de: {
menuItem: { learningPaths: 'Lernpfade' },
learningPaths: { title: 'Lernpfade' },
},
es: {
menuItem: { learningPaths: 'Rutas de aprendizaje' },
learningPaths: { title: 'Rutas de aprendizaje' },
},
fr: {
menuItem: { learningPaths: "Parcours d'apprentissage" },
learningPaths: { title: "Parcours d'apprentissage" },
},
it: {
menuItem: { learningPaths: 'Learning Path' },
learningPaths: { title: 'Learning Path' },
},
ja: {
menuItem: { learningPaths: 'ラーニングパス' },
learningPaths: { title: 'ラーニングパス' },
},
};

export function getLearningPathsTranslations(
locale = 'en',
): LearningPathsE2eMessages {
const baseLocale = locale.split('-')[0];
return localeMessages[baseLocale] ?? enMessages;
}
11 changes: 9 additions & 2 deletions workspaces/app-defaults/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"prettier:fix": "prettier --write .",
"prettier:check": "prettier --check .",
"new": "backstage-cli new --scope @red-hat-developer-hub",
"postinstall": "cd ../../ && yarn install"
"postinstall": "cd ../../ && yarn install",
"test:e2e": "playwright test",
"playwright": "playwright"
},
"workspaces": [
"packages/*",
Expand All @@ -43,6 +45,7 @@
"@backstage/repo-tools": "^0.19.0",
"@changesets/cli": "^2.27.1",
"@jest/environment-jsdom-abstract": "^30.3.0",
"@playwright/test": "1.62.1",
"@types/jest": "^30.0.0",
"jest": "^30.3.0",
"jsdom": "^27.1.0",
Expand All @@ -52,10 +55,14 @@
"typescript": "~5.8.0"
},
"resolutions": {
"@backstage/catalog-model": "1.10.0",
"@backstage/core-components": "0.18.13",
"@backstage/core-plugin-api": "1.12.9",
"@backstage/frontend-plugin-api": "0.18.0",
"@backstage/plugin-catalog-react": "3.2.2",
"@types/react": "^18",
"@types/react-dom": "^18",
"@backstage/plugin-catalog": "2.0.8",
"@backstage/plugin-catalog-react": "3.2.2",
"refractor@npm:3.6.0/prismjs": "^1.30.0"
},
"prettier": "@backstage/cli/config/prettier"
Expand Down
2 changes: 1 addition & 1 deletion workspaces/app-defaults/packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@red-hat-developer-hub/backstage-plugin-app-defaults": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-app-integrations": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-app-react": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-global-header": "^1.21.0",
"@red-hat-developer-hub/backstage-plugin-global-header": "^3.0.0",
"material-icons": "^1.13.14",
"react": "^18.0.2",
"react-dom": "^18.0.2",
Expand Down
2 changes: 1 addition & 1 deletion workspaces/app-defaults/packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { appIntegrationsModule } from '@red-hat-developer-hub/backstage-plugin-a
import {
globalHeaderModule,
globalHeaderTranslationsModule,
} from '@red-hat-developer-hub/backstage-plugin-global-header/alpha';
} from '@red-hat-developer-hub/backstage-plugin-global-header';
import { navModule } from './modules/nav';
import { drawerDemoModule } from './modules/drawer-demo';
import { templateCardDemoModule } from './modules/template-card-demo';
Expand Down
14 changes: 0 additions & 14 deletions workspaces/app-defaults/packages/app/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,4 @@ import ReactDOM from 'react-dom/client';
import App from './App';
import '@backstage/ui/css/styles.css';

// TODO: Remove once @red-hat-developer-hub/backstage-plugin-global-header
// publishes a version with built-in drawer support (width: auto + margin-right
// on the AppBar). Tracked by the GlobalHeader.tsx change in the global-header
// workspace.
const style = document.createElement('style');
style.textContent = `
#global-header {
width: auto;
margin-right: var(--docked-drawer-width, 0px);
transition: margin-right 225ms cubic-bezier(0, 0, 0.2, 1);
}
`;
document.head.appendChild(style);

ReactDOM.createRoot(document.getElementById('root')!).render(App.createRoot());
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/

import { useAppDrawer } from '@red-hat-developer-hub/backstage-plugin-app-react';
import { GlobalHeaderMenuItem } from '@red-hat-developer-hub/backstage-plugin-global-header/alpha';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
Expand Down Expand Up @@ -122,24 +121,3 @@ export const HelpDrawerContent = () => {
</Box>
);
};

export const HelpDrawerMenuItem = ({
handleClose,
}: {
handleClose?: () => void;
}) => {
const { toggleDrawer } = useAppDrawer();

const handleClick = () => {
toggleDrawer('demo-help');
handleClose?.();
};

return (
<GlobalHeaderMenuItem
title="Help"
icon="help_outline"
onClick={handleClick}
/>
);
};
Loading
Loading