Add titles to codeblocks and switch from diff codeblock to language codeblock
Signed-off-by: Paul Schultz <pschultz@pobox.com>
This commit is contained in:
@@ -23,8 +23,8 @@ yarn add --cwd packages/app @backstage/plugin-kubernetes
|
||||
Once the package has been installed, you need to import the plugin in your app
|
||||
by adding the "Kubernetes" tab to the respective catalog pages.
|
||||
|
||||
```tsx
|
||||
// In packages/app/src/components/catalog/EntityPage.tsx
|
||||
```tsx title="packages/app/src/components/catalog/EntityPage.tsx"
|
||||
/* highlight-add-next-line */
|
||||
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
|
||||
|
||||
// You can add the tab to any number of pages, the service page is shown as an
|
||||
@@ -32,9 +32,13 @@ import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
|
||||
const serviceEntityPage = (
|
||||
<EntityLayout>
|
||||
{/* other tabs... */}
|
||||
{/* highlight-add-start */}
|
||||
<EntityLayout.Route path="/kubernetes" title="Kubernetes">
|
||||
<EntityKubernetesContent refreshIntervalMs={30000} />
|
||||
</EntityLayout.Route>
|
||||
{/* highlight-add-end */}
|
||||
</EntityLayout>
|
||||
)
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
@@ -57,8 +61,7 @@ yarn add --cwd packages/backend @backstage/plugin-kubernetes-backend
|
||||
Create a file called `kubernetes.ts` inside `packages/backend/src/plugins/` and
|
||||
add the following:
|
||||
|
||||
```typescript
|
||||
// In packages/backend/src/plugins/kubernetes.ts
|
||||
```ts title="packages/backend/src/plugins/kubernetes.ts"
|
||||
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
@@ -81,14 +84,17 @@ And import the plugin to `packages/backend/src/index.ts`. There are three lines
|
||||
of code you'll need to add, and they should be added near similar code in your
|
||||
existing Backstage backend.
|
||||
|
||||
```typescript
|
||||
// In packages/backend/src/index.ts
|
||||
```typescript title="packages/backend/src/index.ts"
|
||||
// ..
|
||||
/* highlight-add-next-line */
|
||||
import kubernetes from './plugins/kubernetes';
|
||||
// ...
|
||||
|
||||
async function main() {
|
||||
// ...
|
||||
/* highlight-add-next-line */
|
||||
const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
|
||||
// ...
|
||||
/* highlight-add-next-line */
|
||||
apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
|
||||
```
|
||||
|
||||
@@ -104,52 +110,66 @@ don't work for your use-case, it is possible to implement a custom
|
||||
|
||||
Change the following in `packages/backend/src/plugins/kubernetes.ts`:
|
||||
|
||||
```diff
|
||||
-import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
|
||||
+import {
|
||||
+ ClusterDetails,
|
||||
+ KubernetesBuilder,
|
||||
+ KubernetesClustersSupplier,
|
||||
+} from '@backstage/plugin-kubernetes-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
+import { Duration } from 'luxon';
|
||||
+
|
||||
+export class CustomClustersSupplier implements KubernetesClustersSupplier {
|
||||
+ constructor(private clusterDetails: ClusterDetails[] = []) {}
|
||||
+
|
||||
+ static create(refreshInterval: Duration) {
|
||||
+ const clusterSupplier = new CustomClustersSupplier();
|
||||
+ // setup refresh, e.g. using a copy of https://github.com/backstage/backstage/blob/master/plugins/search-backend-node/src/runPeriodically.ts
|
||||
+ runPeriodically(
|
||||
+ () => clusterSupplier.refreshClusters(),
|
||||
+ refreshInterval.toMillis(),
|
||||
+ );
|
||||
+ return clusterSupplier;
|
||||
+ }
|
||||
+
|
||||
+ async refreshClusters(): Promise<void> {
|
||||
+ this.clusterDetails = []; // fetch from somewhere
|
||||
+ }
|
||||
+
|
||||
+ async getClusters(): Promise<ClusterDetails[]> {
|
||||
+ return this.clusterDetails;
|
||||
+ }
|
||||
+}
|
||||
```ts title="packages/backend/src/plugins/kubernetes.ts"
|
||||
import {
|
||||
/* highlight-add-next-line */
|
||||
ClusterDetails,
|
||||
KubernetesBuilder,
|
||||
/* highlight-add-next-line */
|
||||
KubernetesClustersSupplier,
|
||||
} from '@backstage/plugin-kubernetes-backend';
|
||||
import { Router } from 'express';
|
||||
import { PluginEnvironment } from '../types';
|
||||
/* highlight-add-next-line */
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
- const { router } = await KubernetesBuilder.createBuilder({
|
||||
+ const builder = await KubernetesBuilder.createBuilder({
|
||||
/* highlight-add-start */
|
||||
export class CustomClustersSupplier implements KubernetesClustersSupplier {
|
||||
constructor(private clusterDetails: ClusterDetails[] = []) {}
|
||||
|
||||
static create(refreshInterval: Duration) {
|
||||
const clusterSupplier = new CustomClustersSupplier();
|
||||
// setup refresh, e.g. using a copy of https://github.com/backstage/backstage/blob/master/plugins/search-backend-node/src/runPeriodically.ts
|
||||
runPeriodically(
|
||||
() => clusterSupplier.refreshClusters(),
|
||||
refreshInterval.toMillis(),
|
||||
);
|
||||
return clusterSupplier;
|
||||
}
|
||||
|
||||
async refreshClusters(): Promise<void> {
|
||||
this.clusterDetails = []; // fetch from somewhere
|
||||
}
|
||||
|
||||
async getClusters(): Promise<ClusterDetails[]> {
|
||||
return this.clusterDetails;
|
||||
}
|
||||
}
|
||||
/* highlight-add-end */
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
|
||||
/* highlight-remove-next-line */
|
||||
const { router } = await KubernetesBuilder.createBuilder({
|
||||
/* highlight-add-next-line */
|
||||
const builder = await KubernetesBuilder.createBuilder({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
- }).build();
|
||||
+ });
|
||||
+ builder.setClusterSupplier(
|
||||
+ CustomClustersSupplier.create(Duration.fromObject({ minutes: 60 })),
|
||||
+ );
|
||||
+ const { router } = await builder.build();
|
||||
/* highlight-remove-next-line */
|
||||
}).build();
|
||||
/* highlight-add-start */
|
||||
});
|
||||
builder.setClusterSupplier(
|
||||
CustomClustersSupplier.create(Duration.fromObject({ minutes: 60 })),
|
||||
);
|
||||
const { router } = await builder.build();
|
||||
/* highlight-add-end */
|
||||
|
||||
// ..
|
||||
return router;
|
||||
}
|
||||
```
|
||||
|
||||
## Running Backstage locally
|
||||
|
||||
@@ -20,30 +20,30 @@ to do that in two steps.
|
||||
[interface](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L31)
|
||||
according to your needs.
|
||||
|
||||
```typescript
|
||||
export class SearchClient implements SearchApi {
|
||||
// your implementation
|
||||
}
|
||||
```
|
||||
```typescript
|
||||
export class SearchClient implements SearchApi {
|
||||
// your implementation
|
||||
}
|
||||
```
|
||||
|
||||
2. Override the API ref `searchApiRef` with your new implemented API in the
|
||||
`App.tsx` using `ApiFactories`.
|
||||
[Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis).
|
||||
|
||||
```typescript
|
||||
const app = createApp({
|
||||
apis: [
|
||||
// SearchApi
|
||||
createApiFactory({
|
||||
api: searchApiRef,
|
||||
deps: { discovery: discoveryApiRef },
|
||||
factory({ discovery }) {
|
||||
return new SearchClient({ discoveryApi: discovery });
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
```typescript
|
||||
const app = createApp({
|
||||
apis: [
|
||||
// SearchApi
|
||||
createApiFactory({
|
||||
api: searchApiRef,
|
||||
deps: { discovery: discoveryApiRef },
|
||||
factory({ discovery }) {
|
||||
return new SearchClient({ discoveryApi: discovery });
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
## How to index TechDocs documents
|
||||
|
||||
@@ -63,35 +63,35 @@ getting started guide.
|
||||
1. Import the `DefaultTechDocsCollatorFactory` from
|
||||
`@backstage/plugin-techdocs-backend`.
|
||||
|
||||
```typescript
|
||||
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
|
||||
```
|
||||
```typescript
|
||||
import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
|
||||
```
|
||||
|
||||
2. If there isn't an existing schedule you'd like to run the collator on, be
|
||||
sure to create it first. Something like...
|
||||
|
||||
```typescript
|
||||
import { Duration } from 'luxon';
|
||||
```typescript
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
|
||||
frequency: Duration.fromObject({ seconds: 600 }),
|
||||
timeout: Duration.fromObject({ seconds: 900 }),
|
||||
initialDelay: Duration.fromObject({ seconds: 3 }),
|
||||
});
|
||||
```
|
||||
const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({
|
||||
frequency: Duration.fromObject({ seconds: 600 }),
|
||||
timeout: Duration.fromObject({ seconds: 900 }),
|
||||
initialDelay: Duration.fromObject({ seconds: 3 }),
|
||||
});
|
||||
```
|
||||
|
||||
3. Register the `DefaultTechDocsCollatorFactory` with the IndexBuilder.
|
||||
|
||||
```typescript
|
||||
indexBuilder.addCollator({
|
||||
schedule: every10MinutesSchedule,
|
||||
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
logger: env.logger,
|
||||
tokenManager: env.tokenManager,
|
||||
}),
|
||||
});
|
||||
```
|
||||
```typescript
|
||||
indexBuilder.addCollator({
|
||||
schedule: every10MinutesSchedule,
|
||||
factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
logger: env.logger,
|
||||
tokenManager: env.tokenManager,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
You should now have your TechDocs documents indexed to your search engine of
|
||||
choice!
|
||||
@@ -101,14 +101,14 @@ searching, you can update your `SearchPage.tsx` file in
|
||||
`packages/app/src/components/search` by adding `techdocs` to the list of values
|
||||
of the `SearchType` component.
|
||||
|
||||
```tsx
|
||||
```tsx title="packages/app/src/components/search/SearchPage.tsx"
|
||||
<Paper className={classes.filters}>
|
||||
<SearchType
|
||||
values={['techdocs', 'software-catalog']}
|
||||
name="type"
|
||||
defaultValue="software-catalog"
|
||||
/>
|
||||
...
|
||||
{/* ... */}
|
||||
</Paper>
|
||||
```
|
||||
|
||||
@@ -124,9 +124,7 @@ You can either just simply amend default behaviour, or even to write completely
|
||||
|
||||
> `authorization` and `location` cannot be modified via a `entityTransformer`, `location` can be modified only through `locationTemplate`.
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/search.ts
|
||||
|
||||
```ts title="packages/backend/src/plugins/search.ts"
|
||||
const entityTransformer: CatalogCollatorEntityTransformer = (entity: Entity) => {
|
||||
if (entity.kind === 'SomeKind') {
|
||||
return {
|
||||
@@ -145,7 +143,8 @@ indexBuilder.addCollator({
|
||||
collator: DefaultCatalogCollatorFactory.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
tokenManager: env.tokenManager,
|
||||
+ entityTransformer,
|
||||
/* highlight-add-next-line */
|
||||
entityTransformer,
|
||||
}),
|
||||
});
|
||||
```
|
||||
@@ -166,17 +165,17 @@ exactly what's available to search, (or a [Decorator](./concepts.md#decorators)
|
||||
to filter things out here and there), but the `DefaultCatalogCollator` that's
|
||||
provided by `@backstage/plugin-catalog-backend` offers some configuration too!
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/search.ts
|
||||
|
||||
```ts title="packages/backend/src/plugins/search.ts"
|
||||
indexBuilder.addCollator({
|
||||
defaultRefreshIntervalSeconds: 600,
|
||||
collator: DefaultCatalogCollator.fromConfig(env.config, {
|
||||
discovery: env.discovery,
|
||||
tokenManager: env.tokenManager,
|
||||
+ filter: {
|
||||
+ kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
|
||||
+ },
|
||||
/* highlight-add-start */
|
||||
filter: {
|
||||
kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'],
|
||||
},
|
||||
/* highlight-add-end */
|
||||
}),
|
||||
});
|
||||
```
|
||||
@@ -194,7 +193,7 @@ to create an override with your preferred styling.
|
||||
|
||||
For example, the following will result in highlighted terms to be bold & underlined:
|
||||
|
||||
```jsx
|
||||
```tsx
|
||||
const highlightOverride = {
|
||||
BackstageHighlightedSearchResultText: {
|
||||
highlight: {
|
||||
@@ -207,10 +206,6 @@ const highlightOverride = {
|
||||
};
|
||||
```
|
||||
|
||||
[obj-mode]: https://nodejs.org/dist/latest-v16.x/docs/api/stream.html#stream_object_mode
|
||||
[read-stream]: https://nodejs.org/dist/latest-v16.x/docs/api/stream.html#readable-streams
|
||||
[async-gen]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of#iterating_over_async_generators
|
||||
|
||||
## How to render search results using extensions
|
||||
|
||||
Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages:
|
||||
@@ -219,8 +214,7 @@ Extensions for search results let you customize components used to render search
|
||||
|
||||
Using the example below, you can provide an extension to be used as a default result item:
|
||||
|
||||
```tsx
|
||||
// plugins/your-plugin/src/plugin.ts
|
||||
```tsx title="plugins/your-plugin/src/plugin.ts"
|
||||
import { createPlugin } from '@backstage/core-plugin-api';
|
||||
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
|
||||
|
||||
@@ -251,8 +245,7 @@ export const YourSearchResultListItemExtension: (
|
||||
|
||||
Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not:
|
||||
|
||||
```tsx
|
||||
// plugins/your-plugin/src/plugin.ts
|
||||
```tsx title="plugins/your-plugin/src/plugin.ts"
|
||||
import { createPlugin } from '@backstage/core-plugin-api';
|
||||
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react';
|
||||
|
||||
@@ -271,8 +264,7 @@ export const YourSearchResultListItemExtension = plugin.provide(
|
||||
|
||||
Remember to export your new extension:
|
||||
|
||||
```tsx
|
||||
// plugins/your-plugin/src/index.ts
|
||||
```tsx title="plugins/your-plugin/src/index.ts"
|
||||
export { YourSearchResultListItem } from './plugin.ts';
|
||||
```
|
||||
|
||||
@@ -282,8 +274,7 @@ For more details, see the [createSearchResultListItemExtension](https://backstag
|
||||
|
||||
Now that you know how a search result item is provided, let's finally see how they can be used, for example, to compose a page in your application:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/searchPage.tsx
|
||||
```tsx title="packages/app/src/components/searchPage.tsx"
|
||||
import React from 'react';
|
||||
|
||||
import { Grid, Paper } from '@material-ui/core';
|
||||
@@ -337,8 +328,7 @@ export const searchPage = <SearchPage />;
|
||||
|
||||
As another example, here's a search modal that renders results with extensions:
|
||||
|
||||
```tsx
|
||||
// packages/app/src/components/searchModal.tsx
|
||||
```tsx title="packages/app/src/components/searchModal.tsx"
|
||||
import React from 'react';
|
||||
|
||||
import { DialogContent, DialogTitle, Paper } from '@material-ui/core';
|
||||
@@ -367,7 +357,7 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => (
|
||||
<CatalogSearchResultListItem icon={<CatalogIcon />} />
|
||||
<TechDocsSearchResultListItem icon={<DocsIcon />} />
|
||||
<ToolSearchResultListItem icon={<BuildIcon />} />
|
||||
{/* As a "default" extension, it does not define a predicate function,
|
||||
{/* As a "default" extension, it does not define a predicate function,
|
||||
so it must be the last child to render results that do not match the above extensions */}
|
||||
<YourSearchResultListItem />
|
||||
</SearchResult>
|
||||
|
||||
@@ -142,21 +142,22 @@ export const EntitySecurityTierPicker = () => {
|
||||
|
||||
Now we can add the component to `CustomCatalogPage`:
|
||||
|
||||
```diff
|
||||
```tsx
|
||||
export const CustomCatalogPage = ({
|
||||
columns,
|
||||
actions,
|
||||
initiallySelectedFilter = 'owned',
|
||||
}: CatalogPageProps) => {
|
||||
return (
|
||||
...
|
||||
{/* ... */}
|
||||
<EntityListProvider>
|
||||
<CatalogFilterLayout>
|
||||
<CatalogFilterLayout.Filters>
|
||||
<EntityKindPicker initialFilter="component" hidden />
|
||||
<EntityTypePicker />
|
||||
<UserListPicker initialFilter={initiallySelectedFilter} />
|
||||
+ <EntitySecurityTierPicker />
|
||||
{/* highlight-add-next-line */}
|
||||
<EntitySecurityTierPicker />
|
||||
<EntityTagPicker />
|
||||
<CatalogFilterLayout.Filters>
|
||||
<CatalogFilterLayout.Content>
|
||||
@@ -164,21 +165,26 @@ export const CustomCatalogPage = ({
|
||||
</CatalogFilterLayout.Content>
|
||||
</CatalogFilterLayout>
|
||||
</EntityListProvider>
|
||||
...
|
||||
{/* ... */}
|
||||
};
|
||||
```
|
||||
|
||||
Finally, we can apply our new `CustomCatalogPage`.
|
||||
|
||||
```diff
|
||||
# packages/app/src/App.tsx
|
||||
```tsx title="packages/app/src/App.tsx"
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
<Navigate key="/" to="catalog" />
|
||||
- <Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
+ <Route path="/catalog" element={<CatalogIndexPage />}>
|
||||
+ <CustomCatalogPage />
|
||||
+ </Route>
|
||||
{/* highlight-remove-next-line */}
|
||||
<Route path="/catalog" element={<CatalogIndexPage />} />
|
||||
{/* highlight-add-start */}
|
||||
<Route path="/catalog" element={<CatalogIndexPage />}>
|
||||
<CustomCatalogPage />
|
||||
</Route>
|
||||
{/* highlight-add-end */}
|
||||
{/* ... */}
|
||||
</FlatRoutes>
|
||||
);
|
||||
```
|
||||
|
||||
The same method can be used to customize the _default_ filters with a different
|
||||
|
||||
@@ -530,12 +530,17 @@ export class FoobarEntitiesProcessor implements CatalogProcessor {
|
||||
Once the processor is created it can be wired up to the catalog via the
|
||||
`CatalogBuilder` in `packages/backend/src/plugins/catalog.ts`:
|
||||
|
||||
```diff
|
||||
+ import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { FoobarEntitiesProcessor } from '@internal/plugin-foobar-backend';
|
||||
|
||||
// ...
|
||||
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
+ builder.addProcessor(new FoobarEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/* highlight-add-next-line */
|
||||
builder.addProcessor(new FoobarEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
@@ -235,26 +235,33 @@ others.
|
||||
You should now be able to add this class to your backend in
|
||||
`packages/backend/src/plugins/catalog.ts`:
|
||||
|
||||
```diff
|
||||
+import { FrobsProvider } from '../path/to/class';
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { FrobsProvider } from '../path/to/class';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = CatalogBuilder.create(env);
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = CatalogBuilder.create(env);
|
||||
/* highlight-add-start */
|
||||
const frobs = new FrobsProvider('production', env.reader);
|
||||
builder.addEntityProvider(frobs);
|
||||
/* highlight-add-end */
|
||||
|
||||
+ const frobs = new FrobsProvider('production', env.reader);
|
||||
+ builder.addEntityProvider(frobs);
|
||||
const { processingEngine, router } = await builder.build();
|
||||
await processingEngine.start();
|
||||
|
||||
const { processingEngine, router } = await builder.build();
|
||||
await processingEngine.start();
|
||||
/* highlight-add-start */
|
||||
await env.scheduler.scheduleTask({
|
||||
id: 'run_frobs_refresh',
|
||||
fn: async () => { await frobs.run(); },
|
||||
frequency: { minutes: 30 },
|
||||
timeout: { minutes: 10 },
|
||||
});
|
||||
/* highlight-add-end */
|
||||
|
||||
+ await env.scheduler.scheduleTask({
|
||||
+ id: 'run_frobs_refresh',
|
||||
+ fn: async () => { await frobs.run(); },
|
||||
+ frequency: { minutes: 30 },
|
||||
+ timeout: { minutes: 10 },
|
||||
+ });
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Note that we used the builtin scheduler facility to regularly call the `run`
|
||||
@@ -464,8 +471,7 @@ feeding it into the ingestion loop. For this kind of an integration, you'd
|
||||
typically want to add it to the list of statically always-available locations in
|
||||
the config.
|
||||
|
||||
```yaml
|
||||
# In app-config.yaml
|
||||
```yaml title="app-config.yaml"
|
||||
catalog:
|
||||
locations:
|
||||
- type: system-x
|
||||
@@ -551,14 +557,19 @@ The key points to note are:
|
||||
You should now be able to add this class to your backend in
|
||||
`packages/backend/src/plugins/catalog.ts`:
|
||||
|
||||
```diff
|
||||
+import { SystemXReaderProcessor } from '../path/to/class';
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { SystemXReaderProcessor } from '../path/to/class';
|
||||
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = CatalogBuilder.create(env);
|
||||
+ builder.addProcessor(new SystemXReaderProcessor(env.reader));
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = CatalogBuilder.create(env);
|
||||
/* highlight-add-next-line */
|
||||
builder.addProcessor(new SystemXReaderProcessor(env.reader));
|
||||
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
Start up the backend - it should now start reading from the previously
|
||||
|
||||
@@ -31,16 +31,19 @@ allow most templates built for `fetch:cookiecutter` to work without any changes.
|
||||
2. Set `cookiecutterCompat` to `true` in the `fetch:template` step input in
|
||||
`template.yaml`.
|
||||
|
||||
```diff
|
||||
steps:
|
||||
- id: fetch-base
|
||||
name: Fetch Base
|
||||
- action: fetch:cookiecutter
|
||||
+ action: fetch:template
|
||||
input:
|
||||
url: ./skeleton
|
||||
+ cookiecutterCompat: true
|
||||
values:
|
||||
```yaml title="template.yaml"
|
||||
steps:
|
||||
- id: fetch-base
|
||||
name: Fetch Base
|
||||
# highlight-remove-next-line
|
||||
action: fetch:cookiecutter
|
||||
# highlight-add-next-line
|
||||
action: fetch:template
|
||||
input:
|
||||
url: ./skeleton
|
||||
# highlight-add-next-line
|
||||
cookiecutterCompat: true
|
||||
values:
|
||||
```
|
||||
|
||||
### Manual migration
|
||||
|
||||
@@ -38,14 +38,20 @@ to upgrade.
|
||||
|
||||
An important change is to add the required processor to your `packages/backend/src/plugins/catalog.ts`
|
||||
|
||||
```diff
|
||||
+import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
|
||||
```ts title="packages/backend/src/plugins/catalog.ts"
|
||||
/* highlight-add-next-line */
|
||||
import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
|
||||
|
||||
...
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/* highlight-add-next-line */
|
||||
builder.addProcessor(new ScaffolderEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
+ builder.addProcessor(new ScaffolderEntitiesProcessor());
|
||||
const { processingEngine, router } = await builder.build();
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
## `backstage.io/v1beta2` -> `scaffolder.backstage.io/v1beta3`
|
||||
@@ -53,10 +59,12 @@ An important change is to add the required processor to your `packages/backend/s
|
||||
The most important change is that you'll need to switch over the `apiVersion` in
|
||||
your templates to the new one.
|
||||
|
||||
```diff
|
||||
```yaml
|
||||
kind: Template
|
||||
- apiVersion: backstage.io/v1beta2
|
||||
+ apiVersion: scaffolder.backstage.io/v1beta3
|
||||
# highlight-remove-next-line
|
||||
apiVersion: backstage.io/v1beta2
|
||||
# highlight-add-next-line
|
||||
apiVersion: scaffolder.backstage.io/v1beta3
|
||||
```
|
||||
|
||||
## `${{ }}` instead of `"{{ }}"`
|
||||
@@ -68,15 +76,19 @@ was pretty annoying, as it also meant that all things look like strings. Now
|
||||
that's no longer the case, you can now remove the `""` and take advantage of
|
||||
writing nice `yaml` files that just work.
|
||||
|
||||
```diff
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
allowedHosts: ['github.com']
|
||||
- description: 'This is {{ parameters.name }}'
|
||||
+ description: This is ${{ parameters.name }}
|
||||
- repoUrl: '{{ parameters.repoUrl }}'
|
||||
+ repoUrl: ${{ parameters.repoUrl }}
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
allowedHosts: ['github.com']
|
||||
# highlight-remove-next-line
|
||||
description: 'This is {{ parameters.name }}'
|
||||
# highlight-add-next-line
|
||||
description: This is ${{ parameters.name }}
|
||||
# highlight-remove-next-line
|
||||
repoUrl: '{{ parameters.repoUrl }}'
|
||||
# highlight-add-next-line
|
||||
repoUrl: ${{ parameters.repoUrl }}
|
||||
```
|
||||
|
||||
## No more `eq` or `not` helpers
|
||||
@@ -85,24 +97,26 @@ These helpers are no longer needed with the more expressive `api` that
|
||||
`nunjucks` provides. You can simply use the built-in `nunjucks` and `jinja2`
|
||||
style operators.
|
||||
|
||||
```diff
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
- if: '{{ eq parameters.value "backstage" }}'
|
||||
+ if: ${{ parameters.value === "backstage" }}
|
||||
...
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
# highlight-remove-next-line
|
||||
if: '{{ eq parameters.value "backstage" }}'
|
||||
# highlight-add-next-line
|
||||
if: ${{ parameters.value === "backstage" }}
|
||||
```
|
||||
|
||||
And then for the `not`
|
||||
|
||||
```diff
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
- if: '{{ not parameters.value "backstage" }}'
|
||||
+ if: ${{ parameters.value !== "backstage" }}
|
||||
...
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
# highlight-remove-next-line
|
||||
if: '{{ not parameters.value "backstage" }}'
|
||||
# highlight-add-next-line
|
||||
if: ${{ parameters.value !== "backstage" }}
|
||||
```
|
||||
|
||||
Much better right? ✨
|
||||
@@ -115,32 +129,36 @@ supporting the additional primitive values now rather than everything being a
|
||||
should all work as expected and keep the type that has been declared in the
|
||||
input schema.
|
||||
|
||||
```diff
|
||||
spec:
|
||||
parameters:
|
||||
test:
|
||||
type: number
|
||||
name: Test Number
|
||||
address:
|
||||
type: object
|
||||
required:
|
||||
- line1
|
||||
properties:
|
||||
line1:🙏
|
||||
type: string
|
||||
name: Line 1
|
||||
line2:
|
||||
type: string
|
||||
name: Line 2
|
||||
```yaml
|
||||
spec:
|
||||
parameters:
|
||||
test:
|
||||
type: number
|
||||
name: Test Number
|
||||
address:
|
||||
type: object
|
||||
required:
|
||||
- line1
|
||||
properties:
|
||||
line1:
|
||||
type: string
|
||||
name: Line 1
|
||||
line2:
|
||||
type: string
|
||||
name: Line 2
|
||||
|
||||
steps:
|
||||
- id: test step
|
||||
action: run:something
|
||||
input:
|
||||
- address: '{{ json parameters.address }}'
|
||||
+ address: ${{ parameters.address }}
|
||||
- test: '{{ parameters.test }}'
|
||||
+ test: ${{ parameters.test }} # this will now make sure that the type of test is a number 🙏
|
||||
steps:
|
||||
- id: test step
|
||||
action: run:something
|
||||
input:
|
||||
# highlight-remove-next-line
|
||||
address: '{{ json parameters.address }}'
|
||||
# highlight-add-next-line
|
||||
address: ${{ parameters.address }}
|
||||
# highlight-remove-next-line
|
||||
test: '{{ parameters.test }}'
|
||||
# highlight-add-next-line
|
||||
test: ${{ parameters.test }} # this will now make sure that the type of test is a number 🙏
|
||||
```
|
||||
|
||||
## `parseRepoUrl` is now a `filter`
|
||||
@@ -148,13 +166,14 @@ input schema.
|
||||
All calls to `parseRepoUrl` are now a `jinja2` `filter`, which means you'll need
|
||||
to update the syntax.
|
||||
|
||||
```diff
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
- repoUrl: '{{ parseRepoUrl parameters.repoUrl }}'
|
||||
+ repoUrl: ${{ parameters.repoUrl | parseRepoUrl }}
|
||||
...
|
||||
```yaml
|
||||
spec:
|
||||
steps:
|
||||
input:
|
||||
# highlight-remove-next-line
|
||||
repoUrl: '{{ parseRepoUrl parameters.repoUrl }}'
|
||||
# highlight-add-next-line
|
||||
repoUrl: ${{ parameters.repoUrl | parseRepoUrl }}
|
||||
```
|
||||
|
||||
Now we have complex value support here too, expect that this `filter` will go
|
||||
@@ -167,39 +186,46 @@ away in future versions and the `RepoUrlPicker` will return an object so
|
||||
Previously, it was possible to provide links to the frontend using the named output `entityRef` and `remoteUrl`.
|
||||
These should be moved to `links` under the `output` object instead.
|
||||
|
||||
```diff
|
||||
output:
|
||||
- remoteUrl: {{ steps['publish'].output.remoteUrl }}
|
||||
- entityRef: {{ steps['register'].output.entityRef }}
|
||||
+ links:
|
||||
+ - title: Repository
|
||||
+ url: ${{ steps['publish'].output.remoteUrl }}
|
||||
+ - title: Open in catalog
|
||||
+ icon: catalog
|
||||
+ entityRef: ${{ steps['register'].output.entityRef }}
|
||||
|
||||
```yaml
|
||||
output:
|
||||
# highlight-remove-start
|
||||
remoteUrl: {{ steps['publish'].output.remoteUrl }}
|
||||
entityRef: {{ steps['register'].output.entityRef }}
|
||||
# highlight-remove-end
|
||||
# highlight-add-start
|
||||
links:
|
||||
- title: Repository
|
||||
url: ${{ steps['publish'].output.remoteUrl }}
|
||||
- title: Open in catalog
|
||||
icon: catalog
|
||||
entityRef: ${{ steps['register'].output.entityRef }}
|
||||
# highlight-add-end
|
||||
```
|
||||
|
||||
## Watch out for `dash-case`
|
||||
|
||||
The nunjucks compiler can run into issues if the `id` fields in your template steps use dash characters, since these IDs translate directly to JavaScript object properties when accessed as output. One possible migration path is to use `camelCase` for your action IDs.
|
||||
|
||||
```diff
|
||||
```yaml
|
||||
steps:
|
||||
- id: my-custom-action
|
||||
- ...
|
||||
-
|
||||
- id: publish-pull-request
|
||||
- input:
|
||||
- repoUrl: {{ steps.my-custom-action.output.repoUrl }} # Will not recognize 'my-custom-action' as a JS property since it contains dashes!
|
||||
# highlight-remove-start
|
||||
id: my-custom-action
|
||||
...
|
||||
|
||||
id: publish-pull-request
|
||||
input:
|
||||
repoUrl: {{ steps.my-custom-action.output.repoUrl }} # Will not recognize 'my-custom-action' as a JS property since it contains dashes!
|
||||
# highlight-remove-end
|
||||
|
||||
steps:
|
||||
+ id: myCustomAction
|
||||
+ ...
|
||||
+
|
||||
+ id: publishPullRequest
|
||||
+ input:
|
||||
+ repoUrl: ${{ steps.myCustomAction.output.repoUrl }}
|
||||
# highlight-add-start
|
||||
id: myCustomAction
|
||||
...
|
||||
|
||||
id: publishPullRequest
|
||||
input:
|
||||
repoUrl: ${{ steps.myCustomAction.output.repoUrl }}
|
||||
# highlight-add-end
|
||||
```
|
||||
|
||||
Alternatively, it's possible to keep the `dash-case` syntax and use brackets for property access as you would in JavaScript:
|
||||
|
||||
@@ -41,39 +41,43 @@ It's also worth calling out that if you do test this out, and find some issues o
|
||||
|
||||
The `ScaffolderPage` router has a completely different export for the `scaffolder/next` work, so you will want to change any import from the old `ScaffolderPage` to the new `NextScaffolderPage`
|
||||
|
||||
```diff
|
||||
- import { ScaffolderPage } from '@backstage/plugin-scaffolder';
|
||||
+ import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
|
||||
```tsx
|
||||
/* highlight-remove-next-line */
|
||||
import { ScaffolderPage } from '@backstage/plugin-scaffolder';
|
||||
/* highlight-add-next-line */
|
||||
import { NextScaffolderPage } from '@backstage/plugin-scaffolder/alpha';
|
||||
|
||||
```
|
||||
|
||||
And this API should be the exact same as the previous Router, so you should be able to make a change like the following further down in this file:
|
||||
|
||||
```diff
|
||||
<Route
|
||||
path="/create"
|
||||
element={
|
||||
- <ScaffolderPage
|
||||
+ <NextScaffolderPage
|
||||
groups={[
|
||||
{
|
||||
title: 'Recommended',
|
||||
filter: entity =>
|
||||
entity?.metadata?.tags?.includes('recommended') ?? false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
... other extensions
|
||||
</ScaffolderFieldExtensions>
|
||||
<ScaffolderLayouts>
|
||||
<TwoColumnLayout />
|
||||
... other layouts
|
||||
</ScaffolderLayouts>
|
||||
</Route>
|
||||
```tsx
|
||||
<Route
|
||||
path="/create"
|
||||
element={
|
||||
{/* highlight-remove-next-line */}
|
||||
<ScaffolderPage
|
||||
{/* highlight-add-next-line */}
|
||||
<NextScaffolderPage
|
||||
groups={[
|
||||
{
|
||||
title: 'Recommended',
|
||||
filter: entity =>
|
||||
entity?.metadata?.tags?.includes('recommended') ?? false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScaffolderFieldExtensions>
|
||||
<LowerCaseValuePickerFieldExtension />
|
||||
{/* ... other extensions */}
|
||||
</ScaffolderFieldExtensions>
|
||||
<ScaffolderLayouts>
|
||||
<TwoColumnLayout />
|
||||
{/* ... other layouts */}
|
||||
</ScaffolderLayouts>
|
||||
</Route>
|
||||
```
|
||||
|
||||
### Make the required changes to your `CustomFieldExtensions`
|
||||
@@ -95,13 +99,17 @@ export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
|
||||
References for `createScaffolderFieldExtension` have an `/alpha` version of `createNextScaffolderFieldExtension`, which should be used instead.
|
||||
|
||||
```diff
|
||||
-import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
|
||||
+import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder/alpha';
|
||||
```ts
|
||||
/* highlight-remove-next-line */
|
||||
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder';
|
||||
/* highlight-add-next-line */
|
||||
import { createNextScaffolderFieldExtension } from '@backstage/plugin-scaffolder/alpha';
|
||||
|
||||
export const EntityNamePickerFieldExtension = scaffolderPlugin.provide(
|
||||
- createScaffolderFieldExtension({
|
||||
+ createNextScaffolderFieldExtension({
|
||||
/* highlight-remove-next-line */
|
||||
createScaffolderFieldExtension({
|
||||
/* highlight-add-next-line */
|
||||
createNextScaffolderFieldExtension({
|
||||
component: EntityNamePicker,
|
||||
name: 'EntityNamePicker',
|
||||
validation: entityNamePickerValidation,
|
||||
@@ -113,7 +121,7 @@ Once you've done this you will find that you will have two squiggly lines under
|
||||
|
||||
Let's take the following code for the `EntityNamePicker` component:
|
||||
|
||||
```ts
|
||||
```tsx
|
||||
export const EntityNamePicker = (
|
||||
props: FieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
) => {
|
||||
@@ -127,19 +135,23 @@ export const EntityNamePicker = (
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
...
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
There's another `/alpha` export that you need to replace `FieldExtensionComponentProps` with which is the `NextFieldExtensionComponentProps`.
|
||||
|
||||
```diff
|
||||
- import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
|
||||
+ import { NextFieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
```tsx
|
||||
/* highlight-remove-next-line */
|
||||
import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react';
|
||||
/* highlight-add-next-line */
|
||||
import { NextFieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
|
||||
export const EntityNamePicker = (
|
||||
- props: FieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
+ props: NextFieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
/* highlight-remove-next-line */
|
||||
props: FieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
/* highlight-add-next-line */
|
||||
props: NextFieldExtensionComponentProps<string, EntityNamePickerProps>,
|
||||
) => {
|
||||
const {
|
||||
onChange,
|
||||
@@ -147,12 +159,14 @@ export const EntityNamePicker = (
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
formData,
|
||||
- uiSchema: { 'ui:autofocus': autoFocus },
|
||||
+ uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
/* highlight-remove-next-line */
|
||||
uiSchema: { 'ui:autofocus': autoFocus },
|
||||
/* highlight-add-next-line */
|
||||
uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
...
|
||||
// ..
|
||||
}
|
||||
```
|
||||
|
||||
@@ -182,8 +196,10 @@ You will need to change the import for `FieldValidation` to point at the new `re
|
||||
|
||||
> Note: you will probably need to install this dependency too, by using `yarn add @rjsf/utils` in the package where you define these validation functions, this could also be in the `packages/app` folder, so you can install it there if needed.
|
||||
|
||||
```diff
|
||||
```ts
|
||||
/* highlight-remove-next-line */
|
||||
- import { FieldValidation } from '@rjsf/core';
|
||||
/* highlight-add-next-line */
|
||||
+ import { FieldValidation } from '@rjsf/utils;
|
||||
import { KubernetesValidatorFunctions } from '@backstage/catalog-model';
|
||||
|
||||
@@ -201,34 +217,40 @@ Once we fully release the code that is in the `/alpha` exports right now onto th
|
||||
|
||||
Later releases of `react-jsonschema-form` have made the `uiSchema` optional, and if you don't provide it, it will be `undefined` instead of an empty object. This means that you will need to make sure that you're defaulting the `uiSchema` to an empty object if you're using it in your code.
|
||||
|
||||
```diff
|
||||
```tsx
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
formData,
|
||||
- uiSchema: { 'ui:autofocus': autoFocus },
|
||||
+ uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
/* highlight-remove-next-line */
|
||||
uiSchema: { 'ui:autofocus': autoFocus },
|
||||
/* highlight-add-next-line */
|
||||
uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
// ..
|
||||
```
|
||||
|
||||
### `formData` can also be `undefined`
|
||||
|
||||
If you were using the `formData` and assuming that it was set to an empty object when building `Field Extensions` that return objects, then this will be `undefined` now due to a change in the `react-jsonschema-form` library.
|
||||
|
||||
```diff
|
||||
```tsx
|
||||
const {
|
||||
onChange,
|
||||
required,
|
||||
schema: { title = 'Name', description = 'Unique name of the component' },
|
||||
rawErrors,
|
||||
- formData,
|
||||
+ formData = {}, // or maybe some other default value that you would prefer
|
||||
/* highlight-remove-next-line */
|
||||
formData,
|
||||
/* highlight-add-next-line */
|
||||
formData = {}, // or maybe some other default value that you would prefer
|
||||
uiSchema: { 'ui:autofocus': autoFocus } = {},
|
||||
idSchema,
|
||||
placeholder,
|
||||
} = props;
|
||||
// ..
|
||||
```
|
||||
|
||||
@@ -31,18 +31,16 @@ Once the package has been installed, you need to import the plugin in your app.
|
||||
In `packages/app/src/App.tsx`, import `TechDocsPage` and add the following to
|
||||
`FlatRoutes`:
|
||||
|
||||
```tsx
|
||||
```tsx title="packages/app/src/App.tsx"
|
||||
import {
|
||||
DefaultTechDocsHome,
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
|
||||
// ...
|
||||
|
||||
const AppRoutes = () => {
|
||||
<FlatRoutes>
|
||||
// ... other plugin routes
|
||||
{/* ... other plugin routes */}
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
@@ -58,20 +56,20 @@ It would be nice to decorate your pages with something else... Having a link tha
|
||||
|
||||
With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `<ReportIssue />` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed:
|
||||
|
||||
```diff
|
||||
```tsx
|
||||
import {
|
||||
DefaultTechDocsHome,
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
} from '@backstage/plugin-techdocs';
|
||||
+ import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
+ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
|
||||
// ...
|
||||
/* highlight-add-start */
|
||||
import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
|
||||
import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
|
||||
/* highlight-add-end */
|
||||
|
||||
const AppRoutes = () => {
|
||||
<FlatRoutes>
|
||||
// ... other plugin routes
|
||||
{/* ... other plugin routes */}
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
@@ -79,9 +77,11 @@ const AppRoutes = () => {
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
>
|
||||
+ <TechDocsAddons>
|
||||
+ <ReportIssue />
|
||||
+ </TechDocsAddons>
|
||||
{/* highlight-add-start */}
|
||||
<TechDocsAddons>
|
||||
<ReportIssue />
|
||||
</TechDocsAddons>
|
||||
{/* highlight-add-end */}
|
||||
</Route>
|
||||
</FlatRoutes>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user