Merge branch 'master' of github.com:backstage/backstage into blam/isomorphic-git

* 'master' of github.com:backstage/backstage: (45 commits)
  Speed up some PRs with paths-ignore
  Remove unnecessary step
  catalog: export EntitySwitch component and conditions
  Apply suggestions from code review
  changeset: add changeset for new catalog composability components
  catalog: fix EntityLayout TabbedLayout route path matching
  catalog: gather all mount points beneath EntityLayout Routes
  catalog: add EntityLayout
  Change to raw Windows Discord hook
  Release plugin-cost-insights 0.5.3
  catalog: add EntitySwitch
  catalog: export new CatalogIndexPage and CatalogEntityPage
  Create dull-seals-march.md
  core-api: add BackstageRoutes
  Fix plugin name
  fixed category lables
  feat(cost-insights): Make alert url field optional
  fossa: Add changeset
  fix(fossa): Bump core packages version to fix failing CI
  Update 2020-12-22-stability-index.md
  ...
This commit is contained in:
blam
2020-12-26 22:22:11 +01:00
142 changed files with 3149 additions and 399 deletions
+18
View File
@@ -1,5 +1,23 @@
# example-app
## 0.2.8
### Patch Changes
- Updated dependencies [19554f6d6]
- Updated dependencies [1dc445e89]
- Updated dependencies [342270e4d]
- Updated dependencies [7d72f9b09]
- @backstage/cli@0.4.3
- @backstage/plugin-github-actions@0.2.6
- @backstage/plugin-scaffolder@0.3.5
- @backstage/core@0.4.2
- @backstage/test-utils@0.1.6
- @backstage/plugin-graphiql@0.2.3
- @backstage/plugin-catalog@0.2.8
- @backstage/plugin-pagerduty@0.2.4
- @backstage/plugin-techdocs@0.5.1
## 0.2.7
### Patch Changes
+10 -10
View File
@@ -1,39 +1,39 @@
{
"name": "example-app",
"version": "0.2.7",
"version": "0.2.8",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.6.0",
"@backstage/cli": "^0.4.2",
"@backstage/core": "^0.4.1",
"@backstage/cli": "^0.4.3",
"@backstage/core": "^0.4.2",
"@backstage/plugin-api-docs": "^0.4.1",
"@backstage/plugin-catalog": "^0.2.7",
"@backstage/plugin-catalog": "^0.2.8",
"@backstage/plugin-catalog-import": "^0.3.2",
"@backstage/plugin-circleci": "^0.2.5",
"@backstage/plugin-cloudbuild": "^0.2.5",
"@backstage/plugin-cost-insights": "^0.5.2",
"@backstage/plugin-explore": "^0.2.2",
"@backstage/plugin-gcp-projects": "^0.2.2",
"@backstage/plugin-github-actions": "^0.2.5",
"@backstage/plugin-github-actions": "^0.2.6",
"@backstage/plugin-gitops-profiles": "^0.2.2",
"@backstage/plugin-graphiql": "^0.2.2",
"@backstage/plugin-graphiql": "^0.2.3",
"@backstage/plugin-org": "^0.3.2",
"@backstage/plugin-jenkins": "^0.3.4",
"@backstage/plugin-kubernetes": "^0.3.3",
"@backstage/plugin-lighthouse": "^0.2.6",
"@backstage/plugin-newrelic": "^0.2.2",
"@backstage/plugin-pagerduty": "0.2.3",
"@backstage/plugin-pagerduty": "0.2.4",
"@backstage/plugin-register-component": "^0.2.5",
"@backstage/plugin-rollbar": "^0.2.7",
"@backstage/plugin-scaffolder": "^0.3.4",
"@backstage/plugin-scaffolder": "^0.3.5",
"@backstage/plugin-sentry": "^0.3.1",
"@backstage/plugin-search": "^0.2.4",
"@backstage/plugin-tech-radar": "^0.3.1",
"@backstage/plugin-techdocs": "^0.5.0",
"@backstage/plugin-techdocs": "^0.5.1",
"@backstage/plugin-user-settings": "^0.2.3",
"@backstage/plugin-welcome": "^0.2.3",
"@backstage/test-utils": "^0.1.5",
"@backstage/test-utils": "^0.1.6",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
+17
View File
@@ -1,5 +1,22 @@
# example-backend
## 0.2.8
### Patch Changes
- 7cfcd58ee: use node 14 for backend Dockerfile
- Updated dependencies [19554f6d6]
- Updated dependencies [33a82a713]
- Updated dependencies [5de26b9a6]
- Updated dependencies [30d6c78fb]
- Updated dependencies [5084e5039]
- Updated dependencies [a8573e53b]
- Updated dependencies [aed8f7f12]
- @backstage/plugin-scaffolder-backend@0.3.6
- @backstage/plugin-catalog-backend@0.5.1
- @backstage/plugin-techdocs-backend@0.5.0
- example-app@0.2.8
## 0.2.7
### Patch Changes
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.7",
"version": "0.2.8",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,18 +32,18 @@
"@backstage/config": "^0.1.2",
"@backstage/plugin-app-backend": "^0.3.3",
"@backstage/plugin-auth-backend": "^0.2.7",
"@backstage/plugin-catalog-backend": "^0.5.0",
"@backstage/plugin-catalog-backend": "^0.5.1",
"@backstage/plugin-graphql-backend": "^0.1.4",
"@backstage/plugin-kubernetes-backend": "^0.2.3",
"@backstage/plugin-proxy-backend": "^0.2.3",
"@backstage/plugin-rollbar-backend": "^0.1.5",
"@backstage/plugin-scaffolder-backend": "^0.3.5",
"@backstage/plugin-techdocs-backend": "^0.4.0",
"@backstage/plugin-scaffolder-backend": "^0.3.6",
"@backstage/plugin-techdocs-backend": "^0.5.0",
"@gitbeaker/node": "^25.2.0",
"@octokit/rest": "^18.0.0",
"azure-devops-node-api": "^10.1.1",
"dockerode": "^3.2.1",
"example-app": "^0.2.7",
"example-app": "^0.2.8",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
"knex": "^0.21.6",
@@ -53,7 +53,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.4.2",
"@backstage/cli": "^0.4.3",
"@types/dockerode": "^3.2.1",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
+13 -22
View File
@@ -15,12 +15,8 @@
*/
import {
createRouter,
DirectoryPreparer,
Preparers,
Generators,
TechdocsGenerator,
CommonGitPreparer,
UrlPreparer,
Publisher,
} from '@backstage/plugin-techdocs-backend';
import { PluginEnvironment } from '../types';
@@ -33,30 +29,25 @@ export default async function createPlugin({
reader,
}: PluginEnvironment) {
// Preparers are responsible for fetching source files for documentation.
const preparers = new Preparers();
const directoryPreparer = new DirectoryPreparer(logger);
preparers.register('dir', directoryPreparer);
const commonGitPreparer = new CommonGitPreparer(logger);
preparers.register('github', commonGitPreparer);
preparers.register('gitlab', commonGitPreparer);
preparers.register('azure/api', commonGitPreparer);
const urlPreparer = new UrlPreparer(reader, logger);
preparers.register('url', urlPreparer);
const preparers = await Preparers.fromConfig(config, {
logger,
reader,
});
// Generators are used for generating documentation sites.
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator(logger, config);
generators.register('techdocs', techdocsGenerator);
const generators = await Generators.fromConfig(config, {
logger,
});
// Publishers are used for
// Publisher is used for
// 1. Publishing generated files to storage
// 2. Fetching files from storage and passing them to TechDocs frontend.
const publisher = Publisher.fromConfig(config, logger, discovery);
const publisher = await Publisher.fromConfig(config, {
logger,
discovery,
});
// Docker client used by the generators.
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
const dockerClient = new Docker();
return await createRouter({
+7
View File
@@ -1,5 +1,12 @@
# @backstage/cli
## 0.4.3
### Patch Changes
- 19554f6d6: Added Github Actions for Create React App, and allow better imports of files inside a module when they're exposed using `files` in `package.json`
- 7d72f9b09: Fix for `app.listen.host` configuration not properly overriding listening host.
## 0.4.2
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.4.2",
"version": "0.4.3",
"private": false,
"publishConfig": {
"access": "public"
@@ -113,9 +113,9 @@
"devDependencies": {
"@backstage/backend-common": "^0.4.1",
"@backstage/config": "^0.1.2",
"@backstage/core": "^0.4.1",
"@backstage/core": "^0.4.2",
"@backstage/dev-utils": "^0.1.6",
"@backstage/test-utils": "^0.1.5",
"@backstage/test-utils": "^0.1.6",
"@backstage/theme": "^0.2.2",
"@types/diff": "^4.0.2",
"@types/fs-extra": "^9.0.1",
+9
View File
@@ -1,5 +1,14 @@
# @backstage/core-api
## 0.2.7
### Patch Changes
- d681db2b5: Fix for GitHub and SAML auth not properly updating session state when already logged in.
- 1dc445e89: Introduce new plugin extension API
- Updated dependencies [1dc445e89]
- @backstage/test-utils@0.1.6
## 0.2.6
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-api",
"description": "Internal Core API used by Backstage plugins and apps",
"version": "0.2.6",
"version": "0.2.7",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.2",
"@backstage/test-utils": "^0.1.5",
"@backstage/test-utils": "^0.1.6",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -43,7 +43,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.4.2",
"@backstage/cli": "^0.4.3",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
@@ -0,0 +1,78 @@
/*
* Copyright 2020 Spotify AB
*
* 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 { ReactNode, Children, isValidElement, Fragment } from 'react';
import { useRoutes } from 'react-router-dom';
type RouteObject = {
path: string;
element: JSX.Element;
children?: RouteObject[];
};
// Similar to the same function from react-router, this collects routes from the
// children, but only the first level of routes
function createRoutesFromChildren(children: ReactNode): RouteObject[] {
return Children.toArray(children)
.flatMap(child => {
if (!isValidElement(child)) {
return [];
}
const { children } = child.props;
if (child.type === Fragment) {
return createRoutesFromChildren(children);
}
let path = child.props.path as string | undefined;
// TODO(Rugvip): Work around plugins registering empty paths, remove once deprecated routes are gone
if (path === '') {
return [];
}
path = path?.replace(/\/\*$/, '') ?? '/';
return [
{
path,
element: child,
children: children && [
{
path: '/*',
element: children,
},
],
},
];
})
.sort((a, b) => b.path.localeCompare(a.path))
.map(obj => {
obj.path = obj.path === '/' ? '/' : `${obj.path}/*`;
return obj;
});
}
type BackstageRoutesProps = {
children: ReactNode;
};
export const BackstageRoutes = (
props: BackstageRoutesProps,
): JSX.Element | null => {
const routes = createRoutesFromChildren(props.children);
return useRoutes(routes);
};
+210 -44
View File
@@ -15,7 +15,11 @@
*/
import React, { PropsWithChildren } from 'react';
import { routePathCollector, routeParentCollector } from './collectors';
import {
routePathCollector,
routeParentCollector,
routeObjectCollector,
} from './collectors';
import {
traverseElementTree,
@@ -24,21 +28,22 @@ import {
} from '../extensions/traversal';
import { createRouteRef } from './RouteRef';
import { createPlugin } from '../plugin';
import { createRoutableExtension } from '../extensions';
import { attachComponentData, createRoutableExtension } from '../extensions';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import { RouteRef } from './types';
const mockConfig = () => ({ path: '/foo', title: 'Foo' });
const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => (
<>{children}</>
);
const plugin = createPlugin({ id: 'my-plugin' });
const ref1 = createRouteRef(mockConfig());
const ref2 = createRouteRef(mockConfig());
const ref3 = createRouteRef(mockConfig());
const ref4 = createRouteRef(mockConfig());
const ref5 = createRouteRef(mockConfig());
const ref1 = createRouteRef({ path: '/foo1', title: 'Foo' });
const ref2 = createRouteRef({ path: '/foo2', title: 'Foo' });
const ref3 = createRouteRef({ path: '/foo3', title: 'Foo' });
const ref4 = createRouteRef({ path: '/foo4', title: 'Foo' });
const ref5 = createRouteRef({ path: '/foo5', title: 'Foo' });
const refOrder = [ref1, ref2, ref3, ref4, ref5];
const Extension1 = plugin.provide(
createRoutableExtension({
@@ -71,6 +76,30 @@ const Extension5 = plugin.provide(
}),
);
const AggregationComponent = ({
children,
}: PropsWithChildren<{
path: string;
}>) => <>{children}</>;
attachComponentData(AggregationComponent, 'core.gatherMountPoints', true);
function sortedEntries<T>(map: Map<RouteRef, T>): [RouteRef, T][] {
return Array.from(map).sort(
([a], [b]) => refOrder.indexOf(a) - refOrder.indexOf(b),
);
}
function routeObj(path: string, refs: RouteRef[], children: any[] = []) {
return {
path: path,
caseSensitive: false,
element: null,
routeRefs: new Set(refs),
children: children,
};
}
describe('discovery', () => {
it('should collect routes', () => {
const list = [
@@ -109,33 +138,40 @@ describe('discovery', () => {
</MemoryRouter>
);
const { routes, routeParents } = traverseElementTree({
const { routes, routeParents, routeObjects } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routePathCollector,
routeParents: routeParentCollector,
routeObjects: routeObjectCollector,
},
});
expect(routes).toEqual(
new Map([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]),
);
expect(routeParents).toEqual(
new Map([
[ref1, undefined],
[ref2, ref1],
[ref3, ref2],
[ref4, undefined],
[ref5, ref1],
]),
);
expect(sortedEntries(routes)).toEqual([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]);
expect(sortedEntries(routeParents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, ref2],
[ref4, undefined],
[ref5, ref1],
]);
expect(routeObjects).toEqual([
routeObj(
'/foo',
[ref1],
[
routeObj('/bar/:id', [ref2], [routeObj('/baz', [ref3])]),
routeObj('/blop', [ref5]),
],
),
routeObj('/divsoup', [ref4]),
]);
});
it('should handle all react router Route patterns', () => {
@@ -168,24 +204,154 @@ describe('discovery', () => {
routeParents: routeParentCollector,
},
});
expect(routes).toEqual(
new Map([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]),
expect(sortedEntries(routes)).toEqual([
[ref1, '/foo'],
[ref2, '/bar/:id'],
[ref3, '/baz'],
[ref4, '/divsoup'],
[ref5, '/blop'],
]);
expect(sortedEntries(routeParents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, undefined],
[ref4, ref3],
[ref5, ref3],
]);
});
it('should use the route aggregator key to bind child routes to the same path', () => {
const root = (
<MemoryRouter>
<Routes>
<AggregationComponent path="/foo">
<Extension1 />
<div>
<Extension2 />
</div>
HELLO
</AggregationComponent>
<Extension3 path="/bar">
<AggregationComponent path="/baz">
<Extension4>
<Extension5 />
</Extension4>
</AggregationComponent>
</Extension3>
</Routes>
</MemoryRouter>
);
expect(routeParents).toEqual(
new Map([
[ref1, undefined],
[ref2, ref1],
[ref3, undefined],
[ref4, ref3],
[ref5, ref3],
]),
const { routes, routeParents, routeObjects } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routePathCollector,
routeParents: routeParentCollector,
routeObjects: routeObjectCollector,
},
});
expect(sortedEntries(routes)).toEqual([
[ref1, '/foo'],
[ref2, '/foo'],
[ref3, '/bar'],
[ref4, '/baz'],
[ref5, '/baz'],
]);
expect(sortedEntries(routeParents)).toEqual([
[ref1, undefined],
[ref2, undefined],
[ref3, undefined],
[ref4, ref3],
[ref5, ref3],
]);
expect(routeObjects).toEqual([
routeObj('/foo', [ref1, ref2]),
routeObj('/bar', [ref3], [routeObj('/baz', [ref4, ref5])]),
]);
});
it('should use the route aggregator but stop when encountering explicit path', () => {
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<AggregationComponent path="/bar">
<Extension2>
<Extension3 path="/baz">
<Extension4 path="/blop" />
</Extension3>
<Extension5 />
</Extension2>
</AggregationComponent>
</Extension1>
</Routes>
</MemoryRouter>
);
const { routes, routeParents, routeObjects } = traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routePathCollector,
routeParents: routeParentCollector,
routeObjects: routeObjectCollector,
},
});
expect(sortedEntries(routes)).toEqual([
[ref1, '/foo'],
[ref2, '/bar'],
[ref3, '/baz'],
[ref4, '/blop'],
[ref5, '/bar'],
]);
expect(sortedEntries(routeParents)).toEqual([
[ref1, undefined],
[ref2, ref1],
[ref3, ref1],
[ref4, ref3],
[ref5, ref1],
]);
expect(routeObjects).toEqual([
routeObj(
'/foo',
[ref1],
[
routeObj(
'/bar',
[ref2, ref5],
[routeObj('/baz', [ref3], [routeObj('/blop', [ref4])])],
),
],
),
]);
});
it('should stop gathering mount points after encountering explicit path', () => {
const root = (
<MemoryRouter>
<Routes>
<Extension1 path="/foo">
<AggregationComponent path="/bar">
<Extension2 path="/baz">
<Extension3 />
</Extension2>
</AggregationComponent>
</Extension1>
</Routes>
</MemoryRouter>
);
expect(() => {
traverseElementTree({
root,
discoverers: [childDiscoverer, routeElementDiscoverer],
collectors: {
routes: routePathCollector,
routeParents: routeParentCollector,
},
});
}).toThrow('Mounted routable extension must have a path');
});
it('should not visit the same element twice', () => {
+80 -17
View File
@@ -32,25 +32,48 @@ function getMountPoint(node: ReactElement): RouteRef | undefined {
export const routePathCollector = createCollector(
() => new Map<RouteRef, string>(),
(acc, node, parent) => {
(acc, node, parent, ctxPath: string | undefined) => {
// The context path is used during mount point gathering to assign the same path
// to all discovered mount points
let currentCtxPath = ctxPath;
if (parent?.props.element === node) {
return;
return currentCtxPath;
}
// Start gathering mount points when we encounter a mount point gathering flag
if (getComponentData<boolean>(node, 'core.gatherMountPoints')) {
const path: string | undefined = node.props?.path;
if (!path) {
throw new Error('Mount point gatherer must have a path');
}
currentCtxPath = path;
}
const routeRef = getMountPoint(node);
if (routeRef) {
const path: string | undefined = node.props?.path;
let path: string | undefined = node.props?.path;
// If we're gathering mount points we use the context path as out path, unless
// the element has its own path, in which case we use that instead and stop gathering
if (currentCtxPath) {
if (path) {
currentCtxPath = undefined;
} else {
path = currentCtxPath;
}
}
if (!path) {
throw new Error('Mounted routable extension must have a path');
}
acc.set(routeRef, path);
}
return currentCtxPath;
},
);
export const routeParentCollector = createCollector(
() => new Map<RouteRef, RouteRef | undefined>(),
(acc, node, parent, parentRouteRef?: RouteRef) => {
(acc, node, parent, parentRouteRef?: RouteRef | { sticky: RouteRef }) => {
if (parent?.props.element === node) {
return parentRouteRef;
}
@@ -59,8 +82,27 @@ export const routeParentCollector = createCollector(
const routeRef = getMountPoint(node);
if (routeRef) {
acc.set(routeRef, parentRouteRef);
nextParent = routeRef;
// "sticky" route ref is when we've encountered a mount point gatherer, and we want a
// mount points beneath it to have the same parent, regardless of internal structure
if (parentRouteRef && 'sticky' in parentRouteRef) {
acc.set(routeRef, parentRouteRef.sticky);
// When we encounter a mount point with an explicit path, we stop gathering
// mount points withing the children and remove the sticky state
if (node.props?.path) {
nextParent = routeRef;
} else {
nextParent = parentRouteRef;
}
} else {
acc.set(routeRef, parentRouteRef);
nextParent = routeRef;
}
}
// Mount point gatherers are marked as "sticky"
if (getComponentData<boolean>(node, 'core.gatherMountPoints')) {
return { sticky: nextParent };
}
return nextParent;
@@ -69,9 +111,10 @@ export const routeParentCollector = createCollector(
export const routeObjectCollector = createCollector(
() => Array<BackstageRouteObject>(),
(acc, node, parent, parentChildArr: BackstageRouteObject[] = acc) => {
(acc, node, parent, parentObj: BackstageRouteObject | undefined) => {
const parentChildren = parentObj?.children ?? acc;
if (parent?.props.element === node) {
return parentChildArr;
return parentObj;
}
const path: string | undefined = node.props?.path;
@@ -79,20 +122,40 @@ export const routeObjectCollector = createCollector(
const routeRef = getMountPoint(node);
if (routeRef) {
const children: BackstageRouteObject[] = [];
if (!path) {
throw new Error(`No path found for mount point ${routeRef}`);
if (path) {
const newObject: BackstageRouteObject = {
caseSensitive,
path,
element: null,
routeRefs: new Set([routeRef]),
children: [],
};
parentChildren.push(newObject);
return newObject;
}
parentChildArr.push({
parentObj?.routeRefs.add(routeRef);
}
const isGatherer = getComponentData<boolean>(
node,
'core.gatherMountPoints',
);
if (isGatherer) {
if (!path) {
throw new Error('Mount point gatherer must have a path');
}
const newObject: BackstageRouteObject = {
caseSensitive,
path,
element: null,
routeRef,
children,
});
return children;
routeRefs: new Set(),
children: [],
};
parentChildren.push(newObject);
return newObject;
}
return parentChildArr;
return parentObj;
},
);
+2 -2
View File
@@ -60,8 +60,8 @@ class RouteResolver {
currentRouteRef;
currentRouteRef = this.routeParents.get(currentRouteRef)
) {
matchIndex = match.findIndex(
m => (m.route as BackstageRouteObject).routeRef === currentRouteRef,
matchIndex = match.findIndex(m =>
(m.route as BackstageRouteObject).routeRefs.has(currentRouteRef!),
);
if (matchIndex !== -1) {
break;
+1
View File
@@ -21,5 +21,6 @@ export type {
ConcreteRoute,
MutableRouteRef,
} from './types';
export { BackstageRoutes } from './BackstageRoutes';
export { createRouteRef } from './RouteRef';
export { useRouteRef } from './hooks';
+1 -1
View File
@@ -62,5 +62,5 @@ export interface BackstageRouteObject {
children?: BackstageRouteObject[];
element: React.ReactNode;
path: string;
routeRef: AnyRouteRef;
routeRefs: Set<AnyRouteRef>;
}
+10
View File
@@ -1,5 +1,15 @@
# @backstage/core
## 0.4.2
### Patch Changes
- 1dc445e89: Update to use new plugin extension API
- 342270e4d: Create AboutCard in core and use it in pagerduty and catalog plugin
- Updated dependencies [d681db2b5]
- Updated dependencies [1dc445e89]
- @backstage/core-api@0.2.7
## 0.4.1
### Patch Changes
+4 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core",
"description": "Core API used by Backstage plugins and apps",
"version": "0.4.1",
"version": "0.4.2",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,7 @@
},
"dependencies": {
"@backstage/config": "^0.1.2",
"@backstage/core-api": "^0.2.6",
"@backstage/core-api": "^0.2.7",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -65,8 +65,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.4.2",
"@backstage/test-utils": "^0.1.5",
"@backstage/cli": "^0.4.3",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
+43
View File
@@ -1,5 +1,48 @@
# @backstage/create-app
## 0.3.0
### Minor Changes
- 0101c7a16: Add search plugin to default template for CLI created apps
### Patch Changes
- a8573e53b: techdocs-backend: Simplified file, removing individual preparers and generators.
techdocs-backend: UrlReader is now available to use in preparers.
In your Backstage app, `packages/backend/plugins/techdocs.ts` file has now been simplified,
to remove registering individual preparers and generators.
Please update the file when upgrading the version of `@backstage/plugin-techdocs-backend` package.
```typescript
const preparers = await Preparers.fromConfig(config, {
logger,
reader,
});
const generators = await Generators.fromConfig(config, {
logger,
});
const publisher = await Publisher.fromConfig(config, {
logger,
discovery,
});
```
You should be able to remove unnecessary imports, and just do
```typescript
import {
createRouter,
Preparers,
Generators,
Publisher,
} from '@backstage/plugin-techdocs-backend';
```
## 0.2.5
### Patch Changes
+11 -11
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "Create app package for Backstage",
"version": "0.2.5",
"version": "0.3.0",
"private": false,
"publishConfig": {
"access": "public"
@@ -39,29 +39,29 @@
"devDependencies": {
"@backstage/backend-common": "^0.4.1",
"@backstage/catalog-model": "^0.6.0",
"@backstage/cli": "^0.4.2",
"@backstage/cli": "^0.4.3",
"@backstage/config": "^0.1.2",
"@backstage/core": "^0.4.1",
"@backstage/core": "^0.4.2",
"@backstage/plugin-api-docs": "^0.4.1",
"@backstage/plugin-app-backend": "^0.3.3",
"@backstage/plugin-auth-backend": "^0.2.7",
"@backstage/plugin-catalog": "^0.2.7",
"@backstage/plugin-catalog-backend": "^0.5.0",
"@backstage/plugin-catalog": "^0.2.8",
"@backstage/plugin-catalog-backend": "^0.5.1",
"@backstage/plugin-circleci": "^0.2.5",
"@backstage/plugin-explore": "^0.2.2",
"@backstage/plugin-github-actions": "^0.2.5",
"@backstage/plugin-github-actions": "^0.2.6",
"@backstage/plugin-lighthouse": "^0.2.6",
"@backstage/plugin-proxy-backend": "^0.2.3",
"@backstage/plugin-register-component": "^0.2.5",
"@backstage/plugin-rollbar-backend": "^0.1.5",
"@backstage/plugin-scaffolder": "^0.3.4",
"@backstage/plugin-scaffolder": "^0.3.5",
"@backstage/plugin-search": "^0.2.4",
"@backstage/plugin-scaffolder-backend": "^0.3.5",
"@backstage/plugin-scaffolder-backend": "^0.3.6",
"@backstage/plugin-tech-radar": "^0.3.1",
"@backstage/plugin-techdocs": "^0.5.0",
"@backstage/plugin-techdocs-backend": "^0.4.0",
"@backstage/plugin-techdocs": "^0.5.1",
"@backstage/plugin-techdocs-backend": "^0.5.0",
"@backstage/plugin-user-settings": "^0.2.3",
"@backstage/test-utils": "^0.1.5",
"@backstage/test-utils": "^0.1.6",
"@backstage/theme": "^0.2.2",
"@types/fs-extra": "^9.0.1",
"@types/inquirer": "^7.3.1",
@@ -1,10 +1,7 @@
import {
createRouter,
DirectoryPreparer,
Preparers,
Generators,
TechdocsGenerator,
CommonGitPreparer,
Publisher,
} from '@backstage/plugin-techdocs-backend';
import { PluginEnvironment } from '../types';
@@ -14,22 +11,28 @@ export default async function createPlugin({
logger,
config,
discovery,
reader,
}: PluginEnvironment) {
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator(logger, config);
// Preparers are responsible for fetching source files for documentation.
const preparers = await Preparers.fromConfig(config, {
logger,
reader,
});
generators.register('techdocs', techdocsGenerator);
// Generators are used for generating documentation sites.
const generators = await Generators.fromConfig(config, {
logger,
});
const preparers = new Preparers();
const directoryPreparer = new DirectoryPreparer(logger);
const commonGitPreparer = new CommonGitPreparer(logger);
preparers.register('dir', directoryPreparer);
preparers.register('github', commonGitPreparer);
preparers.register('gitlab', commonGitPreparer);
const publisher = Publisher.fromConfig(config, logger, discovery);
// Publisher is used for
// 1. Publishing generated files to storage
// 2. Fetching files from storage and passing them to TechDocs frontend.
const publisher = await Publisher.fromConfig(config, {
logger,
discovery,
});
// Docker client (conditionally) used by the generators, based on techdocs.generators config.
const dockerClient = new Docker();
return await createRouter({
+39
View File
@@ -1,5 +1,44 @@
# @backstage/techdocs-common
## 0.3.0
### Minor Changes
- a8573e53b: techdocs-backend: Simplified file, removing individual preparers and generators.
techdocs-backend: UrlReader is now available to use in preparers.
In your Backstage app, `packages/backend/plugins/techdocs.ts` file has now been simplified,
to remove registering individual preparers and generators.
Please update the file when upgrading the version of `@backstage/plugin-techdocs-backend` package.
```typescript
const preparers = await Preparers.fromConfig(config, {
logger,
reader,
});
const generators = await Generators.fromConfig(config, {
logger,
});
const publisher = await Publisher.fromConfig(config, {
logger,
discovery,
});
```
You should be able to remove unnecessary imports, and just do
```typescript
import {
createRouter,
Preparers,
Generators,
Publisher,
} from '@backstage/plugin-techdocs-backend';
```
## 0.2.0
### Minor Changes
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/techdocs-common",
"description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli",
"version": "0.2.0",
"version": "0.3.0",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -54,7 +54,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.4.2",
"@backstage/cli": "^0.4.3",
"@types/fs-extra": "^9.0.5",
"@types/git-url-parse": "^9.0.0",
"@types/js-yaml": "^3.12.5",
@@ -14,18 +14,32 @@
* limitations under the License.
*/
import { Logger } from 'winston';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { TechdocsGenerator } from '.';
import {
GeneratorBase,
SupportedGeneratorKey,
GeneratorBuilder,
} from './types';
import { Entity } from '@backstage/catalog-model';
import { getGeneratorKey } from './helpers';
export class Generators implements GeneratorBuilder {
private generatorMap = new Map<SupportedGeneratorKey, GeneratorBase>();
static async fromConfig(
config: Config,
{ logger }: { logger: Logger },
): Promise<GeneratorBuilder> {
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator(logger, config);
generators.register('techdocs', techdocsGenerator);
return generators;
}
register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) {
this.generatorMap.set(generatorKey, generator);
}
@@ -13,14 +13,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types';
import { Logger } from 'winston';
import { UrlReader } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { DirectoryPreparer, CommonGitPreparer, UrlPreparer } from '.';
import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types';
import { parseReferenceAnnotation } from '../../helpers';
type factoryOptions = {
logger: Logger;
reader: UrlReader;
};
export class Preparers implements PreparerBuilder {
private preparerMap = new Map<RemoteProtocol, PreparerBase>();
static async fromConfig(
// @ts-ignore
// Config not used now, but will be used in urlPreparer when it starts using
// @backstage/integration to get the tokens for providers.
config: Config,
{ logger, reader }: factoryOptions,
): Promise<PreparerBuilder> {
const preparers = new Preparers();
const directoryPreparer = new DirectoryPreparer(logger);
preparers.register('dir', directoryPreparer);
const commonGitPreparer = new CommonGitPreparer(logger);
preparers.register('github', commonGitPreparer);
preparers.register('gitlab', commonGitPreparer);
preparers.register('azure/api', commonGitPreparer);
const urlPreparer = new UrlPreparer(reader, logger);
preparers.register('url', urlPreparer);
return preparers;
}
register(protocol: RemoteProtocol, preparer: PreparerBase) {
this.preparerMap.set(protocol, preparer);
}
@@ -23,24 +23,27 @@ import { LocalPublish } from './local';
import { GoogleGCSPublish } from './googleStorage';
const logger = getVoidLogger();
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
const discovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest.fn().mockResolvedValueOnce('http://localhost:7000'),
getExternalBaseUrl: jest.fn(),
};
describe('Publisher', () => {
it('should create local publisher by default', () => {
it('should create local publisher by default', async () => {
const mockConfig = new ConfigReader({
techdocs: {
requestUrl: 'http://localhost:7000',
},
});
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
const publisher = await Publisher.fromConfig(mockConfig, {
logger,
discovery,
});
expect(publisher).toBeInstanceOf(LocalPublish);
});
it('should create local publisher from config', () => {
it('should create local publisher from config', async () => {
const mockConfig = new ConfigReader({
techdocs: {
requestUrl: 'http://localhost:7000',
@@ -50,11 +53,14 @@ describe('Publisher', () => {
},
});
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
const publisher = await Publisher.fromConfig(mockConfig, {
logger,
discovery,
});
expect(publisher).toBeInstanceOf(LocalPublish);
});
it('should create google gcs publisher from config', () => {
it('should create google gcs publisher from config', async () => {
const mockConfig = new ConfigReader({
techdocs: {
requestUrl: 'http://localhost:7000',
@@ -69,7 +75,10 @@ describe('Publisher', () => {
},
});
const publisher = Publisher.fromConfig(mockConfig, logger, testDiscovery);
const publisher = await Publisher.fromConfig(mockConfig, {
logger,
discovery,
});
expect(publisher).toBeInstanceOf(GoogleGCSPublish);
});
});
@@ -21,16 +21,20 @@ import { PublisherType, PublisherBase } from './types';
import { LocalPublish } from './local';
import { GoogleGCSPublish } from './googleStorage';
type factoryOptions = {
logger: Logger;
discovery: PluginEndpointDiscovery;
};
/**
* Factory class to create a TechDocs publisher based on defined publisher type in app config.
* Uses `techdocs.publisher.type`.
*/
export class Publisher {
static fromConfig(
static async fromConfig(
config: Config,
logger: Logger,
discovery: PluginEndpointDiscovery,
): PublisherBase {
{ logger, discovery }: factoryOptions,
): Promise<PublisherBase> {
const publisherType = (config.getOptionalString(
'techdocs.publisher.type',
) ?? 'local') as PublisherType;
+9
View File
@@ -1,5 +1,14 @@
# @backstage/test-utils
## 0.1.6
### Patch Changes
- 1dc445e89: Update to use new plugin extension API
- Updated dependencies [d681db2b5]
- Updated dependencies [1dc445e89]
- @backstage/core-api@0.2.7
## 0.1.5
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
"version": "0.1.5",
"version": "0.1.6",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-api": "^0.2.5",
"@backstage/core-api": "^0.2.7",
"@backstage/test-utils-core": "^0.1.1",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
@@ -45,7 +45,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.4.1",
"@backstage/cli": "^0.4.3",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0"
},