Merge pull request #10178 from backstage/techdocs/addon-integration
[TechDocs] Addon Framework
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 />;
|
||||
|
||||
Reference in New Issue
Block a user