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:
Paul Schultz
2023-03-01 13:30:38 -06:00
parent adf9fe58f5
commit 9c95f91c0a
44 changed files with 2257 additions and 1869 deletions
+70 -50
View File
@@ -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
+58 -68
View File
@@ -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;
// ..
```
+13 -13
View File
@@ -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>;
};