Merge pull request #10178 from backstage/techdocs/addon-integration

[TechDocs] Addon Framework
This commit is contained in:
Eric Peterson
2022-04-12 16:27:02 +02:00
committed by GitHub
60 changed files with 3275 additions and 1696 deletions
+2 -1
View File
@@ -66,8 +66,8 @@ import { SearchPage } from '@backstage/plugin-search';
import { TechRadarPage } from '@backstage/plugin-tech-radar';
import {
TechDocsIndexPage,
techdocsPlugin,
TechDocsReaderPage,
techdocsPlugin,
} from '@backstage/plugin-techdocs';
import {
UserSettingsPage,
@@ -87,6 +87,7 @@ import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTe
import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import * as plugins from './plugins';
import { techDocsPage } from './components/techdocs/TechDocsPage';
import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow';
import { PermissionedRoute } from '@backstage/plugin-permission-react';
@@ -14,29 +14,20 @@
* limitations under the License.
*/
import { Content } from '@backstage/core-components';
import {
TechDocsReaderPageHeader,
TechDocsReaderPage,
Reader,
TechDocsReaderPageHeader,
TechDocsReaderPageSubheader,
TechDocsReaderPageContent,
} from '@backstage/plugin-techdocs';
import React from 'react';
const DefaultTechDocsPage = () => {
return (
<TechDocsReaderPage>
{({ techdocsMetadataValue, entityMetadataValue, entityRef, onReady }) => (
<>
<TechDocsReaderPageHeader
techDocsMetadata={techdocsMetadataValue}
entityMetadata={entityMetadataValue}
entityRef={entityRef}
/>
<Content data-testid="techdocs-content">
<Reader onReady={onReady} entityRef={entityRef} />
</Content>
</>
)}
<TechDocsReaderPageHeader />
<TechDocsReaderPageSubheader />
<TechDocsReaderPageContent />
</TechDocsReaderPage>
);
};
+6
View File
@@ -416,6 +416,12 @@ export function replaceGitHubUrlType(
type: 'blob' | 'tree' | 'edit',
): string;
// @public
export function replaceGitLabUrlType(
url: string,
type: 'blob' | 'tree' | 'edit',
): string;
// @public
export interface ScmIntegration {
resolveEditUrl(url: string): string;
@@ -15,7 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
import { GitLabIntegration, replaceUrlType } from './GitLabIntegration';
import { GitLabIntegration, replaceGitLabUrlType } from './GitLabIntegration';
describe('GitLabIntegration', () => {
it('has a working factory', () => {
@@ -55,28 +55,28 @@ describe('GitLabIntegration', () => {
});
});
describe('replaceUrlType', () => {
describe('replaceGitLabUrlType', () => {
it('should replace with expected type', () => {
expect(
replaceUrlType(
replaceGitLabUrlType(
'https://gitlab.com/my-org/my-project/-/blob/develop/README.md',
'edit',
),
).toBe('https://gitlab.com/my-org/my-project/-/edit/develop/README.md');
expect(
replaceUrlType(
replaceGitLabUrlType(
'https://gitlab.com/webmodules/blob/-/blob/develop/test',
'tree',
),
).toBe('https://gitlab.com/webmodules/blob/-/tree/develop/test');
expect(
replaceUrlType(
replaceGitLabUrlType(
'https://gitlab.com/blob/blob/-/blob/develop/test',
'tree',
),
).toBe('https://gitlab.com/blob/blob/-/tree/develop/test');
expect(
replaceUrlType(
replaceGitLabUrlType(
'https://gitlab.com/blob/blob/-/edit/develop/README.md',
'tree',
),
@@ -60,11 +60,18 @@ export class GitLabIntegration implements ScmIntegration {
}
resolveEditUrl(url: string): string {
return replaceUrlType(url, 'edit');
return replaceGitLabUrlType(url, 'edit');
}
}
export function replaceUrlType(
/**
* Takes a GitLab URL and replaces the type part (blob, tree etc).
*
* @param url - The original URL
* @param type - The desired type, e.g. 'blob', 'tree', 'edit'
* @public
*/
export function replaceGitLabUrlType(
url: string,
type: 'blob' | 'tree' | 'edit',
): string {
+1 -1
View File
@@ -20,4 +20,4 @@ export {
} from './config';
export type { GitLabIntegrationConfig } from './config';
export { getGitLabFileFetchUrl, getGitLabRequestOptions } from './core';
export { GitLabIntegration } from './GitLabIntegration';
export { GitLabIntegration, replaceGitLabUrlType } from './GitLabIntegration';
@@ -17,6 +17,7 @@
"@backstage/integration-react": "^1.0.1-next.1",
"@backstage/plugin-catalog": "^1.1.0-next.1",
"@backstage/plugin-techdocs": "^1.0.1-next.1",
"@backstage/plugin-techdocs-react": "^0.0.0",
"@backstage/test-utils": "^1.0.1-next.1",
"@backstage/theme": "^0.2.15",
"@material-ui/core": "^4.11.0",
+23 -5
View File
@@ -16,20 +16,27 @@
import React from 'react';
import { Navigate, Route } from 'react-router';
import { createApp } from '@backstage/app-defaults';
import { FlatRoutes } from '@backstage/core-app-api';
import { CatalogEntityPage } from '@backstage/plugin-catalog';
import {
DefaultTechDocsHome,
TechDocsIndexPage,
TechDocsReaderPage,
techdocsPlugin,
} from '@backstage/plugin-techdocs';
import {
createTechDocsAddonExtension,
TechDocsAddons,
TechDocsAddonLocations,
} from '@backstage/plugin-techdocs-react';
import { createApp } from '@backstage/app-defaults';
import { FlatRoutes } from '@backstage/core-app-api';
import { CatalogEntityPage } from '@backstage/plugin-catalog';
import { apis } from './apis';
import { Root } from './components/Root';
import { techDocsPage } from './components/TechDocsPage';
import * as plugins from './plugins';
import { configLoader } from './config';
import { Root } from './components/Root';
import { techDocsPage, TechDocsThemeToggle } from './components/TechDocsPage';
const app = createApp({
apis,
@@ -40,6 +47,14 @@ const app = createApp({
const AppProvider = app.getProvider();
const AppRouter = app.getRouter();
const ThemeToggleAddon = techdocsPlugin.provide(
createTechDocsAddonExtension({
name: 'ThemeToggleAddon',
component: TechDocsThemeToggle,
location: TechDocsAddonLocations.Header,
}),
);
const routes = (
<FlatRoutes>
<Navigate key="/" to="/docs/default/component/local/" />
@@ -56,6 +71,9 @@ const routes = (
element={<TechDocsReaderPage />}
>
{techDocsPage}
<TechDocsAddons>
<ThemeToggleAddon />
</TechDocsAddons>
</Route>
</FlatRoutes>
);
@@ -14,29 +14,20 @@
* limitations under the License.
*/
import React, {
FC,
createContext,
useContext,
useState,
useCallback,
} from 'react';
import React, { useState } from 'react';
import { Theme, makeStyles } from '@material-ui/core';
import { ThemeProvider, Box, Tooltip, IconButton } from '@material-ui/core';
import { Box, Tooltip, IconButton } from '@material-ui/core';
import LightIcon from '@material-ui/icons/Brightness7';
import DarkIcon from '@material-ui/icons/Brightness4';
import { lightTheme, darkTheme } from '@backstage/theme';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Content } from '@backstage/core-components';
import { appThemeApiRef, useApi } from '@backstage/core-plugin-api';
import {
Reader,
TechDocsReaderPage,
TechDocsReaderPageHeader,
TechDocsReaderPageContent,
} from '@backstage/plugin-techdocs';
const useStyles = makeStyles((theme: Theme) => ({
@@ -60,44 +51,12 @@ enum Themes {
DARK = 'dark',
}
type TechDocsThemeValue = {
theme: Themes;
toggleTheme: () => void;
};
const TechDocsThemeContext = createContext<TechDocsThemeValue>({
theme: Themes.LIGHT,
toggleTheme: () => {},
});
const TechdocsThemeProvider: FC = ({ children }) => {
const [theme, setTheme] = useState<Themes>(Themes.LIGHT);
const toggleTheme = useCallback(() => {
setTheme(prevTheme =>
prevTheme === Themes.LIGHT ? Themes.DARK : Themes.LIGHT,
);
}, [setTheme]);
const value = { theme, toggleTheme };
const themes = {
[Themes.LIGHT]: lightTheme,
[Themes.DARK]: darkTheme,
};
return (
<TechDocsThemeContext.Provider value={value}>
<ThemeProvider theme={themes[theme]}>{children}</ThemeProvider>
</TechDocsThemeContext.Provider>
);
};
const useTechDocsTheme = () => useContext(TechDocsThemeContext);
const TechDocsThemeToggle = () => {
export const TechDocsThemeToggle = () => {
const appThemeApi = useApi(appThemeApiRef);
const classes = useStyles();
const { theme, toggleTheme } = useTechDocsTheme();
const [theme, setTheme] = useState<Themes>(
(appThemeApi.getActiveThemeId() as Themes) || Themes.LIGHT,
);
const themes = {
[Themes.LIGHT]: {
@@ -112,10 +71,18 @@ const TechDocsThemeToggle = () => {
const { title, icon: Icon } = themes[theme];
const handleSetTheme = () => {
setTheme(prevTheme => {
const newTheme = prevTheme === Themes.LIGHT ? Themes.DARK : Themes.LIGHT;
appThemeApi.setActiveThemeId(newTheme);
return newTheme;
});
};
return (
<Box display="flex" alignItems="center" mr={2}>
<Tooltip title={title} arrow>
<IconButton size="small" onClick={toggleTheme}>
<IconButton size="small" onClick={handleSetTheme}>
<Icon className={classes.headerIcon} />
</IconButton>
</Tooltip>
@@ -123,47 +90,13 @@ const TechDocsThemeToggle = () => {
);
};
const TechDocsPageContent = ({
onReady,
entityRef,
}: {
entityRef: CompoundEntityRef;
onReady: () => void;
}) => {
const classes = useStyles();
return (
<Content className={classes.content} data-testid="techdocs-content">
<Reader onReady={onReady} entityRef={entityRef} withSearch={false} />
</Content>
);
};
const DefaultTechDocsPage = () => {
const techDocsMetadata = {
site_name: 'Live preview environment',
site_description: '',
};
return (
<TechDocsReaderPage>
{({ entityRef, onReady }) => (
<>
<TechDocsReaderPageHeader
entityRef={entityRef}
techDocsMetadata={techDocsMetadata}
>
<TechDocsThemeToggle />
</TechDocsReaderPageHeader>
<TechDocsPageContent entityRef={entityRef} onReady={onReady} />
</>
)}
<TechDocsReaderPageHeader />
<TechDocsReaderPageContent withSearch={false} />
</TechDocsReaderPage>
);
};
export const techDocsPage = (
<TechdocsThemeProvider>
<DefaultTechDocsPage />
</TechdocsThemeProvider>
);
export const techDocsPage = <DefaultTechDocsPage />;