Merge branch 'master' of github.com:backstage/backstage into seant-splunk/awsS3_readTree_processor

This commit is contained in:
Sean Tan
2021-09-02 08:54:05 -07:00
256 changed files with 4091 additions and 1381 deletions
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder': patch
---
Allow to pass custom TemplateCard to ScaffolderPage
-6
View File
@@ -1,6 +0,0 @@
---
'@backstage/plugin-catalog-react': patch
---
Added a `useEntityKinds` hook to load a unique list of entity kinds from the catalog.
Fixed a bug in `EntityTypePicker` where the component did not hide when no types were available in returned entities.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
GitHub discovery processor passes over repositories that do not have a default branch
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
GitHub create repository webhook action: `github:webhook` for Backstage plugin Scaffolder has been added.
+23
View File
@@ -0,0 +1,23 @@
---
'@backstage/create-app': patch
---
Updated the search configuration class to use the static `fromConfig`-based constructor for the `DefaultCatalogCollator`.
To apply this change to an existing app, replace the following line in `search.ts`:
```diff
-collator: new DefaultCatalogCollator({ discovery })
+collator: DefaultCatalogCollator.fromConfig(config, { discovery })
```
The `config` parameter was not needed before, so make sure you also add that in the signature of `createPlugin`
in `search.ts`:
```diff
export default async function createPlugin({
logger,
discovery,
+ config,
}: PluginEnvironment) {
```
-9
View File
@@ -1,9 +0,0 @@
---
'@backstage/plugin-techdocs': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/techdocs-common': patch
---
Adding in-context search to TechDocs Reader component. Using existing search-backend to query for indexed search results scoped into a specific entity's techdocs. Needs TechDocsCollator enabled on the backend to work.
Adding extra information to indexed tech docs documents for search.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog-backend': patch
---
Adds optional namespacing for users in the GitHub Multi Org Plugin
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Allow the catalog search collator to filter the entities that it indexes
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/core-components': patch
'@backstage/plugin-catalog-import': patch
'@backstage/plugin-shortcuts': patch
---
Bumped `react-hook-form` to `^7.12.2`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-git-release-manager': patch
---
Wrap each feature in custom feature arrays with an element containing a 'key' to avoid missing-key-warnings
-10
View File
@@ -1,10 +0,0 @@
---
'@backstage/core-components': minor
---
Changed the `titleComponent` prop on `ContentHeader` to accept `ReactNode` instead of a React `ComponentType`. Usages of this prop should be converted from passing a component to passing in the rendered element:
```diff
-<ContentHeader titleComponent={MyComponent}>
+<ContentHeader titleComponent={<MyComponent />}>
```
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-org': patch
---
Don't open a new tab when clicking on the ownership card.
-7
View File
@@ -1,7 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Update the `AboutCard` to properly support non-standard entity types and rework the defaults for the build-in kinds.
This change also uses `useElementFilter(...)` instead of `React.children.count(...)` in `AboutField` to properly recognize whether children are available.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-auth-backend': patch
---
typo fix `tenentId` in Azure auth provider docs
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Added the ability to switch entity kind on the catalog index page. This is a non-breaking change, but if you created a custom `CatalogPage` and wish to use this feature, make the modifications shown on [#6895](https://github.com/backstage/backstage/pull/6895).
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-shortcuts': patch
---
Fix material-ui warning about deprecated `onExit` prop
-11
View File
@@ -1,11 +0,0 @@
---
'@backstage/catalog-model': patch
---
Add an optional `metadata.title` field to all entity kinds.
This used to be available on only the `Template` kind, and we have decided that the metadata block should be the same for all kinds. A title can be useful especially in large and complex catalogs where users have a tough time navigating or discerning among the entities.
It also carries some risk. You do not want to end up giving a title that collides with an actual name, which at best leads to confusion and at worst could be a liability. We do not perform any collision detection in the catalog. If you want to disallow this facility you may want to add a small processor that makes sure it's not set.
At the time of writing this message, only the scaffolder actually makes use of this field for display purposes.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/create-app': patch
---
Minor tweaks to the create-app template to match earlier documented changes
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/backend-common': patch
---
Export type that are needed to implement a new `UrlReader`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-sonarqube': patch
---
Update README to show a simpler proxy configuration.
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/techdocs-common': patch
---
Force using `posix` path for cloud storage
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-catalog': patch
---
Use a `Link` for the edit button on the `AboutCard` instead of doing `window.open(...)`
-5
View File
@@ -1,5 +0,0 @@
---
'@backstage/plugin-org': patch
---
Fix OwnershipCard links to support namespaced owners
+3
View File
@@ -133,6 +133,9 @@ kafka:
brokers:
- localhost:9092
allure:
baseUrl: http://localhost:5050/allure-docker-service
integrations:
github:
- host: github.com
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

@@ -547,8 +547,8 @@ The current set of well-known and common values for this field is:
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the component, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
component, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of a component is the singular entity (commonly a team)
that bears ultimate responsibility for the component, and has the authority and
@@ -566,8 +566,8 @@ component, but there will always be one ultimate owner.
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the component
belongs to, e.g. `artist-engagement-portal`. This field is optional.
An [entity reference](references.md#string-references) to the system that the
component belongs to, e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
@@ -575,8 +575,8 @@ belongs to, e.g. `artist-engagement-portal`. This field is optional.
### `spec.subcomponentOf` [optional]
An [entity reference](#string-references) to another component of which the
component is a part, e.g. `spotify-ios-app`. This field is optional.
An [entity reference](references.md#string-references) to another component of
which the component is a part, e.g. `spotify-ios-app`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ---------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
@@ -584,8 +584,8 @@ component is a part, e.g. `spotify-ios-app`. This field is optional.
### `spec.providesApis` [optional]
An array of [entity references](#string-references) to the APIs that are
provided by the component, e.g. `artist-api`. This field is optional.
An array of [entity references](references.md#string-references) to the APIs
that are provided by the component, e.g. `artist-api`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
@@ -593,8 +593,8 @@ provided by the component, e.g. `artist-api`. This field is optional.
### `spec.consumesApis` [optional]
An array of [entity references](#string-references) to the APIs that are
consumed by the component, e.g. `artist-api`. This field is optional.
An array of [entity references](references.md#string-references) to the APIs
that are consumed by the component, e.g. `artist-api`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------- |
@@ -602,9 +602,9 @@ consumed by the component, e.g. `artist-api`. This field is optional.
### `spec.dependsOn` [optional]
An array of [entity references](#string-references) to the components and
resources that the component depends on, e.g. `artists-db`. This field is
optional.
An array of [entity references](references.md#string-references) to the
components and resources that the component depends on, e.g. `artists-db`. This
field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
@@ -732,8 +732,8 @@ You can find out more about the `steps` key
### `spec.owner` [optional]
An [entity reference](#string-references) to the owner of the template, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
template, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of a Template is the singular entity (commonly a team)
that bears ultimate responsibility for the Template, and has the authority and
@@ -842,8 +842,8 @@ The current set of well-known and common values for this field is:
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the component, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
component, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of an API is the singular entity (commonly a team) that
bears ultimate responsibility for the API, and has the authority and capability
@@ -861,8 +861,8 @@ one ultimate owner.
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the API belongs to,
e.g. `artist-engagement-portal`. This field is optional.
An [entity reference](references.md#string-references) to the system that the
API belongs to, e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
@@ -1069,8 +1069,8 @@ Exactly equal to `backstage.io/v1alpha1` and `Resource`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the resource, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
resource, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of a resource is the singular entity (commonly a team)
that bears ultimate responsibility for the resource, and has the authority and
@@ -1101,8 +1101,8 @@ Some common values for this field could be:
### `spec.system` [optional]
An [entity reference](#string-references) to the system that the resource
belongs to, e.g. `artist-engagement-portal`. This field is optional.
An [entity reference](references.md#string-references) to the system that the
resource belongs to, e.g. `artist-engagement-portal`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
@@ -1110,9 +1110,9 @@ belongs to, e.g. `artist-engagement-portal`. This field is optional.
### `spec.dependsOn` [optional]
An array of [entity references](#string-references) to the components and
resources that the resource depends on, e.g. `artist-lookup`. This field is
optional.
An array of [entity references](references.md#string-references) to the
components and resources that the resource depends on, e.g. `artist-lookup`.
This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
@@ -1156,8 +1156,8 @@ Exactly equal to `backstage.io/v1alpha1` and `System`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the system, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
system, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of a system is the singular entity (commonly a team)
that bears ultimate responsibility for the system, and has the authority and
@@ -1175,8 +1175,8 @@ but there will always be one ultimate owner.
### `spec.domain` [optional]
An [entity reference](#string-references) to the domain that the system belongs
to, e.g. `artists`. This field is optional.
An [entity reference](references.md#string-references) to the domain that the
system belongs to, e.g. `artists`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
@@ -1215,8 +1215,8 @@ Exactly equal to `backstage.io/v1alpha1` and `Domain`, respectively.
### `spec.owner` [required]
An [entity reference](#string-references) to the owner of the domain, e.g.
`artist-relations-team`. This field is required.
An [entity reference](references.md#string-references) to the owner of the
domain, e.g. `artist-relations-team`. This field is required.
In Backstage, the owner of a domain is the singular entity (commonly a team)
that bears ultimate responsibility for the domain, and has the authority and
@@ -0,0 +1,130 @@
---
id: life-of-an-entity
title: The Life of an Entity
sidebar_label: The Life of an Entity
# prettier-ignore
description: The life cycle of entities, from being introduced into the catalog, through processing, to being removed again
---
This document gives a high level overview of the catalog backend, and the
technical processes involved in making entities flow through it. It is mainly
aimed at developers who want to understand the internals while installing or
extending the catalog. However, it can be informative for other personas too.
## Key Concepts
The catalog forms a hub of sorts, where entities are ingested from various
authoritative sources and held in a database, subject to automated processing,
and then presented through an API for quick and easy access by Backstage and
others. The most common source is [YAML files](descriptor-format.md) on a
standard format, living in version control systems near the source code of
systems that they describe. Those files are registered with the catalog and
maintained by the respective owners. The catalog makes sure to keep itself up to
date with changes to those files.
The main extension points where developers can customize the catalog are:
- _Entity providers_, that feed initial raw entity data into the catalog,
- _Policies_, that establish baseline rules about the shape of entities,
- _Processors_, that validate, analyze, and mutate the raw entity data into its
final form.
The high level processes involved are:
- _Ingestion_, where entity providers fetch raw entity data from external
sources and seed it into the database,
- _Processing_, where the policies and processors continually treat the ingested
data and may emit both other raw entities (that are also subject to
processing), errors, relations to other entities, etc.,
- _Stitching_, where all of the data emitted by various processors are assembled
together into the final output entity.
An entity is not visible to the outside world (through the catalog API), until
it has passed through the last process and landed among the final entities.
![General overview](../../assets/features/catalog/life-of-an-entity_overview.svg)
The details of these processes are described below.
## Ingestion
Each catalog deployment has a number of entity providers installed. They are
responsible for fetching data from external authoritative sources in any way
that they see fit, to translate those into entity objects, and to notify the
database when those entities are added or removed. These are the _unprocessed
entities_ that will be subject to later processing (see below), and they form
the very basis of existence for entities. If there were no entity providers, no
entities would ever enter the system.
The database always keeps track of the set of entities that belong to each
provider; no two providers can try to output the same entity. And when a
provider signals the removal of an entity, then that leads to an _eager
deletion_: the entity and all auxiliary data that it has led to in the database
is immediately purged.
![Ingestion overview](../../assets/features/catalog/life-of-an-entity_ingestion.svg)
There are two providers installed by default: the one that deals with user
registered locations (e.g. URLs to YAML files), and the one that deals with
static locations in the app-config. You can add more third party providers by
passing them to the catalog builder in your backend initialization code, and you
can easily write your own.
An entity provider is a class that implements the `EntityProvider` interface. It
has three main parts:
- The identity: Each provider instance has a unique, stable identifier that the
database can use to keep track of the originator of each unprocessed entity.
- The connection: During backend startup, each provider is attached to the
catalog runtime.
- The stream of events: During its lifetime, the provider can issue change
events to the runtime at any point in time, to modify its set of unprocessed
entities.
It is entirely up to the provider to choose how and when it produces these
change events. For example, the app-config provider only fires off an update at
startup and then lies dormant. The location database provider does an initial
update at startup, and then small delta updates every time a location database
change is detected. The LDAP provider is driven externally by a timer loop that
occasionally triggers a full update. Some future provider may be entirely event
driven, feeding off an event bus or web hook. There is no magic coordination
among providers; if they need to arrange synchronization or locking among
themselves for example to avoid duplicate work across multiple catalog service
machines, they need to handle that out-of-band.
The entities that are emitted get some coarse validation applied to them, to
ensure that they at least adhere to the most basic schema rules about how an
entity should be shaped. For example, they need to have a `kind`, a
`metadata.name`, and optionally a `metadata.namespace`, among others. Apart from
that, the ingestion stage considers its work done, and stores the unprocessed
entities to be picked up at a later time by the processing system. This means
that the more precise validation rules that you put in place on entities are
_not_ yet applied at this stage.
## Processing
Every unprocessed entity comes with a timestamp, which tells at what time that
the processing loop should next try to process it. When the entity first
appears, this timestamp is set to "now" - asking for it to be picked up as soon
as possible.
Each catalog deployment has a number of processors installed. They are
responsible for receiving unprocessed entities that the catalog decided are due
for processing, and then running that data through a number of processing
stages. mutating the entity and emitting auxiliary data about it. When all of
that is done, the catalog takes all of that information and stores it as the
processed entity, and errors and relations to other entities separately. Then,
the catalog checks to see what entities are touched by that output, and triggers
the final assembly of those (see Stitching below).
There are several stages involved in the processing.
> TODO: More info here
## Stitching
The stitching is currently a fixed process, that cannot be modified or extended.
This means that any modifications you want to make on the final result, has to
happen during ingestion or processing.
> TODO: More info here
+12 -6
View File
@@ -148,12 +148,18 @@ const AppRoutes = () => {
> an external storage provider and external CI/CD is used). If you use the
> "basic" or "out-of-the-box" setup, you can stop here! No action needed.
The beta version of TechDocs (v0.x.y) made a breaking change to the way TechDocs
content was accessed and stored, allowing pages to be accessed with
case-insensitive entity triplet paths (e.g. `/docs/namespace/kind/name` whereas
in prior versions, they could only be accessed at `/docs/namespace/Kind/name`).
In order to enable this change, documentation has to be stored in an external
storage provider using an object key whose entity triplet is lower-cased.
For the purposes of this guide, TechDocs Beta version is defined as:
- **TechDocs Plugin**: At least `v0.11.0`
- **TechDocs Backend Plugin**: At least `v0.10.0`
- **TechDocs CLI**: At least `v0.7.0`
The beta version of TechDocs made a breaking change to the way TechDocs content
was accessed and stored, allowing pages to be accessed with case-insensitive
entity triplet paths (e.g. `/docs/namespace/kind/name` whereas in prior
versions, they could only be accessed at `/docs/namespace/Kind/name`). In order
to enable this change, documentation has to be stored in an external storage
provider using an object key whose entity triplet is lower-cased.
New installations of TechDocs since the beta version will work fine with no
action, but for those who were running TechDocs prior to this version, a
+1 -1
View File
@@ -128,7 +128,7 @@ const themeOptions = createThemeOptions({
tool: genPageTheme(['#123456','#123456'], shapes.round),
service: genPageTheme(['#123456','#123456'], shapes.wave),
website: genPageTheme(['#123456','#123456'], shapes.wave),
library: genPageTheme(['#123456','#123456'] shapes.wave),
library: genPageTheme(['#123456','#123456'], shapes.wave),
other: genPageTheme(['#123456','#123456'], shapes.wave),
app: genPageTheme(['#123456','#123456'], shapes.wave),
apis: genPageTheme(['#123456','#123456'], shapes.wave),
+9
View File
@@ -0,0 +1,9 @@
---
title: Allure Reports
author: Deepak Bhardwaj
authorUrl: https://github.com/deepak-bhardwaj-ps
category: Reporting
description: View Allure reports for your components in Backstage.
documentation: https://github.com/backstage/backstage/tree/master/plugins/allure
iconUrl: https://avatars.githubusercontent.com/u/5879127
npmPackageName: '@backstage/plugin-allure'
+9
View File
@@ -0,0 +1,9 @@
---
title: Home
author: Spotify
authorUrl: https://github.com/spotify
category: Discovery
description: This plugin provides a composable home page, and ability to create home page components
documentation: https://github.com/backstage/backstage/blob/master/plugins/home/README.md
iconUrl: img/home.png
npmPackageName: '@backstage/plugin-home'
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+1
View File
@@ -33,6 +33,7 @@ nav:
- Core Features:
- Software Catalog:
- Overview: 'features/software-catalog/index.md'
- The Life of an Entity: 'features/software-catalog/life-of-an-entity.md'
- Catalog Configuration: 'features/software-catalog/configuration.md'
- System Model: 'features/software-catalog/system-model.md'
- YAML File Format: 'features/software-catalog/descriptor-format.md'
+42
View File
@@ -1,5 +1,47 @@
# example-app
## 0.2.44
### Patch Changes
- Updated dependencies
- @backstage/plugin-scaffolder@0.11.2
- @backstage/cli@0.7.10
- @backstage/plugin-catalog-react@0.4.5
- @backstage/plugin-techdocs@0.11.1
- @backstage/core-components@0.4.0
- @backstage/plugin-catalog-import@0.5.20
- @backstage/plugin-shortcuts@0.1.7
- @backstage/plugin-org@0.3.21
- @backstage/plugin-catalog@0.6.14
- @backstage/search-common@0.2.0
- @backstage/plugin-search@0.4.9
- @backstage/catalog-model@0.9.1
- @backstage/core-app-api@0.1.11
- @backstage/integration-react@0.1.8
- @backstage/plugin-api-docs@0.6.7
- @backstage/plugin-badges@0.2.8
- @backstage/plugin-circleci@0.2.22
- @backstage/plugin-cloudbuild@0.2.22
- @backstage/plugin-code-coverage@0.1.10
- @backstage/plugin-cost-insights@0.11.5
- @backstage/plugin-explore@0.3.15
- @backstage/plugin-gcp-projects@0.3.3
- @backstage/plugin-github-actions@0.4.17
- @backstage/plugin-graphiql@0.2.15
- @backstage/plugin-home@0.2.1
- @backstage/plugin-jenkins@0.5.5
- @backstage/plugin-kafka@0.2.14
- @backstage/plugin-kubernetes@0.4.12
- @backstage/plugin-lighthouse@0.2.24
- @backstage/plugin-newrelic@0.3.3
- @backstage/plugin-pagerduty@0.3.12
- @backstage/plugin-rollbar@0.3.13
- @backstage/plugin-sentry@0.3.20
- @backstage/plugin-tech-radar@0.4.6
- @backstage/plugin-todo@0.1.8
- @backstage/plugin-user-settings@0.3.4
## 0.2.43
### Patch Changes
@@ -34,7 +34,7 @@ describe('SearchPage', () => {
cy.visit('/search-next', {
onBeforeLoad(win) {
cy.stub(win, 'fetch')
.withArgs(`${API_ENDPOINT}?term=&pageCursor=`)
.withArgs(`${API_ENDPOINT}?term=`)
.resolves({
ok: true,
json: () => ({ results }),
@@ -56,7 +56,7 @@ describe('SearchPage', () => {
onBeforeLoad(win) {
cy.stub(win, 'fetch')
.withArgs(
`${API_ENDPOINT}?term=&filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B0%5D=experimental&pageCursor=`,
`${API_ENDPOINT}?term=&filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B0%5D=experimental`,
)
.resolves({
ok: true,
@@ -102,7 +102,7 @@ describe('SearchPage', () => {
cy.visit('/search-next?query=backstage', {
onBeforeLoad(win) {
cy.stub(win, 'fetch')
.withArgs(`${API_ENDPOINT}?term=backstage&pageCursor=`)
.withArgs(`${API_ENDPOINT}?term=backstage`)
.resolves({
ok: true,
json: () => ({ results: [] }),
+37 -37
View File
@@ -1,46 +1,46 @@
{
"name": "example-app",
"version": "0.2.43",
"version": "0.2.44",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/catalog-model": "^0.9.0",
"@backstage/cli": "^0.7.9",
"@backstage/core-app-api": "^0.1.10",
"@backstage/core-components": "^0.3.3",
"@backstage/catalog-model": "^0.9.1",
"@backstage/cli": "^0.7.10",
"@backstage/core-app-api": "^0.1.11",
"@backstage/core-components": "^0.4.0",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration-react": "^0.1.7",
"@backstage/plugin-api-docs": "^0.6.6",
"@backstage/plugin-badges": "^0.2.7",
"@backstage/plugin-catalog": "^0.6.13",
"@backstage/plugin-catalog-import": "^0.5.19",
"@backstage/plugin-catalog-react": "^0.4.4",
"@backstage/plugin-circleci": "^0.2.21",
"@backstage/plugin-cloudbuild": "^0.2.21",
"@backstage/plugin-code-coverage": "^0.1.9",
"@backstage/plugin-cost-insights": "^0.11.4",
"@backstage/plugin-explore": "^0.3.14",
"@backstage/plugin-gcp-projects": "^0.3.2",
"@backstage/plugin-github-actions": "^0.4.16",
"@backstage/plugin-graphiql": "^0.2.14",
"@backstage/plugin-home": "^0.2.0",
"@backstage/plugin-jenkins": "^0.5.4",
"@backstage/plugin-kafka": "^0.2.13",
"@backstage/plugin-kubernetes": "^0.4.11",
"@backstage/plugin-lighthouse": "^0.2.23",
"@backstage/plugin-newrelic": "^0.3.2",
"@backstage/plugin-org": "^0.3.20",
"@backstage/plugin-pagerduty": "0.3.11",
"@backstage/plugin-rollbar": "^0.3.12",
"@backstage/plugin-scaffolder": "^0.11.1",
"@backstage/plugin-search": "^0.4.8",
"@backstage/plugin-sentry": "^0.3.19",
"@backstage/plugin-shortcuts": "^0.1.6",
"@backstage/plugin-tech-radar": "^0.4.5",
"@backstage/plugin-techdocs": "^0.11.0",
"@backstage/plugin-todo": "^0.1.7",
"@backstage/plugin-user-settings": "^0.3.3",
"@backstage/search-common": "^0.1.3",
"@backstage/integration-react": "^0.1.8",
"@backstage/plugin-api-docs": "^0.6.7",
"@backstage/plugin-badges": "^0.2.8",
"@backstage/plugin-catalog": "^0.6.14",
"@backstage/plugin-catalog-import": "^0.5.20",
"@backstage/plugin-catalog-react": "^0.4.5",
"@backstage/plugin-circleci": "^0.2.22",
"@backstage/plugin-cloudbuild": "^0.2.22",
"@backstage/plugin-code-coverage": "^0.1.10",
"@backstage/plugin-cost-insights": "^0.11.5",
"@backstage/plugin-explore": "^0.3.15",
"@backstage/plugin-gcp-projects": "^0.3.3",
"@backstage/plugin-github-actions": "^0.4.17",
"@backstage/plugin-graphiql": "^0.2.15",
"@backstage/plugin-home": "^0.2.1",
"@backstage/plugin-jenkins": "^0.5.5",
"@backstage/plugin-kafka": "^0.2.14",
"@backstage/plugin-kubernetes": "^0.4.12",
"@backstage/plugin-lighthouse": "^0.2.24",
"@backstage/plugin-newrelic": "^0.3.3",
"@backstage/plugin-org": "^0.3.21",
"@backstage/plugin-pagerduty": "0.3.12",
"@backstage/plugin-rollbar": "^0.3.13",
"@backstage/plugin-scaffolder": "^0.11.2",
"@backstage/plugin-search": "^0.4.9",
"@backstage/plugin-sentry": "^0.3.20",
"@backstage/plugin-shortcuts": "^0.1.7",
"@backstage/plugin-tech-radar": "^0.4.6",
"@backstage/plugin-techdocs": "^0.11.1",
"@backstage/plugin-todo": "^0.1.8",
"@backstage/plugin-user-settings": "^0.3.4",
"@backstage/search-common": "^0.2.0",
"@backstage/theme": "^0.2.10",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -14,20 +14,19 @@
* limitations under the License.
*/
import React, { useState } from 'react';
import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core';
import Pagination from '@material-ui/lab/Pagination';
import { Content, Header, Lifecycle, Page } from '@backstage/core-components';
import { CatalogResultListItem } from '@backstage/plugin-catalog';
import {
DefaultResultListItem,
SearchBar,
SearchFilter,
SearchResult,
SearchResultPager,
SearchType,
DefaultResultListItem,
} from '@backstage/plugin-search';
import { Content, Header, Lifecycle, Page } from '@backstage/core-components';
import { DocsResultListItem } from '@backstage/plugin-techdocs';
import { SearchResultSet } from '@backstage/search-common';
import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core';
import React from 'react';
const useStyles = makeStyles((theme: Theme) => ({
bar: {
@@ -43,57 +42,6 @@ const useStyles = makeStyles((theme: Theme) => ({
},
}));
// TODO: Move this into the search plugin once pagination is natively supported.
// See: https://github.com/backstage/backstage/issues/6062
const SearchResultList = ({ results }: SearchResultSet) => {
const pageSize = 10;
const [page, setPage] = useState(1);
const changePage = (_: any, pageIndex: number) => {
setPage(pageIndex);
};
const pageAmount = Math.ceil((results.length || 0) / pageSize);
return (
<>
<List>
{results
.slice(pageSize * (page - 1), pageSize * page)
.map(({ type, document }) => {
switch (type) {
case 'software-catalog':
return (
<CatalogResultListItem
key={document.location}
result={document}
/>
);
case 'techdocs':
return (
<DocsResultListItem
key={document.location}
result={document}
/>
);
default:
return (
<DefaultResultListItem
key={document.location}
result={document}
/>
);
}
})}
</List>
<Pagination
count={pageAmount}
page={page}
onChange={changePage}
showFirstButton
showLastButton
/>
</>
);
};
const SearchPage = () => {
const classes = useStyles();
return (
@@ -127,8 +75,37 @@ const SearchPage = () => {
</Grid>
<Grid item xs={9}>
<SearchResult>
{({ results }) => <SearchResultList results={results} />}
{({ results }) => (
<List>
{results.map(({ type, document }) => {
switch (type) {
case 'software-catalog':
return (
<CatalogResultListItem
key={document.location}
result={document}
/>
);
case 'techdocs':
return (
<DocsResultListItem
key={document.location}
result={document}
/>
);
default:
return (
<DefaultResultListItem
key={document.location}
result={document}
/>
);
}
})}
</List>
)}
</SearchResult>
<SearchResultPager />
</Grid>
</Grid>
</Content>
+8
View File
@@ -1,5 +1,13 @@
# @backstage/backend-common
## 0.9.1
### Patch Changes
- 714a2a918: Export type that are needed to implement a new `UrlReader`
- Updated dependencies
- @backstage/integration@0.6.3
## 0.9.0
### Minor Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
"version": "0.9.0",
"version": "0.9.1",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -33,7 +33,7 @@
"@backstage/config": "^0.1.8",
"@backstage/config-loader": "^0.6.7",
"@backstage/errors": "^0.1.1",
"@backstage/integration": "^0.6.2",
"@backstage/integration": "^0.6.3",
"@google-cloud/storage": "^5.8.0",
"@octokit/rest": "^18.5.3",
"@types/cors": "^2.8.6",
@@ -77,7 +77,7 @@
}
},
"devDependencies": {
"@backstage/cli": "^0.7.9",
"@backstage/cli": "^0.7.10",
"@backstage/test-utils": "^0.1.17",
"@types/archiver": "^5.1.0",
"@types/compression": "^1.7.0",
+18
View File
@@ -1,5 +1,23 @@
# example-backend
## 0.2.44
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-backend@0.13.6
- @backstage/plugin-scaffolder-backend@0.15.3
- @backstage/plugin-techdocs-backend@0.10.1
- @backstage/plugin-auth-backend@0.3.24
- @backstage/integration@0.6.3
- @backstage/plugin-search-backend@0.2.6
- @backstage/plugin-search-backend-module-elasticsearch@0.0.3
- @backstage/plugin-search-backend-module-pg@0.2.0
- @backstage/plugin-search-backend-node@0.4.2
- @backstage/catalog-model@0.9.1
- @backstage/backend-common@0.9.1
- example-app@0.2.44
## 0.2.43
### Patch Changes
+15 -15
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.2.43",
"version": "0.2.44",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,15 +27,15 @@
"migrate:create": "knex migrate:make -x ts"
},
"dependencies": {
"@backstage/backend-common": "^0.9.0",
"@backstage/backend-common": "^0.9.1",
"@backstage/catalog-client": "^0.3.17",
"@backstage/catalog-model": "^0.9.0",
"@backstage/catalog-model": "^0.9.1",
"@backstage/config": "^0.1.8",
"@backstage/integration": "^0.6.2",
"@backstage/integration": "^0.6.3",
"@backstage/plugin-app-backend": "^0.3.16",
"@backstage/plugin-auth-backend": "^0.3.23",
"@backstage/plugin-auth-backend": "^0.3.24",
"@backstage/plugin-badges-backend": "^0.1.9",
"@backstage/plugin-catalog-backend": "^0.13.5",
"@backstage/plugin-catalog-backend": "^0.13.6",
"@backstage/plugin-code-coverage-backend": "^0.1.10",
"@backstage/plugin-graphql-backend": "^0.1.9",
"@backstage/plugin-jenkins-backend": "^0.1.4",
@@ -43,19 +43,19 @@
"@backstage/plugin-kafka-backend": "^0.2.9",
"@backstage/plugin-proxy-backend": "^0.2.12",
"@backstage/plugin-rollbar-backend": "^0.1.14",
"@backstage/plugin-scaffolder-backend": "^0.15.2",
"@backstage/plugin-scaffolder-backend": "^0.15.3",
"@backstage/plugin-scaffolder-backend-module-rails": "^0.1.5",
"@backstage/plugin-search-backend": "^0.2.5",
"@backstage/plugin-search-backend-node": "^0.4.0",
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.2",
"@backstage/plugin-search-backend-module-pg": "^0.1.3",
"@backstage/plugin-techdocs-backend": "^0.10.0",
"@backstage/plugin-search-backend": "^0.2.6",
"@backstage/plugin-search-backend-node": "^0.4.2",
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.3",
"@backstage/plugin-search-backend-module-pg": "^0.2.0",
"@backstage/plugin-techdocs-backend": "^0.10.1",
"@backstage/plugin-todo-backend": "^0.1.11",
"@gitbeaker/node": "^30.2.0",
"@octokit/rest": "^18.5.3",
"azure-devops-node-api": "^10.2.2",
"azure-devops-node-api": "^11.0.1",
"dockerode": "^3.2.1",
"example-app": "^0.2.43",
"example-app": "^0.2.44",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"express-prom-bundle": "^6.3.6",
@@ -67,7 +67,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.7.8",
"@backstage/cli": "^0.7.10",
"@types/dockerode": "^3.2.1",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5"
+1 -1
View File
@@ -68,7 +68,7 @@ export default async function createPlugin({
// particular collator gathers entities from the software catalog.
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
collator: DefaultCatalogCollator.fromConfig(config, { discovery }),
});
indexBuilder.addCollator({
+12
View File
@@ -1,5 +1,17 @@
# @backstage/catalog-model
## 0.9.1
### Patch Changes
- 13dc3735c: Add an optional `metadata.title` field to all entity kinds.
This used to be available on only the `Template` kind, and we have decided that the metadata block should be the same for all kinds. A title can be useful especially in large and complex catalogs where users have a tough time navigating or discerning among the entities.
It also carries some risk. You do not want to end up giving a title that collides with an actual name, which at best leads to confusion and at worst could be a liability. We do not perform any collision detection in the catalog. If you want to disallow this facility you may want to add a small processor that makes sure it's not set.
At the time of writing this message, only the scaffolder actually makes use of this field for display purposes.
## 0.9.0
### Minor Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
"version": "0.9.0",
"version": "0.9.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -40,7 +40,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@backstage/cli": "^0.7.2",
"@backstage/cli": "^0.7.10",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
+7
View File
@@ -1,5 +1,12 @@
# @backstage/cli
## 0.7.10
### Patch Changes
- 5e803edb8: Added support for importing font files. Imports in CSS via `url()` are supported for the final frontend bundle, but not for packages that are built for publishing. Module imports of fonts files from TypeScript are supported everywhere.
- b5118ff76: Updated dependencies
## 0.7.9
### Patch Changes
+20
View File
@@ -68,6 +68,26 @@ declare module '*.svg' {
export default src;
}
declare module '*.eot' {
const src: string;
export default src;
}
declare module '*.woff' {
const src: string;
export default src;
}
declare module '*.woff2' {
const src: string;
export default src;
}
declare module '*.ttf' {
const src: string;
export default src;
}
declare module '*.css' {
const classes: { readonly [key: string]: string };
export default classes;
+6 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
"version": "0.7.9",
"version": "0.7.10",
"private": false,
"publishConfig": {
"access": "public"
@@ -51,7 +51,7 @@
"@svgr/rollup": "5.5.x",
"@svgr/webpack": "5.5.x",
"@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^v4.28.3",
"@typescript-eslint/eslint-plugin": "^v4.30.0",
"@typescript-eslint/parser": "^v4.28.3",
"@yarnpkg/lockfile": "^1.1.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
@@ -120,12 +120,12 @@
"yn": "^4.0.0"
},
"devDependencies": {
"@backstage/backend-common": "^0.9.0",
"@backstage/backend-common": "^0.9.1",
"@backstage/config": "^0.1.8",
"@backstage/core-components": "^0.3.3",
"@backstage/core-components": "^0.4.0",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/core-app-api": "^0.1.10",
"@backstage/dev-utils": "^0.2.7",
"@backstage/core-app-api": "^0.1.11",
"@backstage/dev-utils": "^0.2.8",
"@backstage/test-utils": "^0.1.17",
"@backstage/theme": "^0.2.10",
"@types/diff": "^5.0.0",
+11 -1
View File
@@ -80,7 +80,17 @@ export const makeConfigs = async (
postcss(),
forwardFileImports({
exclude: /\.icon\.svg$/,
include: [/\.svg$/, /\.png$/, /\.gif$/, /\.jpg$/, /\.jpeg$/],
include: [
/\.svg$/,
/\.png$/,
/\.gif$/,
/\.jpg$/,
/\.jpeg$/,
/\.eot$/,
/\.woff$/,
/\.woff2$/,
/\.ttf$/,
],
}),
json(),
yaml(),
@@ -89,6 +89,17 @@ export const transforms = (options: TransformOptions): Transforms => {
name: 'static/[name].[hash:8].[ext]',
},
},
{
test: /\.(eot|woff|woff2|ttf)$/,
use: [
{
loader: require.resolve('file-loader'),
options: {
name: 'static/[name].[hash:8].[ext]',
},
},
],
},
{
test: /\.ya?ml$/,
use: require.resolve('yml-loader'),
+8
View File
@@ -1,5 +1,13 @@
# @backstage/codemods
## 0.1.12
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.4.0
- @backstage/core-app-api@0.1.11
## 0.1.11
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/codemods",
"description": "A collection of codemods for Backstage projects",
"version": "0.1.11",
"version": "0.1.12",
"private": false,
"publishConfig": {
"access": "public",
+7
View File
@@ -1,5 +1,12 @@
# @backstage/core-app-api
## 0.1.11
### Patch Changes
- Updated dependencies
- @backstage/core-components@0.4.0
## 0.1.10
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
"version": "0.1.10",
"version": "0.1.11",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-components": "^0.3.3",
"@backstage/core-components": "^0.4.0",
"@backstage/config": "^0.1.8",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/theme": "^0.2.10",
@@ -44,7 +44,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/cli": "^0.7.9",
"@backstage/cli": "^0.7.10",
"@backstage/test-utils": "^0.1.17",
"@backstage/test-utils-core": "^0.1.2",
"@testing-library/jest-dom": "^5.10.1",
+15
View File
@@ -1,5 +1,20 @@
# @backstage/core-components
## 0.4.0
### Minor Changes
- 3ed78fca3: Changed the `titleComponent` prop on `ContentHeader` to accept `ReactNode` instead of a React `ComponentType`. Usages of this prop should be converted from passing a component to passing in the rendered element:
```diff
-<ContentHeader titleComponent={MyComponent}>
+<ContentHeader titleComponent={<MyComponent />}>
```
### Patch Changes
- e0a6aea82: Bumped `react-hook-form` to `^7.12.2`
## 0.3.3
### Patch Changes
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
"version": "0.3.3",
"version": "0.4.0",
"private": false,
"publishConfig": {
"access": "public",
@@ -70,8 +70,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
"@backstage/core-app-api": "^0.1.10",
"@backstage/cli": "^0.7.9",
"@backstage/core-app-api": "^0.1.11",
"@backstage/cli": "^0.7.10",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
+6
View File
@@ -1,5 +1,11 @@
# @backstage/create-app
## 0.3.39
### Patch Changes
- 25924638b: Minor tweaks to the create-app template to match earlier documented changes
## 0.3.38
### Patch Changes
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "Create app package for Backstage",
"version": "0.3.38",
"version": "0.3.39",
"private": false,
"publishConfig": {
"access": "public"
@@ -10,6 +10,7 @@ import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
export default async function createPlugin({
logger,
discovery,
config,
}: PluginEnvironment) {
// Initialize a connection to a search engine.
const searchEngine = new LunrSearchEngine({ logger });
@@ -19,7 +20,7 @@ export default async function createPlugin({
// particular collator gathers entities from the software catalog.
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
collator: DefaultCatalogCollator.fromConfig(config, { discovery }),
});
// The scheduler controls when documents are gathered from collators and sent
+11
View File
@@ -1,5 +1,16 @@
# @backstage/dev-utils
## 0.2.8
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@0.4.5
- @backstage/core-components@0.4.0
- @backstage/catalog-model@0.9.1
- @backstage/core-app-api@0.1.11
- @backstage/integration-react@0.1.8
## 0.2.7
### Patch Changes
+7 -7
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/dev-utils",
"description": "Utilities for developing Backstage plugins.",
"version": "0.2.7",
"version": "0.2.8",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,12 +29,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/core-app-api": "^0.1.9",
"@backstage/core-components": "^0.3.2",
"@backstage/core-app-api": "^0.1.11",
"@backstage/core-components": "^0.4.0",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/catalog-model": "^0.9.0",
"@backstage/integration-react": "^0.1.7",
"@backstage/plugin-catalog-react": "^0.4.3",
"@backstage/catalog-model": "^0.9.1",
"@backstage/integration-react": "^0.1.8",
"@backstage/plugin-catalog-react": "^0.4.5",
"@backstage/test-utils": "^0.1.17",
"@backstage/theme": "^0.2.10",
"@material-ui/core": "^4.12.2",
@@ -50,7 +50,7 @@
"react-router-dom": "6.0.0-beta.0"
},
"devDependencies": {
"@backstage/cli": "^0.7.9",
"@backstage/cli": "^0.7.10",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32"
},
+8
View File
@@ -1,5 +1,13 @@
# @backstage/integration-react
## 0.1.8
### Patch Changes
- Updated dependencies
- @backstage/integration@0.6.3
- @backstage/core-components@0.4.0
## 0.1.7
### Patch Changes
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration-react",
"version": "0.1.7",
"version": "0.1.8",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -21,9 +21,9 @@
},
"dependencies": {
"@backstage/config": "^0.1.6",
"@backstage/core-components": "^0.3.1",
"@backstage/core-components": "^0.4.0",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/integration": "^0.6.0",
"@backstage/integration": "^0.6.3",
"@backstage/theme": "^0.2.9",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -33,8 +33,8 @@
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.8",
"@backstage/dev-utils": "^0.2.6",
"@backstage/cli": "^0.7.10",
"@backstage/dev-utils": "^0.2.8",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
+6
View File
@@ -1,5 +1,11 @@
# @backstage/integration
## 0.6.3
### Patch Changes
- 5dca42b17: Update to properly handle Azure DevOps Server download URL
## 0.6.2
### Patch Changes
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
"version": "0.6.2",
"version": "0.6.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -37,7 +37,7 @@
"luxon": "^2.0.2"
},
"devDependencies": {
"@backstage/cli": "^0.7.9",
"@backstage/cli": "^0.7.10",
"@backstage/config-loader": "^0.6.7",
"@backstage/test-utils": "^0.1.17",
"@types/jest": "^26.0.7",
@@ -97,5 +97,20 @@ describe('azure core', () => {
);
expect(new URL(result).searchParams.get('scopePath')).toEqual('docs');
});
it.each([
{
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name',
result:
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?recursionLevel=full&download=true&api-version=6.0',
},
{
url: 'https://api.com/org-name/project-name/_git/repo-name',
result:
'https://api.com/org-name/project-name/_apis/git/repositories/repo-name/items?recursionLevel=full&download=true&api-version=6.0',
},
])('should handle happy path %#', async ({ url, result }) => {
expect(getAzureDownloadUrl(url)).toBe(result);
});
});
});
+11 -1
View File
@@ -100,7 +100,17 @@ export function getAzureDownloadUrl(url: string): string {
? `&scopePath=${encodeURIComponent(filepath)}`
: '';
return `${protocol}://${resource}/${organization}/${project}/_apis/git/repositories/${repoName}/items?recursionLevel=full&download=true&api-version=6.0${scopePath}`;
if (resource === 'dev.azure.com') {
return `${protocol}://${resource}/${organization}/${project}/_apis/git/repositories/${repoName}/items?recursionLevel=full&download=true&api-version=6.0${scopePath}`;
}
// For Azure DevOps Server `parseGitUrl` returns the same values
// for `organization` and `project` like this: `organization/project/_git`
// so we drop `project` and then strip `/_git` from `organization`
return `${protocol}://${resource}/${organization.replace(
'/_git',
'',
)}/_apis/git/repositories/${repoName}/items?recursionLevel=full&download=true&api-version=6.0${scopePath}`;
}
/**
+9
View File
@@ -1,5 +1,14 @@
# @backstage/search-common
## 0.2.0
### Minor Changes
- a13f21cdc: Implement optional `pageCursor` based paging in search.
To use paging in your app, add a `<SearchResultPager />` to your
`SearchPage.tsx`.
## 0.1.3
### Patch Changes
+5 -1
View File
@@ -53,7 +53,7 @@ export interface SearchQuery {
// (undocumented)
filters?: JsonObject;
// (undocumented)
pageCursor: string;
pageCursor?: string;
// (undocumented)
term: string;
// (undocumented)
@@ -74,6 +74,10 @@ export interface SearchResult {
//
// @public (undocumented)
export interface SearchResultSet {
// (undocumented)
nextPageCursor?: string;
// (undocumented)
previousPageCursor?: string;
// (undocumented)
results: SearchResult[];
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/search-common",
"description": "Common functionalities for Search, to be shared between various search-enabled plugins",
"version": "0.1.3",
"version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -39,7 +39,7 @@
"@backstage/config": "^0.1.6"
},
"devDependencies": {
"@backstage/cli": "^0.7.7"
"@backstage/cli": "^0.7.10"
},
"jest": {
"roots": [
+3 -1
View File
@@ -19,7 +19,7 @@ export interface SearchQuery {
term: string;
filters?: JsonObject;
types?: string[];
pageCursor: string;
pageCursor?: string;
}
export interface SearchResult {
@@ -29,6 +29,8 @@ export interface SearchResult {
export interface SearchResultSet {
results: SearchResult[];
nextPageCursor?: string;
previousPageCursor?: string;
}
/**
+37
View File
@@ -1,5 +1,42 @@
# @backstage/techdocs-common
## 0.10.0
### Minor Changes
- 8b0f6f860: Set the correct `edit_uri` or `repo_url` for documentation pages that are hosted on GitHub and GitLab.
The constructor of the `TechDocsGenerator` changed.
Prefer the use of `TechdocsGenerator.fromConfig(…)` instead:
```diff
- const techdocsGenerator = new TechdocsGenerator({
+ const techdocsGenerator = TechdocsGenerator.fromConfig(config, {
logger,
containerRunner,
- config,
});
```
### Patch Changes
- 30ed662a3: Adding in-context search to TechDocs Reader component. Using existing search-backend to query for indexed search results scoped into a specific entity's techdocs. Needs TechDocsCollator enabled on the backend to work.
Adding extra information to indexed tech docs documents for search.
- 3624616e7: "Local" (out-of-the-box) publisher explicitly follows lower-case entity triplet
logic.
- 67ba7e088: Only write the updated `mkdocs.yml` file if the content was updated.
This keeps local files unchanged if the `dir` annotation is used in combination with the `file` location.
- 8eab6be6a: Force using `posix` path for cloud storage
- Updated dependencies
- @backstage/integration@0.6.3
- @backstage/search-common@0.2.0
- @backstage/catalog-model@0.9.1
- @backstage/backend-common@0.9.1
## 0.9.0
### Minor Changes
+3 -1
View File
@@ -249,10 +249,12 @@ export class TechdocsGenerator implements GeneratorBase {
logger,
containerRunner,
config,
scmIntegrations,
}: {
logger: Logger_2;
containerRunner: ContainerRunner;
config: Config;
scmIntegrations: ScmIntegrationRegistry;
});
// (undocumented)
static fromConfig(
@@ -264,7 +266,7 @@ export class TechdocsGenerator implements GeneratorBase {
containerRunner: ContainerRunner;
logger: Logger_2;
},
): Promise<TechdocsGenerator>;
): TechdocsGenerator;
// (undocumented)
run({
inputDir,
+7 -6
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.9.0",
"version": "0.10.0",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -38,18 +38,19 @@
"dependencies": {
"@azure/identity": "^1.5.0",
"@azure/storage-blob": "^12.5.0",
"@backstage/backend-common": "^0.9.0",
"@backstage/catalog-model": "^0.9.0",
"@backstage/backend-common": "^0.9.1",
"@backstage/catalog-model": "^0.9.1",
"@backstage/config": "^0.1.8",
"@backstage/errors": "^0.1.1",
"@backstage/search-common": "^0.1.3",
"@backstage/integration": "^0.6.2",
"@backstage/search-common": "^0.2.0",
"@backstage/integration": "^0.6.3",
"@google-cloud/storage": "^5.6.0",
"@trendyol-js/openstack-swift-sdk": "^0.0.4",
"@types/express": "^4.17.6",
"aws-sdk": "^2.840.0",
"express": "^4.17.1",
"fs-extra": "9.1.0",
"git-url-parse": "~11.4.4",
"js-yaml": "^4.0.0",
"json5": "^2.1.3",
"mime-types": "^2.1.27",
@@ -59,7 +60,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/cli": "^0.7.8",
"@backstage/cli": "^0.7.10",
"@types/fs-extra": "^9.0.5",
"@types/js-yaml": "^4.0.0",
"@types/mime-types": "^2.1.0",
@@ -0,0 +1,3 @@
site_name: Test site name
site_description: Test site description
# This is a comment that is removed after editing
@@ -0,0 +1,4 @@
site_name: Test site name
site_description: Test site description
edit_uri: https://github.com/backstage/backstage/edit/main/docs
@@ -44,10 +44,9 @@ describe('generators', () => {
it('should return correct registered generator', async () => {
const generators = new Generators();
const techdocs = new TechdocsGenerator({
const techdocs = TechdocsGenerator.fromConfig(new ConfigReader({}), {
logger,
containerRunner,
config: new ConfigReader({}),
});
generators.register('techdocs', techdocs);
@@ -38,10 +38,9 @@ export class Generators implements GeneratorBuilder {
): Promise<GeneratorBuilder> {
const generators = new Generators();
const techdocsGenerator = new TechdocsGenerator({
const techdocsGenerator = TechdocsGenerator.fromConfig(config, {
logger,
containerRunner,
config,
});
generators.register('techdocs', techdocsGenerator);
@@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import fs from 'fs-extra';
import mockFs from 'mock-fs';
import os from 'os';
import path, { resolve as resolvePath } from 'path';
import { ParsedLocationAnnotation } from '../../helpers';
import { RemoteProtocol } from '../prepare/types';
import {
addBuildTimestampMetadata,
getGeneratorKey,
getMkdocsYml,
getRepoUrlFromLocationAnnotation,
isValidRepoUrlForMkdocs,
patchMkdocsYmlPreBuild,
storeEtagMetadata,
validateMkdocsYaml,
@@ -48,6 +49,9 @@ const mkdocsYmlWithExtensions = fs.readFileSync(
const mkdocsYmlWithRepoUrl = fs.readFileSync(
resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'),
);
const mkdocsYmlWithEditUri = fs.readFileSync(
resolvePath(__filename, '../__fixtures__/mkdocs_with_edit_uri.yml'),
);
const mkdocsYmlWithValidDocDir = fs.readFileSync(
resolvePath(__filename, '../__fixtures__/mkdocs_valid_doc_dir.yml'),
);
@@ -57,9 +61,14 @@ const mkdocsYmlWithInvalidDocDir = fs.readFileSync(
const mkdocsYmlWithInvalidDocDir2 = fs.readFileSync(
resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir2.yml'),
);
const mkdocsYmlWithComments = fs.readFileSync(
resolvePath(__filename, '../__fixtures__/mkdocs_with_comments.yml'),
);
const mockLogger = getVoidLogger();
const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir';
const scmIntegrations = ScmIntegrations.fromConfig(new ConfigReader({}));
describe('helpers', () => {
describe('getGeneratorKey', () => {
it('should return techdocs as the only generator key', () => {
@@ -68,120 +77,85 @@ describe('helpers', () => {
});
});
describe('isValidRepoUrlForMkdocs', () => {
it('should return true for valid repo_url values for mkdocs', () => {
const validRepoUrls = [
'https://github.com/org/repo',
'https://github.com/backstage/backstage/',
'https://github.com/org123/repo1-2-3/',
'http://github.com/insecureOrg/insecureRepo',
'https://gitlab.com/org/repo',
'https://gitlab.com/backstage/backstage/',
'https://gitlab.com/org123/repo1-2-3/',
'http://gitlab.com/insecureOrg/insecureRepo',
];
const validRemoteProtocols = ['github', 'gitlab'];
validRepoUrls.forEach(url => {
validRemoteProtocols.forEach(targetType => {
expect(
isValidRepoUrlForMkdocs(url, targetType as RemoteProtocol),
).toBe(true);
});
});
});
it('should return false for invalid repo_urls values for mkdocs', () => {
const invalidRepoUrls = [
'git@github.com:org/repo',
'https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend',
];
invalidRepoUrls.forEach(url => {
expect(isValidRepoUrlForMkdocs(url, 'github')).toBe(false);
});
});
it('should return false for unsupported remote protocols', () => {
const validRepoUrl = 'https://github.com/backstage/backstage';
const unsupportedRemoteProtocols = ['dir', 'file', 'url'];
unsupportedRemoteProtocols.forEach(targetType => {
expect(
isValidRepoUrlForMkdocs(validRepoUrl, targetType as RemoteProtocol),
).toBe(false);
});
});
});
describe('getRepoUrlFromLocationAnnotation', () => {
it('should return undefined for unsupported location type', () => {
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
it.each`
url | repo_url | edit_uri
${'https://github.com/backstage/backstage'} | ${'https://github.com/backstage/backstage'} | ${undefined}
${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${undefined} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/docs'}
${'https://github.com/backstage/backstage/tree/main/'} | ${undefined} | ${'https://github.com/backstage/backstage/edit/main/docs'}
${'https://gitlab.com/backstage/backstage'} | ${'https://gitlab.com/backstage/backstage'} | ${undefined}
${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${undefined} | ${'https://gitlab.com/backstage/backstage/-/edit/main/examples/techdocs/docs'}
${'https://gitlab.com/backstage/backstage/-/blob/main/'} | ${undefined} | ${'https://gitlab.com/backstage/backstage/-/edit/main/docs'}
`('should convert $url', ({ url: target, repo_url, edit_uri }) => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'url',
target,
};
expect(
getRepoUrlFromLocationAnnotation(
parsedLocationAnnotation,
scmIntegrations,
),
).toEqual({ repo_url, edit_uri });
});
it.each`
url | edit_uri
${'https://github.com/backstage/backstage/tree/main/examples/techdocs/'} | ${'https://github.com/backstage/backstage/edit/main/examples/techdocs/custom/folder'}
${'https://github.com/backstage/backstage/tree/main/'} | ${'https://github.com/backstage/backstage/edit/main/custom/folder'}
${'https://gitlab.com/backstage/backstage/-/blob/main/examples/techdocs/'} | ${'https://gitlab.com/backstage/backstage/-/edit/main/examples/techdocs/custom/folder'}
${'https://gitlab.com/backstage/backstage/-/blob/main/'} | ${'https://gitlab.com/backstage/backstage/-/edit/main/custom/folder'}
`(
'should convert $url with custom docsFolder',
({ url: target, edit_uri }) => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'url',
target,
};
expect(
getRepoUrlFromLocationAnnotation(
parsedLocationAnnotation,
scmIntegrations,
'./custom/folder',
),
).toEqual({ edit_uri });
},
);
it.each`
url
${'https://bitbucket.org/backstage/backstage/src/master/examples/techdocs/'}
${'https://bitbucket.org/backstage/backstage/src/master/'}
${'https://dev.azure.com/organization/project/_git/repository?path=%2Fexamples%2Ftechdocs'}
${'https://dev.azure.com/organization/project/_git/repository?path=%2F'}
`('should ignore $url', ({ url: target }) => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'url',
target,
};
expect(
getRepoUrlFromLocationAnnotation(
parsedLocationAnnotation,
scmIntegrations,
),
).toEqual({});
});
it('should ignore unsupported location type', () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'dir',
target: '/home/user/workspace/docs-repository',
};
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
type: 'file' as RemoteProtocol,
target: '/home/user/workspace/docs-repository/catalog-info.yaml',
};
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
type: 'url',
target: 'https://my-website.com/storage/this/docs/repository',
};
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
undefined,
);
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
undefined,
);
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
undefined,
);
});
it('should return correct target url for supported hosts', () => {
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
target: 'https://github.com/backstage/backstage.git',
};
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe(
'https://github.com/backstage/backstage',
);
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
target: 'https://github.com/org/repo',
};
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe(
'https://github.com/org/repo',
);
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
type: 'gitlab' as RemoteProtocol,
target: 'https://gitlab.com/org/repo',
};
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe(
'https://gitlab.com/org/repo',
);
const parsedLocationAnnotation4: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
target:
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
};
expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation4)).toBe(
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
);
expect(
getRepoUrlFromLocationAnnotation(
parsedLocationAnnotation,
scmIntegrations,
),
).toEqual({});
});
});
@@ -190,7 +164,9 @@ describe('helpers', () => {
mockFs({
'/mkdocs.yml': mkdocsYml,
'/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
'/mkdocs_with_edit_uri.yml': mkdocsYmlWithEditUri,
'/mkdocs_with_extensions.yml': mkdocsYmlWithExtensions,
'/mkdocs_with_comments.yml': mkdocsYmlWithComments,
});
});
@@ -198,9 +174,9 @@ describe('helpers', () => {
mockFs.restore();
});
it('should add repo_url to mkdocs.yml', async () => {
it('should add edit_uri to mkdocs.yml', async () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
type: 'url',
target: 'https://github.com/backstage/backstage',
};
@@ -208,6 +184,7 @@ describe('helpers', () => {
'/mkdocs.yml',
mockLogger,
parsedLocationAnnotation,
scmIntegrations,
);
const updatedMkdocsYml = await fs.readFile('/mkdocs.yml');
@@ -219,7 +196,7 @@ describe('helpers', () => {
it('should add repo_url to mkdocs.yml that contains custom yaml tags', async () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
type: 'url',
target: 'https://github.com/backstage/backstage',
};
@@ -227,6 +204,7 @@ describe('helpers', () => {
'/mkdocs_with_extensions.yml',
mockLogger,
parsedLocationAnnotation,
scmIntegrations,
);
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_extensions.yml');
@@ -241,7 +219,7 @@ describe('helpers', () => {
it('should not override existing repo_url in mkdocs.yml', async () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'github' as RemoteProtocol,
type: 'url',
target: 'https://github.com/neworg/newrepo',
};
@@ -249,6 +227,7 @@ describe('helpers', () => {
'/mkdocs_with_repo_url.yml',
mockLogger,
parsedLocationAnnotation,
scmIntegrations,
);
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml');
@@ -260,6 +239,51 @@ describe('helpers', () => {
'repo_url: https://github.com/neworg/newrepo',
);
});
it('should not override existing edit_uri in mkdocs.yml', async () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'url',
target: 'https://github.com/neworg/newrepo',
};
await patchMkdocsYmlPreBuild(
'/mkdocs_with_edit_uri.yml',
mockLogger,
parsedLocationAnnotation,
scmIntegrations,
);
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_edit_uri.yml');
expect(updatedMkdocsYml.toString()).toContain(
'edit_uri: https://github.com/backstage/backstage/edit/main/docs',
);
expect(updatedMkdocsYml.toString()).not.toContain(
'https://github.com/neworg/newrepo',
);
});
it('should not update mkdocs.yml if nothing should be changed', async () => {
const parsedLocationAnnotation: ParsedLocationAnnotation = {
type: 'dir',
target: '/unsupported/path',
};
await patchMkdocsYmlPreBuild(
'/mkdocs_with_comments.yml',
mockLogger,
parsedLocationAnnotation,
scmIntegrations,
);
const updatedMkdocsYml = await fs.readFile('/mkdocs_with_comments.yml');
expect(updatedMkdocsYml.toString()).toContain(
'# This is a comment that is removed after editing',
);
expect(updatedMkdocsYml.toString()).not.toContain('edit_uri');
expect(updatedMkdocsYml.toString()).not.toContain('repo_url');
});
});
describe('addBuildTimestampMetadata', () => {
@@ -14,10 +14,12 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
import { isChildPath } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { spawn } from 'child_process';
import fs from 'fs-extra';
import gitUrlParse from 'git-url-parse';
import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml';
import path, { resolve as resolvePath } from 'path';
import { PassThrough, Writable } from 'stream';
@@ -80,63 +82,42 @@ export const runCommand = async ({
};
/**
* Return true if mkdocs can compile docs with provided repo_url
*
* Valid repo_url examples in mkdocs.yml
* - https://github.com/backstage/backstage
* - https://gitlab.com/org/repo/
* - http://github.com/backstage/backstage
* - A http(s) protocol URL to the root of the repository
*
* Invalid repo_url examples in mkdocs.yml
* - https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component
* - (anything that is not valid as described above)
*
* @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml
* @param {string} locationType Type of source code host - github, gitlab, dir, url, etc.
* @returns {boolean}
*/
export const isValidRepoUrlForMkdocs = (
repoUrl: string,
locationType: string,
): boolean => {
// Trim trailing slash
const cleanRepoUrl = repoUrl.replace(/\/$/, '');
if (locationType === 'github' || locationType === 'gitlab') {
// A valid repoUrl to the root of the repository will be split into 5 strings if split using the / delimiter.
// We do not want URLs which have more than that number of forward slashes since they will signify a non-root location
// Note: This is not the best possible implementation but will work most of the times.. Feel free to improve or
// highlight edge cases.
return cleanRepoUrl.split('/').length === 5;
}
return false;
};
/**
* Return a valid URL of the repository used in backstage.io/techdocs-ref annotation.
* Return undefined if the `target` is not valid in context of repo_url in mkdocs.yml
* Alter URL so that it is a valid repo_url config in mkdocs.yml
* Return the source url for MkDocs based on the backstage.io/techdocs-ref annotation.
* Depending on the type of target, it can either return a repo_url, an edit_uri, both, or none.
*
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
* @returns {string | undefined}
* @param {ScmIntegrationRegistry} scmIntegrations the scmIntegration to do url transformations
* @param {string} docsFolder the configured docs folder in the mkdocs.yml (defaults to 'docs')
* @returns the settings for the mkdocs.yml
*/
export const getRepoUrlFromLocationAnnotation = (
parsedLocationAnnotation: ParsedLocationAnnotation,
): string | undefined => {
scmIntegrations: ScmIntegrationRegistry,
docsFolder: string = 'docs',
): { repo_url?: string; edit_uri?: string } => {
const { type: locationType, target } = parsedLocationAnnotation;
// Add more options from the RemoteProtocol type of parsedLocationAnnotation.type here
// when TechDocs supports more hosts and if mkdocs can generated an Edit URL for them.
const supportedHosts = ['github', 'gitlab'];
if (locationType === 'url') {
const integration = scmIntegrations.byUrl(target);
if (supportedHosts.includes(locationType)) {
// Trim .git or .git/ from the end of repository url
return target.replace(/.git\/*$/, '');
// We only support it for github and gitlab for now as the edit_uri
// is not properly supported for others yet.
if (integration && ['github', 'gitlab'].includes(integration.type)) {
// handle the case where a user manually writes url:https://github.com/backstage/backstage i.e. without /blob/...
const { filepathtype } = gitUrlParse(target);
if (filepathtype === '') {
return { repo_url: target };
}
const sourceFolder = integration.resolveUrl({
url: `./${docsFolder}`,
base: target,
});
return { edit_uri: integration.resolveEditUrl(sourceFolder) };
}
}
return undefined;
return {};
};
class UnknownTag {
@@ -217,7 +198,7 @@ export const validateMkdocsYaml = async (
* Update the mkdocs.yml file before TechDocs generator uses it to generate docs site.
*
* List of tasks:
* - Add repo_url if it does not exists
* - Add repo_url or edit_uri if it does not exists
* If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default.
* If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get
* the repository URL.
@@ -228,12 +209,18 @@ export const validateMkdocsYaml = async (
* @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
* @param {Logger} logger
* @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type
* @param {ScmIntegrationRegistry} scmIntegrations the scmIntegration to do url transformations
*/
export const patchMkdocsYmlPreBuild = async (
mkdocsYmlPath: string,
logger: Logger,
parsedLocationAnnotation: ParsedLocationAnnotation,
scmIntegrations: ScmIntegrationRegistry,
) => {
// We only want to override the mkdocs.yml if it has actually changed. This is relevant if
// used with a 'dir' location on the file system as this would permanently update the file.
let didEdit = false;
let mkdocsYmlFileString;
try {
mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
@@ -260,25 +247,37 @@ export const patchMkdocsYmlPreBuild = async (
return;
}
// Add repo_url to mkdocs.yml if it is missing. This will enable the Page edit button generated by MkDocs.
if (!('repo_url' in mkdocsYml)) {
const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation);
if (repoUrl !== undefined) {
// mkdocs.yml will not build with invalid repo_url. So, make sure it is valid.
// TODO: this is no longer working/meaningful because annotation type is
// now only ever "url" or "dir." Should be re-implemented!
if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) {
mkdocsYml.repo_url = repoUrl;
}
// Add edit_uri and/or repo_url to mkdocs.yml if it is missing.
// This will enable the Page edit button generated by MkDocs.
// If the either has been set, keep the original value
if (!('repo_url' in mkdocsYml) && !('edit_uri' in mkdocsYml)) {
const result = getRepoUrlFromLocationAnnotation(
parsedLocationAnnotation,
scmIntegrations,
mkdocsYml.docs_dir,
);
if (result.repo_url || result.edit_uri) {
mkdocsYml.repo_url = result.repo_url;
mkdocsYml.edit_uri = result.edit_uri;
didEdit = true;
logger.info(
`Set ${JSON.stringify(
result,
)}. You can disable this feature by manually setting 'repo_url' or 'edit_uri' according to the MkDocs documentation at https://www.mkdocs.org/user-guide/configuration/#repo_url`,
);
}
}
try {
await fs.writeFile(
mkdocsYmlPath,
yaml.dump(mkdocsYml, { schema: MKDOCS_SCHEMA }),
'utf8',
);
if (didEdit) {
await fs.writeFile(
mkdocsYmlPath,
yaml.dump(mkdocsYml, { schema: MKDOCS_SCHEMA }),
'utf8',
);
}
} catch (error) {
logger.warn(
`Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`,
@@ -18,6 +18,10 @@ import { ContainerRunner } from '@backstage/backend-common';
import { Config } from '@backstage/config';
import path from 'path';
import { Logger } from 'winston';
import {
ScmIntegrationRegistry,
ScmIntegrations,
} from '@backstage/integration';
import {
addBuildTimestampMetadata,
getMkdocsYml,
@@ -39,29 +43,39 @@ export class TechdocsGenerator implements GeneratorBase {
private readonly logger: Logger;
private readonly containerRunner: ContainerRunner;
private readonly options: GeneratorConfig;
private readonly scmIntegrations: ScmIntegrationRegistry;
static async fromConfig(
static fromConfig(
config: Config,
{
containerRunner,
logger,
}: { containerRunner: ContainerRunner; logger: Logger },
) {
return new TechdocsGenerator({ logger, containerRunner, config });
const scmIntegrations = ScmIntegrations.fromConfig(config);
return new TechdocsGenerator({
logger,
containerRunner,
config,
scmIntegrations,
});
}
constructor({
logger,
containerRunner,
config,
scmIntegrations,
}: {
logger: Logger;
containerRunner: ContainerRunner;
config: Config;
scmIntegrations: ScmIntegrationRegistry;
}) {
this.logger = logger;
this.options = readGeneratorConfig(config, logger);
this.containerRunner = containerRunner;
this.scmIntegrations = scmIntegrations;
}
public async run({
@@ -74,16 +88,19 @@ export class TechdocsGenerator implements GeneratorBase {
}: GeneratorRunOptions): Promise<void> {
// Do some updates to mkdocs.yml before generating docs e.g. adding repo_url
const { path: mkdocsYmlPath, content } = await getMkdocsYml(inputDir);
// validate the docs_dir first
await validateMkdocsYaml(inputDir, content);
if (parsedLocationAnnotation) {
await patchMkdocsYmlPreBuild(
mkdocsYmlPath,
childLogger,
parsedLocationAnnotation,
this.scmIntegrations,
);
}
await validateMkdocsYaml(inputDir, content);
// Directories to bind on container
const mountDirs = {
[inputDir]: '/input',
@@ -25,10 +25,10 @@ import mockFs from 'mock-fs';
import * as os from 'os';
import { LocalPublish } from './local';
const createMockEntity = (annotations = {}) => {
const createMockEntity = (annotations = {}, lowerCase = false) => {
return {
apiVersion: 'version',
kind: 'TestKind',
kind: lowerCase ? 'testkind' : 'TestKind',
metadata: {
name: 'test-component-name',
annotations: {
@@ -65,11 +65,42 @@ describe('local publisher', () => {
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
const mockEntity = createMockEntity();
const lowerMockEntity = createMockEntity(undefined, true);
await publisher.publish({ entity: mockEntity, directory: tmpDir });
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
// Lower/upper should be treated the same.
expect(await publisher.hasDocsBeenGenerated(lowerMockEntity)).toBe(true);
mockFs.restore();
});
it('should respect legacy casing', async () => {
mockFs({
[tmpDir]: {
'index.html': '',
},
});
const mockConfig = new ConfigReader({
techdocs: {
legacyUseCaseSensitiveTripletPaths: true,
},
});
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
const mockEntity = createMockEntity();
const lowerMockEntity = createMockEntity(undefined, true);
await publisher.publish({ entity: mockEntity, directory: tmpDir });
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
// Lower/upper should be treated differently.
expect(await publisher.hasDocsBeenGenerated(lowerMockEntity)).toBe(false);
mockFs.restore();
});
@@ -86,6 +117,13 @@ describe('local publisher', () => {
[resolvedDir]: {
'unsafe.html': '<html></html>',
'unsafe.svg': '<svg></svg>',
default: {
testkind: {
testname: {
'index.html': 'found it',
},
},
},
},
});
});
@@ -107,5 +145,38 @@ describe('local publisher', () => {
'content-type': 'text/plain; charset=utf-8',
});
});
it('should redirect case-sensitive triplet path to lower-case', async () => {
const response = await request(app)
.get('/default/TestKind/TestName/index.html')
.expect('Location', '/default/testkind/testname/index.html');
expect(response.status).toBe(301);
});
it('should resolve lower-case triplet path content eventually', async () => {
const response = await request(app)
.get('/default/TestKind/TestName/index.html')
.redirects(1);
expect(response.text).toEqual('found it');
});
it('should not redirect when legacy case setting is used', async () => {
const legacyConfig = new ConfigReader({
techdocs: {
legacyUseCaseSensitiveTripletPaths: true,
},
});
const legacyPublisher = new LocalPublish(
legacyConfig,
logger,
testDiscovery,
);
app = express().use(legacyPublisher.docsRouter());
const response = await request(app).get(
'/default/TestKind/TestName/index.html',
);
expect(response.status).toBe(404);
});
});
});
@@ -58,6 +58,8 @@ try {
* called "static" at the root of techdocs-backend plugin.
*/
export class LocalPublish implements PublisherBase {
private legacyPathCasing: boolean;
// TODO: Use a static fromConfig method to create a LocalPublish instance, similar to aws/gcs publishers.
// Move the logic of setting staticDocsDir based on config over to fromConfig,
// and set the value as a class parameter.
@@ -70,6 +72,10 @@ export class LocalPublish implements PublisherBase {
this.config = config;
this.logger = logger;
this.discovery = discovery;
this.legacyPathCasing =
config.getOptionalBoolean(
'techdocs.legacyUseCaseSensitiveTripletPaths',
) || false;
}
async getReadiness(): Promise<ReadinessResponse> {
@@ -81,8 +87,7 @@ export class LocalPublish implements PublisherBase {
publish({ entity, directory }: PublishRequest): Promise<PublishResponse> {
const entityNamespace = entity.metadata.namespace ?? 'default';
const publishDir = path.join(
staticDocsDir,
const publishDir = this.staticEntityPathJoin(
entityNamespace,
entity.kind,
entity.metadata.name,
@@ -119,8 +124,7 @@ export class LocalPublish implements PublisherBase {
async fetchTechDocsMetadata(
entityName: EntityName,
): Promise<TechDocsMetadata> {
const metadataPath = path.join(
staticDocsDir,
const metadataPath = this.staticEntityPathJoin(
entityName.namespace,
entityName.kind,
entityName.name,
@@ -138,23 +142,61 @@ export class LocalPublish implements PublisherBase {
}
docsRouter(): express.Handler {
return express.static(staticDocsDir, {
// Handle content-type header the same as all other publishers.
setHeaders: (res, filePath) => {
const fileExtension = path.extname(filePath);
const headers = getHeadersForFileExtension(fileExtension);
for (const [header, value] of Object.entries(headers)) {
res.setHeader(header, value);
}
},
const router = express.Router();
// Redirect middleware ensuring that requests to case-sensitive entity
// triplet paths are always sent to lower-case versions.
router.use((req, res, next) => {
// If legacy path casing is on, let the request immediately continue.
if (this.legacyPathCasing) {
return next();
}
// Generate a lower-case entity triplet path.
const [_, namespace, kind, name, ...rest] = req.path.split('/');
// Ignore non-triplet objects.
if (!namespace || !kind || !name) {
return next();
}
const newPath = [
_,
namespace.toLowerCase(),
kind.toLowerCase(),
name.toLowerCase(),
...rest,
].join('/');
// If there was no change, then let express.static() handle the request.
if (newPath === req.path) {
return next();
}
// Otherwise, redirect to the new path.
return res.redirect(req.baseUrl + newPath, 301);
});
router.use(
express.static(staticDocsDir, {
// Handle content-type header the same as all other publishers.
setHeaders: (res, filePath) => {
const fileExtension = path.extname(filePath);
const headers = getHeadersForFileExtension(fileExtension);
for (const [header, value] of Object.entries(headers)) {
res.setHeader(header, value);
}
},
}),
);
return router;
}
async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {
const namespace = entity.metadata.namespace ?? 'default';
const indexHtmlPath = path.join(
staticDocsDir,
const indexHtmlPath = this.staticEntityPathJoin(
namespace,
entity.kind,
entity.metadata.name,
@@ -210,4 +252,22 @@ export class LocalPublish implements PublisherBase {
),
);
}
/**
* Utility wrapper around path.join(), used to control legacy case logic.
*/
protected staticEntityPathJoin(...allParts: string[]): string {
if (this.legacyPathCasing) {
const [namespace, kind, name, ...parts] = allParts;
return path.join(staticDocsDir, namespace, kind, name, ...parts);
}
const [namespace, kind, name, ...parts] = allParts;
return path.join(
staticDocsDir,
namespace.toLowerCase(),
kind.toLowerCase(),
name.toLowerCase(),
...parts,
);
}
}
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+10
View File
@@ -0,0 +1,10 @@
# @backstage/plugin-allure
## 0.1.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@0.4.5
- @backstage/core-components@0.4.0
- @backstage/catalog-model@0.9.1
+47
View File
@@ -0,0 +1,47 @@
# [Allure](https://docs.qameta.io/allure/)
Welcome to the Backstage Allure plugin. This plugin add an entity service page to display Allure test reports related to the service.
## Install
Run the below command from the `app` package directory.
```shell
yarn add @backstage/plugin-allure
```
alternatively, you can execute below command from the root directory of your Backstage app.
```shell
yarn workspace app add @backstage/plugin-allure
```
## Configure
### Configure Allure service
Add below configuration in the `app-config.yaml`.
```yaml
allure:
baseUrl: <ALLURE_SERVICE_BASE_URL> # Example: https://allure.my-company.net or when running allure locally, http://localhost:5050/allure-docker-service
```
### Setup entity service page
Add `EntityAllureReportContent` in the `EntityPage.tsx` like below:
```diff
+ import { EntityAllureReportContent } from '@backstage/plugin-allure';
...
const serviceEntityPage = (
<EntityLayoutWrapper>
...
+ <EntityLayout.Route path="/allure" title="Allure Report">
+ <EntityAllureReportContent />
+ </EntityLayout.Route>
</EntityLayoutWrapper>
);
```
+27
View File
@@ -0,0 +1,27 @@
## API Report File for "@backstage/plugin-allure"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="react" />
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { RouteRef } from '@backstage/core-plugin-api';
// Warning: (ae-missing-release-tag) "allurePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const allurePlugin: BackstagePlugin<
{
root: RouteRef<undefined>;
},
{}
>;
// Warning: (ae-missing-release-tag) "EntityAllureReportContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const EntityAllureReportContent: () => JSX.Element;
// (No @packageDocumentation comment for this package)
```
+11
View File
@@ -0,0 +1,11 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: allure-example-service
description: Allure Example Service
annotations:
qameta.io/allure-project: sample
spec:
type: service
lifecycle: experimental
owner: team-a
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 React from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { EntityAllureReportContent } from '../src/plugin';
import { Content, Header, Page } from '@backstage/core-components';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
import exampleEntity from './example-entity.yaml';
import { allureApiRef } from '../src/api';
createDevApp()
.registerApi({
api: allureApiRef,
deps: {},
factory: () =>
({
async getReportUrl(projectId: string) {
return Promise.resolve(
// Follow the instructions from https://github.com/fescobar/allure-docker-service-ui
// to setup Allure service locally
`http://localhost:5050/allure-docker-service/projects/${projectId}/reports/latest/index.html`,
);
},
} as unknown as typeof allureApiRef.T),
})
.addPage({
element: (
<Page themeId="home">
<Header title="Allure Report" />
<Content>
<EntityProvider entity={exampleEntity as any as Entity}>
<EntityAllureReportContent />
</EntityProvider>
</Content>
</Page>
),
title: 'Allure Report',
path: '/allure',
})
.render();
+69
View File
@@ -0,0 +1,69 @@
{
"name": "@backstage/plugin-allure",
"version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli plugin:build",
"start": "backstage-cli plugin:serve",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"diff": "backstage-cli plugin:diff",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/catalog-model": "^0.9.1",
"@backstage/core-components": "^0.4.0",
"@backstage/core-plugin-api": "^0.1.6",
"@backstage/plugin-catalog-react": "^0.4.5",
"@backstage/theme": "^0.2.10",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^17.2.4"
},
"devDependencies": {
"@backstage/cli": "^0.7.10",
"@backstage/core-app-api": "^0.1.11",
"@backstage/dev-utils": "^0.2.8",
"@backstage/test-utils": "^0.1.17",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32",
"cross-fetch": "^3.0.6",
"msw": "^0.29.0"
},
"files": [
"dist"
],
"configSchema": {
"$schema": "https://backstage.io/schema/config-v1",
"title": "@backstage/allure",
"type": "object",
"properties": {
"allure": {
"type": "object",
"properties": {
"baseUrl": {
"type": "string",
"visibility": "frontend"
}
}
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { createApiRef } from '@backstage/core-plugin-api';
export type AllureApi = {
getReportUrl(projectId: string): Promise<string>;
};
export const allureApiRef = createApiRef<AllureApi>({
id: 'allure-api',
});
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { ConfigApi } from '@backstage/core-plugin-api';
import { AllureApi } from './AllureApi';
export class AllureApiClient implements AllureApi {
readonly configApi: ConfigApi;
constructor(options: { configApi: ConfigApi }) {
this.configApi = options.configApi;
}
async getReportUrl(projectId: string): Promise<string> {
const baseUrl = this.configApi.getString('allure.baseUrl');
return `${baseUrl}/projects/${projectId}/reports/latest/index.html`;
}
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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.
*/
export { allureApiRef } from './AllureApi';
export { AllureApiClient } from './AllureApiClient';
@@ -0,0 +1,44 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 React from 'react';
import { AllureReportComponent } from './AllureReportComponent';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { msw, renderInTestApp } from '@backstage/test-utils';
describe('ExampleComponent', () => {
const server = setupServer();
// Enable sane handlers for network requests
msw.setupDefaultHandlers(server);
// setup mock response
beforeEach(() => {
server.use(
rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))),
);
});
it('should render', async () => {
const rendered = await renderInTestApp(
<ThemeProvider theme={lightTheme}>
<AllureReportComponent />
</ThemeProvider>,
);
expect(rendered.getByText('Missing Annotation')).toBeInTheDocument();
});
});
@@ -0,0 +1,65 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 React from 'react';
import { useApi } from '@backstage/core-plugin-api';
import { allureApiRef } from '../../api';
import { useEntity } from '@backstage/plugin-catalog-react';
import {
ALLURE_PROJECT_ID_ANNOTATION,
isAllureReportAvailable,
getAllureProjectId,
} from '../annotationHelpers';
import {
MissingAnnotationEmptyState,
Progress,
} from '@backstage/core-components';
import { useAsync } from 'react-use';
import { Entity } from '@backstage/catalog-model';
const AllureReport = (props: { entity: Entity }) => {
const allureApi = useApi(allureApiRef);
const allureProjectId = getAllureProjectId(props.entity);
const { value, loading } = useAsync(async () => {
const url = await allureApi.getReportUrl(allureProjectId);
return url;
});
if (loading) {
return <Progress />;
}
return (
<iframe
style={{
display: 'table',
width: '100%',
height: '100%',
}}
title="Allure Report"
src={value}
/>
);
};
export const AllureReportComponent = () => {
const { entity } = useEntity();
const isReportAvailable = entity && isAllureReportAvailable(entity);
if (isReportAvailable) return <AllureReport entity={entity} />;
return (
<MissingAnnotationEmptyState annotation={ALLURE_PROJECT_ID_ANNOTATION} />
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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.
*/
export { AllureReportComponent } from './AllureReportComponent';
@@ -0,0 +1,25 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { Entity } from '@backstage/catalog-model';
export const ALLURE_PROJECT_ID_ANNOTATION = 'qameta.io/allure-project';
export const isAllureReportAvailable = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[ALLURE_PROJECT_ID_ANNOTATION]);
export const getAllureProjectId = (entity: Entity) => {
return entity?.metadata.annotations?.[ALLURE_PROJECT_ID_ANNOTATION] ?? '';
};
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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.
*/
export { allurePlugin, EntityAllureReportContent } from './plugin';
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { allurePlugin } from './plugin';
describe('allure', () => {
it('should export plugin', () => {
expect(allurePlugin).toBeDefined();
});
});
+53
View File
@@ -0,0 +1,53 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 {
configApiRef,
createApiFactory,
createPlugin,
createRoutableExtension,
createRouteRef,
} from '@backstage/core-plugin-api';
import { AllureApiClient, allureApiRef } from './api';
export const allureRouteRef = createRouteRef({
title: 'allure-report',
});
export const allurePlugin = createPlugin({
id: 'allure',
apis: [
createApiFactory({
api: allureApiRef,
deps: {
configApi: configApiRef,
},
factory: ({ configApi }) => new AllureApiClient({ configApi }),
}),
],
routes: {
root: allureRouteRef,
},
});
export const EntityAllureReportContent = allurePlugin.provide(
createRoutableExtension({
component: () =>
import('./components/AllureReportComponent').then(
m => m.AllureReportComponent,
),
mountPoint: allureRouteRef,
}),
);

Some files were not shown because too many files have changed in this diff Show More