Merge branch 'backstage:master' into tech-docs-search-by-title
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Added a new Root Health Service which adds new endpoints for health checks.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-ldap': patch
|
||||
'@backstage/backend-plugin-api': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/backend-tasks': patch
|
||||
---
|
||||
|
||||
Fix bug where ISO durations could no longer be used for schedules
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
- remove unused dependencies `winston` and `yn` from the template of backend plugins;
|
||||
- update `msw` to version `2.3.1` in the template of backend plugins;
|
||||
starting with v1 and switching later to v2 is tedious and not straight forward; it's easier to start with v2;
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updated the scaffolding output message for `plugin-common` in `backstage-cli`. Now, when executing `backstage-cli new` to create a new `plugin-common` package, the output message accurately reflects the action by displaying `Creating common plugin package...` instead of the previous, less accurate `Creating backend plugin...`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Added `relationType` property to EntityMembersListCard component that allows for display users related to a group via some other relationship aside from `memberOf`.
|
||||
|
||||
Also, as a side effect, the `relationsType` property has been deprecated in favor of a more accurately named `relationAggregation` property.
|
||||
@@ -0,0 +1,190 @@
|
||||
{
|
||||
"mode": "pre",
|
||||
"tag": "next",
|
||||
"initialVersions": {
|
||||
"example-app": "0.2.98",
|
||||
"@backstage/app-defaults": "1.5.6",
|
||||
"example-app-next": "0.0.12",
|
||||
"app-next-example-plugin": "0.0.12",
|
||||
"example-backend": "0.0.27",
|
||||
"@backstage/backend-app-api": "0.7.6",
|
||||
"@backstage/backend-common": "0.23.0",
|
||||
"@backstage/backend-defaults": "0.3.0",
|
||||
"@backstage/backend-dev-utils": "0.1.4",
|
||||
"@backstage/backend-dynamic-feature-service": "0.2.11",
|
||||
"example-backend-legacy": "0.2.99",
|
||||
"@backstage/backend-openapi-utils": "0.1.12",
|
||||
"@backstage/backend-plugin-api": "0.6.19",
|
||||
"@backstage/backend-tasks": "0.5.24",
|
||||
"@backstage/backend-test-utils": "0.4.0",
|
||||
"@backstage/catalog-client": "1.6.5",
|
||||
"@backstage/catalog-model": "1.5.0",
|
||||
"@backstage/cli": "0.26.7",
|
||||
"@backstage/cli-common": "0.1.14",
|
||||
"@backstage/cli-node": "0.2.6",
|
||||
"@backstage/codemods": "0.1.49",
|
||||
"@backstage/config": "1.2.0",
|
||||
"@backstage/config-loader": "1.8.1",
|
||||
"@backstage/core-app-api": "1.12.6",
|
||||
"@backstage/core-compat-api": "0.2.6",
|
||||
"@backstage/core-components": "0.14.8",
|
||||
"@backstage/core-plugin-api": "1.9.3",
|
||||
"@backstage/create-app": "0.5.16",
|
||||
"@backstage/dev-utils": "1.0.33",
|
||||
"e2e-test": "0.2.17",
|
||||
"@backstage/e2e-test-utils": "0.1.1",
|
||||
"@backstage/errors": "1.2.4",
|
||||
"@backstage/eslint-plugin": "0.1.8",
|
||||
"@backstage/frontend-app-api": "0.7.1",
|
||||
"@backstage/frontend-plugin-api": "0.6.6",
|
||||
"@backstage/frontend-test-utils": "0.1.8",
|
||||
"@backstage/integration": "1.12.0",
|
||||
"@backstage/integration-aws-node": "0.1.12",
|
||||
"@backstage/integration-react": "1.1.28",
|
||||
"@backstage/release-manifests": "0.0.11",
|
||||
"@backstage/repo-tools": "0.9.1",
|
||||
"@techdocs/cli": "1.8.12",
|
||||
"techdocs-cli-embedded-app": "0.2.97",
|
||||
"@backstage/test-utils": "1.5.6",
|
||||
"@backstage/theme": "0.5.6",
|
||||
"@backstage/types": "1.1.1",
|
||||
"@backstage/version-bridge": "1.0.8",
|
||||
"yarn-plugin-backstage": "0.0.1",
|
||||
"@backstage/plugin-api-docs": "0.11.6",
|
||||
"@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.7",
|
||||
"@backstage/plugin-app-backend": "0.3.68",
|
||||
"@backstage/plugin-app-node": "0.1.19",
|
||||
"@backstage/plugin-app-visualizer": "0.1.7",
|
||||
"@backstage/plugin-auth-backend": "0.22.6",
|
||||
"@backstage/plugin-auth-backend-module-atlassian-provider": "0.2.0",
|
||||
"@backstage/plugin-auth-backend-module-aws-alb-provider": "0.1.11",
|
||||
"@backstage/plugin-auth-backend-module-azure-easyauth-provider": "0.1.2",
|
||||
"@backstage/plugin-auth-backend-module-bitbucket-provider": "0.1.2",
|
||||
"@backstage/plugin-auth-backend-module-cloudflare-access-provider": "0.1.2",
|
||||
"@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.14",
|
||||
"@backstage/plugin-auth-backend-module-github-provider": "0.1.16",
|
||||
"@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.16",
|
||||
"@backstage/plugin-auth-backend-module-google-provider": "0.1.16",
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "0.1.5",
|
||||
"@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.14",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-provider": "0.2.0",
|
||||
"@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.12",
|
||||
"@backstage/plugin-auth-backend-module-oidc-provider": "0.2.0",
|
||||
"@backstage/plugin-auth-backend-module-okta-provider": "0.0.12",
|
||||
"@backstage/plugin-auth-backend-module-onelogin-provider": "0.1.0",
|
||||
"@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.13",
|
||||
"@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.2.0",
|
||||
"@backstage/plugin-auth-node": "0.4.14",
|
||||
"@backstage/plugin-auth-react": "0.1.3",
|
||||
"@backstage/plugin-bitbucket-cloud-common": "0.2.20",
|
||||
"@backstage/plugin-catalog": "1.21.0",
|
||||
"@backstage/plugin-catalog-backend": "1.23.0",
|
||||
"@backstage/plugin-catalog-backend-module-aws": "0.3.14",
|
||||
"@backstage/plugin-catalog-backend-module-azure": "0.1.39",
|
||||
"@backstage/plugin-catalog-backend-module-backstage-openapi": "0.2.2",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.2.6",
|
||||
"@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.33",
|
||||
"@backstage/plugin-catalog-backend-module-gcp": "0.1.20",
|
||||
"@backstage/plugin-catalog-backend-module-gerrit": "0.1.36",
|
||||
"@backstage/plugin-catalog-backend-module-github": "0.6.2",
|
||||
"@backstage/plugin-catalog-backend-module-github-org": "0.1.14",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab": "0.3.18",
|
||||
"@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.2",
|
||||
"@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.24",
|
||||
"@backstage/plugin-catalog-backend-module-ldap": "0.6.0",
|
||||
"@backstage/plugin-catalog-backend-module-msgraph": "0.5.27",
|
||||
"@backstage/plugin-catalog-backend-module-openapi": "0.1.37",
|
||||
"@backstage/plugin-catalog-backend-module-puppetdb": "0.1.25",
|
||||
"@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.17",
|
||||
"@backstage/plugin-catalog-backend-module-unprocessed": "0.4.6",
|
||||
"@backstage/plugin-catalog-common": "1.0.24",
|
||||
"@backstage/plugin-catalog-graph": "0.4.6",
|
||||
"@backstage/plugin-catalog-import": "0.12.0",
|
||||
"@backstage/plugin-catalog-node": "1.12.1",
|
||||
"@backstage/plugin-catalog-react": "1.12.1",
|
||||
"@backstage/plugin-catalog-unprocessed-entities": "0.2.5",
|
||||
"@backstage/plugin-catalog-unprocessed-entities-common": "0.0.2",
|
||||
"@backstage/plugin-config-schema": "0.1.56",
|
||||
"@backstage/plugin-devtools": "0.1.15",
|
||||
"@backstage/plugin-devtools-backend": "0.3.5",
|
||||
"@backstage/plugin-devtools-common": "0.1.10",
|
||||
"@backstage/plugin-events-backend": "0.3.6",
|
||||
"@backstage/plugin-events-backend-module-aws-sqs": "0.3.5",
|
||||
"@backstage/plugin-events-backend-module-azure": "0.2.5",
|
||||
"@backstage/plugin-events-backend-module-bitbucket-cloud": "0.2.5",
|
||||
"@backstage/plugin-events-backend-module-gerrit": "0.2.5",
|
||||
"@backstage/plugin-events-backend-module-github": "0.2.5",
|
||||
"@backstage/plugin-events-backend-module-gitlab": "0.2.5",
|
||||
"@backstage/plugin-events-backend-test-utils": "0.1.29",
|
||||
"@backstage/plugin-events-node": "0.3.5",
|
||||
"@internal/plugin-todo-list": "1.0.28",
|
||||
"@internal/plugin-todo-list-backend": "1.0.28",
|
||||
"@internal/plugin-todo-list-common": "1.0.19",
|
||||
"@backstage/plugin-home": "0.7.5",
|
||||
"@backstage/plugin-home-react": "0.1.14",
|
||||
"@backstage/plugin-kubernetes": "0.11.11",
|
||||
"@backstage/plugin-kubernetes-backend": "0.18.0",
|
||||
"@backstage/plugin-kubernetes-cluster": "0.0.12",
|
||||
"@backstage/plugin-kubernetes-common": "0.8.0",
|
||||
"@backstage/plugin-kubernetes-node": "0.1.13",
|
||||
"@backstage/plugin-kubernetes-react": "0.4.0",
|
||||
"@backstage/plugin-notifications": "0.2.2",
|
||||
"@backstage/plugin-notifications-backend": "0.3.0",
|
||||
"@backstage/plugin-notifications-backend-module-email": "0.1.0",
|
||||
"@backstage/plugin-notifications-common": "0.0.4",
|
||||
"@backstage/plugin-notifications-node": "0.2.0",
|
||||
"@backstage/plugin-org": "0.6.26",
|
||||
"@backstage/plugin-org-react": "0.1.25",
|
||||
"@backstage/plugin-permission-backend": "0.5.43",
|
||||
"@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.16",
|
||||
"@backstage/plugin-permission-common": "0.7.14",
|
||||
"@backstage/plugin-permission-node": "0.7.30",
|
||||
"@backstage/plugin-permission-react": "0.4.23",
|
||||
"@backstage/plugin-proxy-backend": "0.5.0",
|
||||
"@backstage/plugin-scaffolder": "1.21.0",
|
||||
"@backstage/plugin-scaffolder-backend": "1.22.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-azure": "0.1.11",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket": "0.2.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-cloud": "0.1.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-bitbucket-server": "0.1.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.20",
|
||||
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.43",
|
||||
"@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.11",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitea": "0.1.9",
|
||||
"@backstage/plugin-scaffolder-backend-module-github": "0.3.0",
|
||||
"@backstage/plugin-scaffolder-backend-module-gitlab": "0.4.1",
|
||||
"@backstage/plugin-scaffolder-backend-module-notifications": "0.0.2",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "0.4.36",
|
||||
"@backstage/plugin-scaffolder-backend-module-sentry": "0.1.27",
|
||||
"@backstage/plugin-scaffolder-backend-module-yeoman": "0.3.2",
|
||||
"@backstage/plugin-scaffolder-common": "1.5.3",
|
||||
"@backstage/plugin-scaffolder-node": "0.4.5",
|
||||
"@backstage/plugin-scaffolder-node-test-utils": "0.1.5",
|
||||
"@backstage/plugin-scaffolder-react": "1.9.0",
|
||||
"@backstage/plugin-search": "1.4.12",
|
||||
"@backstage/plugin-search-backend": "1.5.10",
|
||||
"@backstage/plugin-search-backend-module-catalog": "0.1.25",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "1.5.0",
|
||||
"@backstage/plugin-search-backend-module-explore": "0.1.25",
|
||||
"@backstage/plugin-search-backend-module-pg": "0.5.28",
|
||||
"@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.12",
|
||||
"@backstage/plugin-search-backend-module-techdocs": "0.1.24",
|
||||
"@backstage/plugin-search-backend-node": "1.2.24",
|
||||
"@backstage/plugin-search-common": "1.2.12",
|
||||
"@backstage/plugin-search-react": "1.7.12",
|
||||
"@backstage/plugin-signals": "0.0.7",
|
||||
"@backstage/plugin-signals-backend": "0.1.5",
|
||||
"@backstage/plugin-signals-node": "0.1.5",
|
||||
"@backstage/plugin-signals-react": "0.0.4",
|
||||
"@backstage/plugin-techdocs": "1.10.6",
|
||||
"@backstage/plugin-techdocs-addons-test-utils": "1.0.33",
|
||||
"@backstage/plugin-techdocs-backend": "1.10.6",
|
||||
"@backstage/plugin-techdocs-module-addons-contrib": "1.1.11",
|
||||
"@backstage/plugin-techdocs-node": "1.12.5",
|
||||
"@backstage/plugin-techdocs-react": "1.2.5",
|
||||
"@backstage/plugin-user-settings": "0.8.7",
|
||||
"@backstage/plugin-user-settings-backend": "0.2.18",
|
||||
"@backstage/plugin-user-settings-common": "0.0.1"
|
||||
},
|
||||
"changesets": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Added mock for the Root Health Service in `mockServices`.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-logs': patch
|
||||
---
|
||||
|
||||
Creates a new module to make logging catalog errors simple. This module subscribes to catalog events and logs them.
|
||||
|
||||
See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install
|
||||
and configure the plugin.
|
||||
@@ -28,6 +28,7 @@ import { coreServices } from '@backstage/backend-plugin-api';
|
||||
- [Permissions Service](./permissions.md) - Permission system integration for authorization of user actions.
|
||||
- [Plugin Metadata Service](./plugin-metadata.md) - Built-in service for accessing metadata about the current plugin.
|
||||
- [Root Config Service](./root-config.md) - Access to static configuration.
|
||||
- [Root Health Service](./root-health.md) - Health check endpoints for the backend.
|
||||
- [Root Http Router Service](./root-http-router.md) - HTTP route registration for root services.
|
||||
- [Root Lifecycle Service](./root-lifecycle.md) - Registration of backend startup and shutdown lifecycle hooks.
|
||||
- [Root Logger Service](./root-logger.md) - Root-level logging.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: root-health
|
||||
title: Root Health Service
|
||||
sidebar_label: Health
|
||||
description: Documentation for the Health service
|
||||
---
|
||||
|
||||
The Root Health service provides some health check endpoints for the backend. By default, the `rootHttpRouter` exposes a `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness` endpoints, which return a JSON object with the status of the backend services according the implementation of the Root Health Service.
|
||||
|
||||
## Configuring the service
|
||||
|
||||
The following example shows how you can override the root health service implementation.
|
||||
|
||||
```ts
|
||||
import { RootHealthService, coreServices } from '@backstage/backend-plugin-api';
|
||||
|
||||
const backend = createBackend();
|
||||
|
||||
class MyRootHealthService implements RootHealthService {
|
||||
async getLiveness() {
|
||||
// provide your own implementation
|
||||
return { status: 200, payload: { status: 'ok' } };
|
||||
}
|
||||
|
||||
async getReadiness() {
|
||||
// provide your own implementation
|
||||
return { status: 200, payload: { status: 'ok' } };
|
||||
}
|
||||
}
|
||||
|
||||
backend.add(
|
||||
createServiceFactory({
|
||||
service: coreServices.rootHealth,
|
||||
deps: {},
|
||||
async factory({}) {
|
||||
return new MyRootHealthService();
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
@@ -177,3 +177,106 @@ here.
|
||||
Setting this value too low risks exhausting rate limits on external systems that
|
||||
are queried by processors, such as version control systems housing catalog-info
|
||||
files.
|
||||
|
||||
## Subscribing to Catalog Errors
|
||||
|
||||
Catalog errors are published to the [events plugin](https://github.com/backstage/backstage/tree/master/plugins/events-node): `@backstage/plugin-events-node`. You can subscribe to events and respond to errors, for example you may wish to log them.
|
||||
|
||||
The first step is to add the events backend plugin to your Backstage application. Navigate to your Backstage application directory and add the plugin package.
|
||||
|
||||
```ts
|
||||
# From your Backstage root directory
|
||||
yarn --cwd packages/backend add @backstage/plugin-events-node
|
||||
```
|
||||
|
||||
Now you can install the events backend plugin in your backend.
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
backend.add(import('@backstage/plugin-events-backend/alpha'));
|
||||
```
|
||||
|
||||
### Logging Errors
|
||||
|
||||
If you want to log catalog errors you can install the `@backstage/plugin-catalog-backend-module-logs` module.
|
||||
|
||||
Install the catalog logs module.
|
||||
|
||||
```ts
|
||||
# From your Backstage root directory
|
||||
yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-logs
|
||||
```
|
||||
|
||||
Add the module to your backend.
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
backend.add(import('@backstage/plugin-catalog-backend-module-logs'));
|
||||
```
|
||||
|
||||
This will log errors with a level of `warn`.
|
||||
|
||||
You should now see logs as the catalog emits events. Example:
|
||||
|
||||
```
|
||||
[1] 2024-06-07T00:00:28.787Z events warn Policy check failed for user:default/guest; caused by Error: Malformed envelope, /metadata/tags must be array entity=user:default/guest location=file:/Users/foobar/code/backstage-demo-instance/examples/org.yaml
|
||||
```
|
||||
|
||||
### Custom Error Handling
|
||||
|
||||
If you wish to handle catalog errors with specific logic different from logging the errors the following should help you get started. For example, you may wish to send a notification or create a ticket for someone to investigate.
|
||||
|
||||
Create a backend module that subscribes to the catalog error events. The topic is `experimental.catalog.errors`.
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
import { CATALOG_ERRORS_TOPIC } from '@backstage/plugin-catalog-backend';
|
||||
import {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { eventsServiceRef, EventParams } from '@backstage/plugin-events-node';
|
||||
|
||||
interface EventsPayload {
|
||||
entity: string;
|
||||
location?: string;
|
||||
errors: Error[];
|
||||
}
|
||||
|
||||
interface EventsParamsWithPayload extends EventParams {
|
||||
eventPayload: EventsPayload;
|
||||
}
|
||||
|
||||
const eventsModuleCatalogErrors = createBackendModule({
|
||||
pluginId: 'events',
|
||||
moduleId: 'catalog-errors',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsServiceRef,
|
||||
logger: coreServices.logger,
|
||||
},
|
||||
async init({ events, logger }) {
|
||||
events.subscribe({
|
||||
id: 'catalog',
|
||||
topics: [CATALOG_ERRORS_TOPIC],
|
||||
async onEvent(params: EventParams): Promise<void> {
|
||||
const event = params as EventsParamsWithPayload;
|
||||
const { entity, location, errors } = event.eventPayload;
|
||||
// Add custom logic here for responding to errors
|
||||
for (const error of errors) {
|
||||
logger.warn(error.message, {
|
||||
entity,
|
||||
location,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now install your module.
|
||||
|
||||
```ts title="packages/backend/src/index.ts"
|
||||
backend.add(eventsModuleCatalogErrors);
|
||||
```
|
||||
|
||||
@@ -197,13 +197,15 @@ cannot be parsed successfully, etc.
|
||||
|
||||
There are two main ways that these errors are surfaced.
|
||||
|
||||
First, the catalog backend will produce detailed logs that should contain
|
||||
sufficient information for a reader to find the causes for errors. Since these
|
||||
logs are typically not easily found by end users, this can mainly be a useful
|
||||
First, the catalog backend will emit events using the [events backend plugin](https://github.com/backstage/backstage/tree/master/plugins/events-node). You can subscribe to the events. The events should contain
|
||||
sufficient information for a reader to find the causes for errors. See the [configuration documentation](./configuration.md#subscribing-to-catalog-errors) for how to subscribe and log these error events.
|
||||
Since these events are typically not easily found by end users, this can mainly be a useful
|
||||
tool for Backstage operators who want to debug problems either with statically
|
||||
registered entities that are under their control, or to help end users find
|
||||
problems.
|
||||
|
||||
> Prior to Backstage version v1.26.0 and `@backstage/plugin-catalog-backend` v1.21.9 catalog errors were logged by default.
|
||||
|
||||
Second, for most classes of errors, the entity itself will contain a status
|
||||
field that describes the problem. The contents of this field is shown at the top
|
||||
of your entity page in Backstage, if you have placed the corresponding error
|
||||
|
||||
@@ -256,3 +256,51 @@ spec:
|
||||
input:
|
||||
url: ${{ '/root' if parameters.path !== true else parameters.path }}
|
||||
```
|
||||
|
||||
## Use placeholders to reference remote files
|
||||
|
||||
#### Note: testing of this functionality is not yet supported using _create/edit_
|
||||
|
||||
### template.yaml
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
parameters:
|
||||
- $yaml: https://github.com/example/path/to/example.yaml
|
||||
- title: Fill in some steps
|
||||
properties:
|
||||
path:
|
||||
title: path
|
||||
type: string
|
||||
|
||||
steps:
|
||||
- $yaml: https://github.com//example/path/to/action.yaml
|
||||
|
||||
- id: fetch
|
||||
name: Fetch template
|
||||
action: fetch:template
|
||||
input:
|
||||
url: ${{ parameters.path if parameters.path else '/root' }}
|
||||
```
|
||||
|
||||
### example.yaml
|
||||
|
||||
```yaml
|
||||
title: Provide simple information
|
||||
required:
|
||||
- url
|
||||
properties:
|
||||
url:
|
||||
title: url
|
||||
type: string
|
||||
```
|
||||
|
||||
### action.yaml
|
||||
|
||||
```yaml
|
||||
id: publish
|
||||
name: Publish files
|
||||
action: publish:github
|
||||
input:
|
||||
repoUrl: ${{ parameters.url }}
|
||||
```
|
||||
|
||||
@@ -102,7 +102,7 @@ See [TechDocs Architecture](architecture.md) to get an overview of where the bel
|
||||
|
||||
## Get involved
|
||||
|
||||
Reach out to us in the **#docs-like-code** channel of our
|
||||
Reach out to us in the **#techdocs** channel of our
|
||||
[Discord chatroom](https://github.com/backstage/backstage#community).
|
||||
|
||||
## Done
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## API Report File for "@backstage/backend-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { RootHealthService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export const rootHealthServiceFactory: () => ServiceFactory<
|
||||
RootHealthService,
|
||||
'root'
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -121,6 +121,8 @@ export interface RootHttpRouterConfigureContext {
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
healthRouter: RequestHandler;
|
||||
// (undocumented)
|
||||
lifecycle: LifecycleService;
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"./logger": "./src/entrypoints/logger/index.ts",
|
||||
"./permissions": "./src/entrypoints/permissions/index.ts",
|
||||
"./rootConfig": "./src/entrypoints/rootConfig/index.ts",
|
||||
"./rootHealth": "./src/entrypoints/rootHealth/index.ts",
|
||||
"./rootHttpRouter": "./src/entrypoints/rootHttpRouter/index.ts",
|
||||
"./rootLifecycle": "./src/entrypoints/rootLifecycle/index.ts",
|
||||
"./rootLogger": "./src/entrypoints/rootLogger/index.ts",
|
||||
@@ -72,6 +73,9 @@
|
||||
"rootConfig": [
|
||||
"src/entrypoints/rootConfig/index.ts"
|
||||
],
|
||||
"rootHealth": [
|
||||
"src/entrypoints/rootHealth/index.ts"
|
||||
],
|
||||
"rootHttpRouter": [
|
||||
"src/entrypoints/rootHttpRouter/index.ts"
|
||||
],
|
||||
|
||||
@@ -30,6 +30,7 @@ import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
|
||||
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
|
||||
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
|
||||
import { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig';
|
||||
import { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';
|
||||
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { rootLoggerServiceFactory } from '@backstage/backend-defaults/rootLogger';
|
||||
@@ -50,6 +51,7 @@ export const defaultServiceFactories = [
|
||||
lifecycleServiceFactory(),
|
||||
loggerServiceFactory(),
|
||||
permissionsServiceFactory(),
|
||||
rootHealthServiceFactory(),
|
||||
rootHttpRouterServiceFactory(),
|
||||
rootLifecycleServiceFactory(),
|
||||
rootLoggerServiceFactory(),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2024 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 { rootHealthServiceFactory } from './rootHealthServiceFactory';
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2024 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 { mockServices } from '@backstage/backend-test-utils';
|
||||
import { DefaultRootHealthService } from './rootHealthServiceFactory';
|
||||
|
||||
describe('DefaultRootHealthService', () => {
|
||||
describe('readiness', () => {
|
||||
it(`should return a 500 response if the server hasn't started yet`, async () => {
|
||||
const service = new DefaultRootHealthService({
|
||||
lifecycle: mockServices.rootLifecycle.mock(),
|
||||
});
|
||||
await expect(service.getReadiness()).resolves.toEqual({
|
||||
status: 503,
|
||||
payload: {
|
||||
message: 'Backend has not started yet',
|
||||
status: 'error',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 200 if the server has started', async () => {
|
||||
let mockServerStartedFn = () => {};
|
||||
|
||||
const lifecycle = mockServices.rootLifecycle.mock({
|
||||
addStartupHook: jest.fn(fn => (mockServerStartedFn = fn)),
|
||||
});
|
||||
|
||||
const service = new DefaultRootHealthService({
|
||||
lifecycle,
|
||||
});
|
||||
|
||||
mockServerStartedFn();
|
||||
|
||||
await expect(service.getReadiness()).resolves.toEqual({
|
||||
status: 200,
|
||||
payload: { status: 'ok' },
|
||||
});
|
||||
});
|
||||
|
||||
it(`should return a 500 response if the server has stopped`, async () => {
|
||||
let mockServerStartedFn = () => {};
|
||||
let mockServerStoppedFn = () => {};
|
||||
|
||||
const lifecycle = mockServices.rootLifecycle.mock({
|
||||
addStartupHook: jest.fn(fn => (mockServerStartedFn = fn)),
|
||||
addShutdownHook: jest.fn(fn => (mockServerStoppedFn = fn)),
|
||||
});
|
||||
|
||||
const service = new DefaultRootHealthService({
|
||||
lifecycle,
|
||||
});
|
||||
|
||||
mockServerStartedFn();
|
||||
mockServerStoppedFn();
|
||||
await expect(service.getReadiness()).resolves.toEqual({
|
||||
status: 503,
|
||||
payload: {
|
||||
message: 'Backend has not started yet',
|
||||
status: 'error',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('liveness', () => {
|
||||
it('should return 200 if the server has started', async () => {
|
||||
const service = new DefaultRootHealthService({
|
||||
lifecycle: mockServices.rootLifecycle.mock(),
|
||||
});
|
||||
|
||||
await expect(service.getLiveness()).resolves.toEqual({
|
||||
status: 200,
|
||||
payload: { status: 'ok' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
RootHealthService,
|
||||
RootLifecycleService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/** @internal */
|
||||
export class DefaultRootHealthService implements RootHealthService {
|
||||
#isRunning = false;
|
||||
|
||||
constructor(readonly options: { lifecycle: RootLifecycleService }) {
|
||||
options.lifecycle.addStartupHook(() => {
|
||||
this.#isRunning = true;
|
||||
});
|
||||
options.lifecycle.addShutdownHook(() => {
|
||||
this.#isRunning = false;
|
||||
});
|
||||
}
|
||||
|
||||
async getLiveness(): Promise<{ status: number; payload?: any }> {
|
||||
return { status: 200, payload: { status: 'ok' } };
|
||||
}
|
||||
|
||||
async getReadiness(): Promise<{ status: number; payload?: any }> {
|
||||
if (!this.#isRunning) {
|
||||
return {
|
||||
status: 503,
|
||||
payload: { message: 'Backend has not started yet', status: 'error' },
|
||||
};
|
||||
}
|
||||
|
||||
return { status: 200, payload: { status: 'ok' } };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const rootHealthServiceFactory = createServiceFactory({
|
||||
service: coreServices.rootHealth,
|
||||
deps: {
|
||||
lifecycle: coreServices.rootLifecycle,
|
||||
},
|
||||
async factory({ lifecycle }) {
|
||||
return new DefaultRootHealthService({ lifecycle });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { RootHealthService } from '@backstage/backend-plugin-api';
|
||||
|
||||
/*
|
||||
* Copyright 2024 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 Router from 'express-promise-router';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
export function createHealthRouter(options: { health: RootHealthService }) {
|
||||
const router = Router();
|
||||
|
||||
router.get(
|
||||
'.backstage/health/v1/readiness',
|
||||
async (_request: Request, response: Response) => {
|
||||
const { status, payload } = await options.health.getReadiness();
|
||||
response.status(status).json(payload);
|
||||
},
|
||||
);
|
||||
|
||||
router.get(
|
||||
'.backstage/health/v1/liveness',
|
||||
async (_request: Request, response: Response) => {
|
||||
const { status, payload } = await options.health.getLiveness();
|
||||
response.status(status).json(payload);
|
||||
},
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
+8
-1
@@ -29,6 +29,7 @@ import {
|
||||
readHttpServerOptions,
|
||||
} from './http';
|
||||
import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
|
||||
import { createHealthRouter } from './createHealthRouter';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -41,6 +42,7 @@ export interface RootHttpRouterConfigureContext {
|
||||
config: RootConfigService;
|
||||
logger: LoggerService;
|
||||
lifecycle: LifecycleService;
|
||||
healthRouter: RequestHandler;
|
||||
applyDefaults: () => void;
|
||||
}
|
||||
|
||||
@@ -75,8 +77,9 @@ export const rootHttpRouterServiceFactory = createServiceFactory(
|
||||
config: coreServices.rootConfig,
|
||||
rootLogger: coreServices.rootLogger,
|
||||
lifecycle: coreServices.rootLifecycle,
|
||||
health: coreServices.rootHealth,
|
||||
},
|
||||
async factory({ config, rootLogger, lifecycle }) {
|
||||
async factory({ config, rootLogger, lifecycle, health }) {
|
||||
const { indexPath, configure = defaultConfigure } = options ?? {};
|
||||
const logger = rootLogger.child({ service: 'rootHttpRouter' });
|
||||
const app = express();
|
||||
@@ -84,6 +87,8 @@ export const rootHttpRouterServiceFactory = createServiceFactory(
|
||||
const router = DefaultRootHttpRouter.create({ indexPath });
|
||||
const middleware = MiddlewareFactory.create({ config, logger });
|
||||
const routes = router.handler();
|
||||
|
||||
const healthRouter = createHealthRouter({ health });
|
||||
const server = await createHttpServer(
|
||||
app,
|
||||
readHttpServerOptions(config.getOptionalConfig('backend')),
|
||||
@@ -98,11 +103,13 @@ export const rootHttpRouterServiceFactory = createServiceFactory(
|
||||
config,
|
||||
logger,
|
||||
lifecycle,
|
||||
healthRouter,
|
||||
applyDefaults() {
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
app.use(middleware.logging());
|
||||
app.use(healthRouter);
|
||||
app.use(routes);
|
||||
app.use(middleware.notFound());
|
||||
app.use(middleware.error());
|
||||
|
||||
@@ -152,7 +152,7 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
|
||||
export function parseDuration(
|
||||
frequency: SchedulerServiceTaskScheduleDefinition['frequency'],
|
||||
): string {
|
||||
if ('cron' in frequency) {
|
||||
if (typeof frequency === 'object' && 'cron' in frequency) {
|
||||
return frequency.cron;
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ export namespace coreServices {
|
||||
const rootConfig: ServiceRef<RootConfigService, 'root'>;
|
||||
const database: ServiceRef<DatabaseService, 'plugin'>;
|
||||
const discovery: ServiceRef<DiscoveryService, 'plugin'>;
|
||||
const rootHealth: ServiceRef<RootHealthService, 'root'>;
|
||||
const httpAuth: ServiceRef<HttpAuthService, 'plugin'>;
|
||||
const httpRouter: ServiceRef<HttpRouterService, 'plugin'>;
|
||||
const lifecycle: ServiceRef<LifecycleService, 'plugin'>;
|
||||
@@ -500,6 +501,18 @@ export function resolveSafeChildPath(base: string, path: string): string;
|
||||
// @public
|
||||
export interface RootConfigService extends Config {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootHealthService {
|
||||
getLiveness(): Promise<{
|
||||
status: number;
|
||||
payload?: JsonValue;
|
||||
}>;
|
||||
getReadiness(): Promise<{
|
||||
status: number;
|
||||
payload?: JsonValue;
|
||||
}>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface RootHttpRouterService {
|
||||
use(path: string, handler: Handler): void;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2024 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 { JsonValue } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface RootHealthService {
|
||||
/**
|
||||
* Get the liveness status of the backend.
|
||||
*/
|
||||
getLiveness(): Promise<{ status: number; payload?: JsonValue }>;
|
||||
/**
|
||||
* Get the readiness status of the backend.
|
||||
*/
|
||||
getReadiness(): Promise<{ status: number; payload?: JsonValue }>;
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { Duration } from 'luxon';
|
||||
import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from './SchedulerService';
|
||||
|
||||
describe('readSchedulerServiceTaskScheduleDefinitionFromConfig', () => {
|
||||
@@ -35,7 +34,7 @@ describe('readSchedulerServiceTaskScheduleDefinitionFromConfig', () => {
|
||||
const result = readSchedulerServiceTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(result.timeout).toEqual({ minutes: 3 });
|
||||
expect((result.initialDelay as HumanDuration).minutes).toEqual(20);
|
||||
expect(result.scope).toBe('global');
|
||||
});
|
||||
@@ -51,7 +50,7 @@ describe('readSchedulerServiceTaskScheduleDefinitionFromConfig', () => {
|
||||
const result = readSchedulerServiceTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(result.timeout).toEqual({ minutes: 3 });
|
||||
expect(result.initialDelay).toBeUndefined();
|
||||
expect(result.scope).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -348,14 +348,14 @@ export interface SchedulerService {
|
||||
getScheduledTasks(): Promise<SchedulerServiceTaskDescriptor[]>;
|
||||
}
|
||||
|
||||
function readDuration(config: Config, key: string): Duration | HumanDuration {
|
||||
function readDuration(config: Config, key: string): HumanDuration {
|
||||
if (typeof config.get(key) === 'string') {
|
||||
const value = config.getString(key);
|
||||
const duration = Duration.fromISO(value);
|
||||
if (!duration.isValid) {
|
||||
throw new Error(`Invalid duration: ${value}`);
|
||||
}
|
||||
return duration;
|
||||
return duration.toObject();
|
||||
}
|
||||
|
||||
return readDurationFromConfig(config, { key });
|
||||
@@ -364,7 +364,7 @@ function readDuration(config: Config, key: string): Duration | HumanDuration {
|
||||
function readCronOrDuration(
|
||||
config: Config,
|
||||
key: string,
|
||||
): { cron: string } | Duration | HumanDuration {
|
||||
): { cron: string } | HumanDuration {
|
||||
const value = config.get(key);
|
||||
if (typeof value === 'object' && (value as { cron?: string }).cron) {
|
||||
return value as { cron: string };
|
||||
|
||||
@@ -102,6 +102,13 @@ export namespace coreServices {
|
||||
import('./DiscoveryService').DiscoveryService
|
||||
>({ id: 'core.discovery' });
|
||||
|
||||
/**
|
||||
* The service reference for the plugin scoped {@link RootHealthService}.
|
||||
*/
|
||||
export const rootHealth = createServiceRef<
|
||||
import('./RootHealthService').RootHealthService
|
||||
>({ id: 'core.rootHealth', scope: 'root' });
|
||||
|
||||
/**
|
||||
* Authentication of HTTP requests.
|
||||
*
|
||||
|
||||
@@ -32,6 +32,7 @@ export type {
|
||||
export type { RootConfigService } from './RootConfigService';
|
||||
export type { DatabaseService } from './DatabaseService';
|
||||
export type { DiscoveryService } from './DiscoveryService';
|
||||
export type { RootHealthService } from './RootHealthService';
|
||||
export type {
|
||||
HttpRouterService,
|
||||
HttpRouterServiceAuthPolicy,
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { Duration } from 'luxon';
|
||||
import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig';
|
||||
|
||||
describe('readTaskScheduleDefinitionFromConfig', () => {
|
||||
@@ -35,7 +34,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => {
|
||||
const result = readTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(result.timeout).toEqual({ minutes: 3 });
|
||||
expect((result.initialDelay as HumanDuration).minutes).toEqual(20);
|
||||
expect(result.scope).toBe('global');
|
||||
});
|
||||
@@ -51,7 +50,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => {
|
||||
const result = readTaskScheduleDefinitionFromConfig(config);
|
||||
|
||||
expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *');
|
||||
expect(result.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(result.timeout).toEqual({ minutes: 3 });
|
||||
expect(result.initialDelay).toBeUndefined();
|
||||
expect(result.scope).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -19,14 +19,14 @@ import { HumanDuration } from '@backstage/types';
|
||||
import { TaskScheduleDefinition } from './types';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
function readDuration(config: Config, key: string): Duration | HumanDuration {
|
||||
function readDuration(config: Config, key: string): HumanDuration {
|
||||
if (typeof config.get(key) === 'string') {
|
||||
const value = config.getString(key);
|
||||
const duration = Duration.fromISO(value);
|
||||
if (!duration.isValid) {
|
||||
throw new Error(`Invalid duration: ${value}`);
|
||||
}
|
||||
return duration;
|
||||
return duration.toObject();
|
||||
}
|
||||
|
||||
return readDurationFromConfig(config, { key });
|
||||
|
||||
@@ -32,6 +32,7 @@ import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { RootHealthService } from '@backstage/backend-plugin-api';
|
||||
import { RootHttpRouterFactoryOptions } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
@@ -280,6 +281,15 @@ export namespace mockServices {
|
||||
) => ServiceFactory<RootConfigService, 'root'>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootHealth {
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<RootHealthService, 'root'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<RootHealthService> | undefined,
|
||||
) => ServiceMock<RootHealthService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootHttpRouter {
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
|
||||
@@ -24,6 +24,7 @@ import { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter
|
||||
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
|
||||
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
|
||||
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
|
||||
import { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';
|
||||
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';
|
||||
@@ -344,6 +345,14 @@ export namespace mockServices {
|
||||
}));
|
||||
}
|
||||
|
||||
export namespace rootHealth {
|
||||
export const factory = rootHealthServiceFactory;
|
||||
export const mock = simpleMock(coreServices.rootHealth, () => ({
|
||||
getLiveness: jest.fn(),
|
||||
getReadiness: jest.fn(),
|
||||
}));
|
||||
}
|
||||
|
||||
export namespace httpRouter {
|
||||
export const factory = httpRouterServiceFactory;
|
||||
export const mock = simpleMock(coreServices.httpRouter, () => ({
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('pluginCommon factory', () => {
|
||||
expect(modified).toBe(true);
|
||||
|
||||
expectLogsToMatch(output, [
|
||||
'Creating backend plugin backstage-plugin-test-common',
|
||||
'Creating common plugin package backstage-plugin-test-common',
|
||||
'Checking Prerequisites:',
|
||||
`availability plugins${sep}test-common`,
|
||||
'creating temp dir',
|
||||
|
||||
@@ -46,7 +46,7 @@ export const pluginCommon = createFactory<Options>({
|
||||
});
|
||||
|
||||
Task.log();
|
||||
Task.log(`Creating backend plugin ${chalk.cyan(name)}`);
|
||||
Task.log(`Creating common plugin package ${chalk.cyan(name)}`);
|
||||
|
||||
const targetDir = ctx.isMonoRepo
|
||||
? paths.resolveTargetRoot('plugins', suffix)
|
||||
|
||||
@@ -35,9 +35,7 @@
|
||||
"@types/express": "{{versionQuery '@types/express' '4.17.6'}}",
|
||||
"express": "{{versionQuery 'express' '4.17.1'}}",
|
||||
"express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}",
|
||||
"winston": "{{versionQuery 'winston' '3.2.1'}}",
|
||||
"node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}",
|
||||
"yn": "{{versionQuery 'yn' '4.0.0'}}"
|
||||
"node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "{{versionQuery '@backstage/cli'}}",
|
||||
@@ -45,7 +43,7 @@
|
||||
"@backstage/plugin-auth-backend-module-guest-provider": "{{versionQuery '@backstage/plugin-auth-backend-module-guest-provider'}}",
|
||||
"@types/supertest": "{{versionQuery '@types/supertest' '2.0.12'}}",
|
||||
"supertest": "{{versionQuery 'supertest' '6.2.4'}}",
|
||||
"msw": "{{versionQuery 'msw' '1.0.0'}}"
|
||||
"msw": "{{versionQuery 'msw' '2.3.1'}}"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleAwsS3EntityProvider } from './catalogModuleAwsS3EntityProvider';
|
||||
import { AwsS3EntityProvider } from '../providers';
|
||||
|
||||
@@ -62,8 +61,8 @@ describe('catalogModuleAwsS3EntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'awsS3-provider:default',
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readAwsS3Configs } from './config';
|
||||
|
||||
describe('readAwsS3Configs', () => {
|
||||
@@ -92,7 +91,7 @@ describe('readAwsS3Configs', () => {
|
||||
id: 'provider4',
|
||||
schedule: {
|
||||
...provider4.schedule,
|
||||
frequency: Duration.fromISO(provider4.schedule.frequency),
|
||||
frequency: { minutes: 30 },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleAzureDevOpsEntityProvider } from './catalogModuleAzureDevOpsEntityProvider';
|
||||
import { AzureDevOpsEntityProvider } from '../providers';
|
||||
|
||||
@@ -66,8 +65,8 @@ describe('catalogModuleAzureDevOpsEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'AzureDevOpsEntityProvider:test',
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readAzureDevOpsConfigs } from './config';
|
||||
|
||||
describe('readAzureDevOpsConfigs', () => {
|
||||
@@ -95,7 +94,7 @@ describe('readAzureDevOpsConfigs', () => {
|
||||
id: 'provider4',
|
||||
schedule: {
|
||||
...provider4.schedule,
|
||||
frequency: Duration.fromISO(provider4.schedule.frequency),
|
||||
frequency: { minutes: 30 },
|
||||
},
|
||||
});
|
||||
expect(actual[4]).toEqual({
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { eventsServiceRef } from '@backstage/plugin-events-node';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleBitbucketCloudEntityProvider } from './catalogModuleBitbucketCloudEntityProvider';
|
||||
import { BitbucketCloudEntityProvider } from '../providers/BitbucketCloudEntityProvider';
|
||||
|
||||
@@ -78,8 +77,8 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
const provider = addedProviders!.pop()!;
|
||||
|
||||
+1
-2
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readProviderConfigs } from './BitbucketCloudEntityProviderConfig';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
@@ -130,7 +129,7 @@ describe('readProviderConfigs', () => {
|
||||
repoSlug: undefined,
|
||||
},
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
|
||||
+2
-3
@@ -18,7 +18,6 @@ import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { catalogModuleBitbucketServerEntityProvider } from './catalogModuleBitbucketServerEntityProvider';
|
||||
import { Duration } from 'luxon';
|
||||
import { BitbucketServerEntityProvider } from '../providers';
|
||||
|
||||
describe('catalogModuleBitbucketServerEntityProvider', () => {
|
||||
@@ -70,8 +69,8 @@ describe('catalogModuleBitbucketServerEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'bitbucketServer-provider:default',
|
||||
|
||||
+1
-2
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readProviderConfigs } from './BitbucketServerEntityProviderConfig';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
@@ -111,7 +110,7 @@ describe('readProviderConfigs', () => {
|
||||
skipArchivedRepos: undefined,
|
||||
},
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleGerritEntityProvider } from './catalogModuleGerritEntityProvider';
|
||||
import { GerritEntityProvider } from '../providers/GerritEntityProvider';
|
||||
|
||||
@@ -76,8 +75,8 @@ describe('catalogModuleGerritEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'gerrit-provider:test',
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readGerritConfigs } from './config';
|
||||
|
||||
describe('readGerritConfigs', () => {
|
||||
@@ -63,7 +62,7 @@ describe('readGerritConfigs', () => {
|
||||
id: 'active-g3',
|
||||
schedule: {
|
||||
...provider3.schedule,
|
||||
frequency: Duration.fromISO(provider3.schedule.frequency),
|
||||
frequency: { minutes: 30 },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@ import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-node';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleGithubOrgEntityProvider } from './module';
|
||||
|
||||
describe('catalogModuleGithubOrgEntityProvider', () => {
|
||||
@@ -66,8 +65,8 @@ describe('catalogModuleGithubOrgEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders![0].getProviderName()).toEqual(
|
||||
'GithubMultiOrgEntityProvider:default',
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
catalogAnalysisExtensionPoint,
|
||||
catalogProcessingExtensionPoint,
|
||||
} from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { githubCatalogModule } from './githubCatalogModule';
|
||||
import { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer';
|
||||
|
||||
@@ -75,8 +74,8 @@ describe('githubCatalogModule', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'github-provider:default',
|
||||
|
||||
+1
-2
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readProviderConfigs } from './GithubEntityProviderConfig';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
@@ -270,7 +269,7 @@ describe('readProviderConfigs', () => {
|
||||
visibility: undefined,
|
||||
},
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
|
||||
+2
-3
@@ -22,7 +22,6 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { eventsServiceRef } from '@backstage/plugin-events-node';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleGitlabOrgDiscoveryEntityProvider } from './catalogModuleGitlabOrgDiscoveryEntityProvider';
|
||||
|
||||
describe('catalogModuleGitlabOrgDiscoveryEntityProvider', () => {
|
||||
@@ -90,8 +89,8 @@ describe('catalogModuleGitlabOrgDiscoveryEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { eventsServiceRef } from '@backstage/plugin-events-node';
|
||||
import { Duration } from 'luxon';
|
||||
import { GitlabDiscoveryEntityProvider } from '../providers';
|
||||
import { catalogModuleGitlabDiscoveryEntityProvider } from './catalogModuleGitlabDiscoveryEntityProvider';
|
||||
|
||||
@@ -89,8 +88,8 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ months: 1 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readGitlabConfigs } from './config';
|
||||
|
||||
describe('config', () => {
|
||||
@@ -179,7 +178,7 @@ describe('config', () => {
|
||||
allowInherited: false,
|
||||
skipForkedRepos: false,
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
|
||||
@@ -16,11 +16,11 @@ import { GroupTransformer as GroupTransformer_2 } from '@backstage/plugin-catalo
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api';
|
||||
import { SearchEntry } from 'ldapjs';
|
||||
import { SearchOptions } from 'ldapjs';
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
import { UserTransformer as UserTransformer_2 } from '@backstage/plugin-catalog-backend-module-ldap';
|
||||
|
||||
@@ -135,7 +135,7 @@ export interface LdapOrgEntityProviderLegacyOptions {
|
||||
groupTransformer?: GroupTransformer;
|
||||
id: string;
|
||||
logger: LoggerService;
|
||||
schedule: 'manual' | TaskRunner;
|
||||
schedule: 'manual' | SchedulerServiceTaskRunner;
|
||||
target: string;
|
||||
userTransformer?: UserTransformer;
|
||||
}
|
||||
@@ -145,8 +145,8 @@ export type LdapOrgEntityProviderOptions =
|
||||
| LdapOrgEntityProviderLegacyOptions
|
||||
| {
|
||||
logger: LoggerService;
|
||||
schedule?: 'manual' | TaskRunner;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
schedule?: 'manual' | SchedulerServiceTaskRunner;
|
||||
scheduler?: SchedulerService;
|
||||
userTransformer?: UserTransformer | Record<string, UserTransformer>;
|
||||
groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;
|
||||
};
|
||||
@@ -199,7 +199,7 @@ export type LdapProviderConfig = {
|
||||
bind?: BindConfig;
|
||||
users: UserConfig;
|
||||
groups: GroupConfig;
|
||||
schedule?: TaskScheduleDefinition;
|
||||
schedule?: SchedulerServiceTaskScheduleDefinition;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -79,6 +79,75 @@ describe('readLdapConfig', () => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('reads schedules well', () => {
|
||||
const config = {
|
||||
catalog: {
|
||||
providers: {
|
||||
ldapOrg: {
|
||||
default: {
|
||||
schedule: {
|
||||
frequency: 'PT3M', // should work for ISO durations
|
||||
timeout: { minutes: 1 },
|
||||
},
|
||||
target: 'target',
|
||||
users: {
|
||||
dn: 'udn',
|
||||
},
|
||||
groups: {
|
||||
dn: 'gdn',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const actual = readProviderConfigs(new ConfigReader(config));
|
||||
const expected = [
|
||||
{
|
||||
id: 'default',
|
||||
target: 'target',
|
||||
bind: undefined,
|
||||
schedule: {
|
||||
frequency: { minutes: 3 },
|
||||
timeout: { minutes: 1 },
|
||||
},
|
||||
users: {
|
||||
dn: 'udn',
|
||||
options: {
|
||||
scope: 'one',
|
||||
attributes: ['*', '+'],
|
||||
},
|
||||
set: undefined,
|
||||
map: {
|
||||
rdn: 'uid',
|
||||
name: 'uid',
|
||||
displayName: 'cn',
|
||||
email: 'mail',
|
||||
memberOf: 'memberOf',
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
dn: 'gdn',
|
||||
options: {
|
||||
scope: 'one',
|
||||
attributes: ['*', '+'],
|
||||
},
|
||||
set: undefined,
|
||||
map: {
|
||||
rdn: 'cn',
|
||||
name: 'cn',
|
||||
description: 'description',
|
||||
type: 'groupType',
|
||||
displayName: 'cn',
|
||||
memberOf: 'memberOf',
|
||||
members: 'member',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('reads all the values', () => {
|
||||
const config = {
|
||||
catalog: {
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
readTaskScheduleDefinitionFromConfig,
|
||||
TaskScheduleDefinition,
|
||||
} from '@backstage/backend-tasks';
|
||||
SchedulerServiceTaskScheduleDefinition,
|
||||
readSchedulerServiceTaskScheduleDefinitionFromConfig,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { SearchOptions } from 'ldapjs';
|
||||
@@ -46,7 +46,7 @@ export type LdapProviderConfig = {
|
||||
// The settings that govern the reading and interpretation of groups
|
||||
groups: GroupConfig;
|
||||
// Schedule configuration for refresh tasks.
|
||||
schedule?: TaskScheduleDefinition;
|
||||
schedule?: SchedulerServiceTaskScheduleDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -380,7 +380,9 @@ export function readProviderConfigs(config: Config): LdapProviderConfig[] {
|
||||
const c = providersConfig.getConfig(id);
|
||||
|
||||
const schedule = c.has('schedule')
|
||||
? readTaskScheduleDefinitionFromConfig(c.getConfig('schedule'))
|
||||
? readSchedulerServiceTaskScheduleDefinitionFromConfig(
|
||||
c.getConfig('schedule'),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const newConfig = {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
@@ -35,7 +34,11 @@ import {
|
||||
readLdapOrg,
|
||||
UserTransformer,
|
||||
} from '../ldap';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
LoggerService,
|
||||
SchedulerService,
|
||||
SchedulerServiceTaskRunner,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { readLdapLegacyConfig, readProviderConfigs } from '../ldap';
|
||||
|
||||
/**
|
||||
@@ -60,16 +63,16 @@ export type LdapOrgEntityProviderOptions =
|
||||
* manually at some interval.
|
||||
*
|
||||
* But more commonly you will pass in the result of
|
||||
* {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}
|
||||
* {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}
|
||||
* to enable automatic scheduling of tasks.
|
||||
*/
|
||||
schedule?: 'manual' | TaskRunner;
|
||||
schedule?: 'manual' | SchedulerServiceTaskRunner;
|
||||
|
||||
/**
|
||||
* Scheduler used to schedule refreshes based on
|
||||
* the schedule config.
|
||||
*/
|
||||
scheduler?: PluginTaskScheduler;
|
||||
scheduler?: SchedulerService;
|
||||
|
||||
/**
|
||||
* The function that transforms a user entry in msgraph to an entity.
|
||||
@@ -122,10 +125,10 @@ export interface LdapOrgEntityProviderLegacyOptions {
|
||||
* manually at some interval.
|
||||
*
|
||||
* But more commonly you will pass in the result of
|
||||
* {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}
|
||||
* {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner}
|
||||
* to enable automatic scheduling of tasks.
|
||||
*/
|
||||
schedule: 'manual' | TaskRunner;
|
||||
schedule: 'manual' | SchedulerServiceTaskRunner;
|
||||
|
||||
/**
|
||||
* The function that transforms a user entry in LDAP to an entity.
|
||||
@@ -311,7 +314,7 @@ export class LdapOrgEntityProvider implements EntityProvider {
|
||||
markCommitComplete();
|
||||
}
|
||||
|
||||
private schedule(taskRunner: TaskRunner) {
|
||||
private schedule(taskRunner: SchedulerServiceTaskRunner) {
|
||||
this.scheduleFn = async () => {
|
||||
const id = `${this.getProviderName()}:refresh`;
|
||||
await taskRunner.run({
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,8 @@
|
||||
# backstage-plugin-catalog-backend-module-logs
|
||||
|
||||
A module that subscribes to catalog related events and logs them.
|
||||
|
||||
## Getting started
|
||||
|
||||
See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install
|
||||
and configure the plugin.
|
||||
@@ -0,0 +1,11 @@
|
||||
## API Report File for "@backstage/plugin-catalog-backend-module-logs"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeatureCompat } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
const catalogModuleLogs: BackendFeatureCompat;
|
||||
export default catalogModuleLogs;
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-plugin-catalog-backend-module-logs
|
||||
title: '@backstage/plugin-catalog-backend-module-logs'
|
||||
description: A module that subscribes to catalog releated events and logs them.
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-backend-plugin-module
|
||||
owner: maintainers
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-logs",
|
||||
"version": "0.0.0",
|
||||
"description": "A module that subscribes to catalog releated events and logs them.",
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module",
|
||||
"pluginId": "catalog",
|
||||
"pluginPackage": "@backstage/plugin-catalog-backend"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/catalog-backend-module-logs"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"start": "backstage-cli package start",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-events-node": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A catalog module that logs catalog errors using the logger service.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
export { catalogModuleLogs as default } from './module';
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2024 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogModuleLogs } from './module';
|
||||
import { createServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { TestEventsService } from '@backstage/plugin-events-backend-test-utils';
|
||||
import { eventsServiceRef } from '@backstage/plugin-events-node';
|
||||
|
||||
describe('catalogModuleLogs', () => {
|
||||
it('should be correctly wired and set up', async () => {
|
||||
const events = new TestEventsService();
|
||||
const eventsServiceFactory = createServiceFactory({
|
||||
service: eventsServiceRef,
|
||||
deps: {},
|
||||
async factory({}) {
|
||||
return events;
|
||||
},
|
||||
});
|
||||
|
||||
await startTestBackend({
|
||||
features: [
|
||||
mockServices.logger.factory(),
|
||||
eventsServiceFactory(),
|
||||
catalogModuleLogs(),
|
||||
],
|
||||
});
|
||||
|
||||
expect(events.subscribed).toHaveLength(1);
|
||||
expect(events.subscribed[0].id).toEqual('catalog');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2024 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 {
|
||||
coreServices,
|
||||
createBackendModule,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CATALOG_ERRORS_TOPIC } from '@backstage/plugin-catalog-backend';
|
||||
import { eventsServiceRef, EventParams } from '@backstage/plugin-events-node';
|
||||
|
||||
interface EventsPayload {
|
||||
entity: string;
|
||||
location?: string;
|
||||
errors: Error[];
|
||||
}
|
||||
|
||||
interface EventsParamsWithPayload extends EventParams {
|
||||
eventPayload: EventsPayload;
|
||||
}
|
||||
|
||||
/**
|
||||
* A catalog module that logs catalog errors using the logger service.
|
||||
*
|
||||
* @packageDocumentation
|
||||
* @public
|
||||
*/
|
||||
export const catalogModuleLogs = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'logs',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
events: eventsServiceRef,
|
||||
logger: coreServices.logger,
|
||||
},
|
||||
async init({ events, logger }) {
|
||||
events.subscribe({
|
||||
id: 'catalog',
|
||||
topics: [CATALOG_ERRORS_TOPIC],
|
||||
async onEvent(params: EventParams): Promise<void> {
|
||||
const event = params as EventsParamsWithPayload;
|
||||
const { entity, location, errors } = event.eventPayload;
|
||||
for (const error of errors) {
|
||||
logger.warn(error.message, {
|
||||
entity,
|
||||
location,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readMicrosoftGraphConfig, readProviderConfigs } from './config';
|
||||
|
||||
describe('readMicrosoftGraphConfig', () => {
|
||||
@@ -204,7 +203,7 @@ describe('readProviderConfigs', () => {
|
||||
groupSelect: ['id', 'displayName', 'description'],
|
||||
groupFilter: 'securityEnabled eq false',
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
frequency: { minutes: 30 },
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { Duration } from 'luxon';
|
||||
import { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider';
|
||||
import { MicrosoftGraphOrgEntityProvider } from '../processors';
|
||||
|
||||
@@ -67,8 +66,8 @@ describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => {
|
||||
],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(usedSchedule?.frequency).toEqual({ minutes: 30 });
|
||||
expect(usedSchedule?.timeout).toEqual({ minutes: 3 });
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId',
|
||||
|
||||
+2
-5
@@ -16,7 +16,6 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { readProviderConfigs } from './PuppetDbEntityProviderConfig';
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
describe('readProviderConfigs', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
@@ -120,10 +119,8 @@ describe('readProviderConfigs', () => {
|
||||
|
||||
expect(providerConfigs).toHaveLength(1);
|
||||
expect(providerConfigs[0].schedule).toEqual({
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
timeout: {
|
||||
minutes: 10,
|
||||
},
|
||||
frequency: { minutes: 30 },
|
||||
timeout: { minutes: 10 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,7 +23,9 @@ export const EntityMembersListCard: (props: {
|
||||
memberDisplayTitle?: string | undefined;
|
||||
pageSize?: number | undefined;
|
||||
showAggregateMembersToggle?: boolean | undefined;
|
||||
relationType?: string | undefined;
|
||||
relationsType?: EntityRelationAggregation | undefined;
|
||||
relationAggregation?: EntityRelationAggregation | undefined;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -32,6 +34,7 @@ export const EntityOwnershipCard: (props: {
|
||||
entityFilterKind?: string[] | undefined;
|
||||
hideRelationsToggle?: boolean | undefined;
|
||||
relationsType?: EntityRelationAggregation | undefined;
|
||||
relationAggregation?: EntityRelationAggregation | undefined;
|
||||
entityLimit?: number | undefined;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
@@ -55,7 +58,9 @@ export const MembersListCard: (props: {
|
||||
memberDisplayTitle?: string;
|
||||
pageSize?: number;
|
||||
showAggregateMembersToggle?: boolean;
|
||||
relationType?: string;
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
}) => React_2.JSX.Element;
|
||||
|
||||
// @public
|
||||
@@ -82,6 +87,7 @@ export const OwnershipCard: (props: {
|
||||
entityFilterKind?: string[];
|
||||
hideRelationsToggle?: boolean;
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityLimit?: number;
|
||||
}) => React_2.JSX.Element;
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ describe('MemberTab Test', () => {
|
||||
] as Entity[],
|
||||
}),
|
||||
};
|
||||
const getEntitiesSpy = jest.spyOn(catalogApi, 'getEntities');
|
||||
|
||||
it('Display Profile Card', async () => {
|
||||
await renderInTestApp(
|
||||
@@ -115,6 +116,12 @@ describe('MemberTab Test', () => {
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(getEntitiesSpy).toHaveBeenCalledWith({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': ['group:default/team-d'],
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getByAltText('Tara MacGovern')).toHaveAttribute(
|
||||
'src',
|
||||
@@ -149,6 +156,29 @@ describe('MemberTab Test', () => {
|
||||
expect(screen.getByText('Testers (1)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Can query a different relationship', async () => {
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
|
||||
<EntityProvider entity={groupEntity}>
|
||||
<MembersListCard relationType="leaderOf" />
|
||||
</EntityProvider>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
'/catalog': rootRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(getEntitiesSpy).toHaveBeenCalledWith({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.leaderof': ['group:default/team-d'],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('Aggregate members toggle', () => {
|
||||
it('Does not show the aggregate members toggle if the showAggregateMembersToggle prop is undefined', async () => {
|
||||
await renderInTestApp(
|
||||
@@ -346,7 +376,7 @@ describe('MemberTab Test', () => {
|
||||
<EntityLayout.Route path="/" title="Title">
|
||||
<MembersListCard
|
||||
showAggregateMembersToggle
|
||||
relationsType="aggregated"
|
||||
relationAggregation="aggregated"
|
||||
/>
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
@@ -384,7 +414,7 @@ describe('MemberTab Test', () => {
|
||||
<EntityProvider entity={groupA}>
|
||||
<EntityLayout>
|
||||
<EntityLayout.Route path="/" title="Title">
|
||||
<MembersListCard relationsType="aggregated" />
|
||||
<MembersListCard relationAggregation="aggregated" />
|
||||
</EntityLayout.Route>
|
||||
</EntityLayout>
|
||||
</EntityProvider>
|
||||
|
||||
@@ -138,14 +138,19 @@ export const MembersListCard = (props: {
|
||||
memberDisplayTitle?: string;
|
||||
pageSize?: number;
|
||||
showAggregateMembersToggle?: boolean;
|
||||
relationType?: string;
|
||||
/** @deprecated Please use `relationAggregation` instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
}) => {
|
||||
const {
|
||||
memberDisplayTitle = 'Members',
|
||||
pageSize = 50,
|
||||
showAggregateMembersToggle,
|
||||
relationsType = 'direct',
|
||||
relationType = 'memberof',
|
||||
} = props;
|
||||
const relationAggregation =
|
||||
props.relationAggregation ?? props.relationsType ?? 'direct';
|
||||
const classes = useListStyles();
|
||||
|
||||
const { entity: groupEntity } = useEntity<GroupEntity>();
|
||||
@@ -165,7 +170,7 @@ export const MembersListCard = (props: {
|
||||
};
|
||||
|
||||
const [showAggregateMembers, setShowAggregateMembers] = useState(
|
||||
relationsType === 'aggregated',
|
||||
relationAggregation === 'aggregated',
|
||||
);
|
||||
|
||||
const { loading: loadingDescendantMembers, value: descendantMembers } =
|
||||
@@ -177,6 +182,7 @@ export const MembersListCard = (props: {
|
||||
return await getAllDesendantMembersForGroupEntity(
|
||||
groupEntity,
|
||||
catalogApi,
|
||||
relationType,
|
||||
);
|
||||
}, [catalogApi, groupEntity, showAggregateMembers]);
|
||||
const {
|
||||
@@ -187,7 +193,7 @@ export const MembersListCard = (props: {
|
||||
const membersList = await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': [
|
||||
[`relations.${relationType.toLocaleLowerCase('en-US')}`]: [
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: groupNamespace.toLocaleLowerCase('en-US'),
|
||||
|
||||
@@ -114,19 +114,27 @@ export const ComponentsGrid = ({
|
||||
className,
|
||||
entity,
|
||||
relationsType,
|
||||
relationAggregation,
|
||||
entityFilterKind,
|
||||
entityLimit = 6,
|
||||
}: {
|
||||
className?: string;
|
||||
entity: Entity;
|
||||
relationsType: EntityRelationAggregation;
|
||||
/** @deprecated Please use relationAggregation instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityFilterKind?: string[];
|
||||
entityLimit?: number;
|
||||
}) => {
|
||||
const catalogLink = useRouteRef(catalogIndexRouteRef);
|
||||
if (!relationsType && !relationAggregation) {
|
||||
throw new Error(
|
||||
'The relationAggregation property must be set as an EntityRelationAggregation type.',
|
||||
);
|
||||
}
|
||||
const { componentsWithCounters, loading, error } = useGetEntities(
|
||||
entity,
|
||||
relationsType,
|
||||
(relationAggregation ?? relationsType)!, // we can safely use the non-null assertion here because of the run-time check above
|
||||
entityFilterKind,
|
||||
entityLimit,
|
||||
);
|
||||
|
||||
@@ -288,7 +288,7 @@ describe('OwnershipCard', () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[catalogApiRef, catalogApi]]}>
|
||||
<EntityProvider entity={userEntity}>
|
||||
<OwnershipCard relationsType="aggregated" />
|
||||
<OwnershipCard relationAggregation="aggregated" />
|
||||
</EntityProvider>
|
||||
</TestApiProvider>,
|
||||
{
|
||||
|
||||
@@ -67,31 +67,34 @@ export const OwnershipCard = (props: {
|
||||
variant?: InfoCardVariants;
|
||||
entityFilterKind?: string[];
|
||||
hideRelationsToggle?: boolean;
|
||||
/** @deprecated Please use relationAggregation instead */
|
||||
relationsType?: EntityRelationAggregation;
|
||||
relationAggregation?: EntityRelationAggregation;
|
||||
entityLimit?: number;
|
||||
}) => {
|
||||
const {
|
||||
variant,
|
||||
entityFilterKind,
|
||||
hideRelationsToggle,
|
||||
relationsType,
|
||||
entityLimit = 6,
|
||||
} = props;
|
||||
const relationAggregation = props.relationAggregation ?? props.relationsType;
|
||||
const relationsToggle =
|
||||
hideRelationsToggle === undefined ? false : hideRelationsToggle;
|
||||
const classes = useStyles();
|
||||
const { entity } = useEntity();
|
||||
|
||||
const defaultRelationsType = entity.kind === 'User' ? 'aggregated' : 'direct';
|
||||
const [getRelationsType, setRelationsType] = useState(
|
||||
relationsType ?? defaultRelationsType,
|
||||
const defaultRelationAggregation =
|
||||
entity.kind === 'User' ? 'aggregated' : 'direct';
|
||||
const [getRelationAggregation, setRelationAggregation] = useState(
|
||||
relationAggregation ?? defaultRelationAggregation,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!relationsType) {
|
||||
setRelationsType(defaultRelationsType);
|
||||
if (!relationAggregation) {
|
||||
setRelationAggregation(defaultRelationAggregation);
|
||||
}
|
||||
}, [setRelationsType, defaultRelationsType, relationsType]);
|
||||
}, [setRelationAggregation, defaultRelationAggregation, relationAggregation]);
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
@@ -112,16 +115,18 @@ export const OwnershipCard = (props: {
|
||||
placement="top"
|
||||
arrow
|
||||
title={`${
|
||||
getRelationsType === 'direct' ? 'Direct' : 'Aggregated'
|
||||
getRelationAggregation === 'direct' ? 'Direct' : 'Aggregated'
|
||||
} Relations`}
|
||||
>
|
||||
<Switch
|
||||
color="primary"
|
||||
checked={getRelationsType !== 'direct'}
|
||||
checked={getRelationAggregation !== 'direct'}
|
||||
onChange={() => {
|
||||
const updatedRelationsType =
|
||||
getRelationsType === 'direct' ? 'aggregated' : 'direct';
|
||||
setRelationsType(updatedRelationsType);
|
||||
const updatedRelationAggregation =
|
||||
getRelationAggregation === 'direct'
|
||||
? 'aggregated'
|
||||
: 'direct';
|
||||
setRelationAggregation(updatedRelationAggregation);
|
||||
}}
|
||||
name="pin"
|
||||
inputProps={{ 'aria-label': 'Ownership Type Switch' }}
|
||||
@@ -136,7 +141,7 @@ export const OwnershipCard = (props: {
|
||||
className={classes.grid}
|
||||
entity={entity}
|
||||
entityLimit={entityLimit}
|
||||
relationsType={getRelationsType}
|
||||
relationAggregation={getRelationAggregation}
|
||||
entityFilterKind={entityFilterKind}
|
||||
/>
|
||||
</InfoCard>
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('useGetEntities', () => {
|
||||
]),
|
||||
});
|
||||
|
||||
describe('given aggregated relationsType', () => {
|
||||
describe('given aggregated relationAggregation', () => {
|
||||
const whenHookIsCalledWith = async (_entity: Entity) => {
|
||||
const { result } = renderHook(
|
||||
({ entity }) => useGetEntities(entity, 'aggregated'),
|
||||
@@ -205,7 +205,7 @@ describe('useGetEntities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('given direct relationsType', () => {
|
||||
describe('given direct relationAggregation', () => {
|
||||
const whenHookIsCalledWith = async (_entity: Entity) => {
|
||||
const { result } = renderHook(
|
||||
({ entity }) => useGetEntities(entity, 'direct'),
|
||||
|
||||
@@ -125,11 +125,11 @@ const getChildOwnershipEntityRefs = async (
|
||||
|
||||
const getOwners = async (
|
||||
entity: Entity,
|
||||
relations: EntityRelationAggregation,
|
||||
relationAggregation: EntityRelationAggregation,
|
||||
catalogApi: CatalogApi,
|
||||
): Promise<string[]> => {
|
||||
const isGroup = entity.kind === 'Group';
|
||||
const isAggregated = relations === 'aggregated';
|
||||
const isAggregated = relationAggregation === 'aggregated';
|
||||
const isUserEntity = entity.kind === 'User';
|
||||
|
||||
if (isAggregated && isGroup) {
|
||||
@@ -166,7 +166,7 @@ const getOwnedEntitiesByOwners = (
|
||||
|
||||
export function useGetEntities(
|
||||
entity: Entity,
|
||||
relations: EntityRelationAggregation,
|
||||
relationAggregation: EntityRelationAggregation,
|
||||
entityFilterKind?: string[],
|
||||
entityLimit = 6,
|
||||
): {
|
||||
@@ -189,7 +189,7 @@ export function useGetEntities(
|
||||
error,
|
||||
value: componentsWithCounters,
|
||||
} = useAsync(async () => {
|
||||
const owners = await getOwners(entity, relations, catalogApi);
|
||||
const owners = await getOwners(entity, relationAggregation, catalogApi);
|
||||
|
||||
const ownedEntitiesList = await getOwnedEntitiesByOwners(
|
||||
owners,
|
||||
@@ -230,7 +230,7 @@ export function useGetEntities(
|
||||
kind: string;
|
||||
queryParams: string;
|
||||
}>;
|
||||
}, [catalogApi, entity, relations]);
|
||||
}, [catalogApi, entity, relationAggregation]);
|
||||
|
||||
return {
|
||||
componentsWithCounters,
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
export const getMembersFromGroups = async (
|
||||
groups: CompoundEntityRef[],
|
||||
catalogApi: CatalogApi,
|
||||
relationship = 'memberof',
|
||||
) => {
|
||||
const membersList =
|
||||
groups.length === 0
|
||||
@@ -38,13 +39,14 @@ export const getMembersFromGroups = async (
|
||||
: await catalogApi.getEntities({
|
||||
filter: {
|
||||
kind: 'User',
|
||||
'relations.memberof': groups.map(group =>
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: group.namespace.toLocaleLowerCase('en-US'),
|
||||
name: group.name.toLocaleLowerCase('en-US'),
|
||||
}),
|
||||
),
|
||||
[`relations.${relationship.toLocaleLowerCase('en-US')}`]:
|
||||
groups.map(group =>
|
||||
stringifyEntityRef({
|
||||
kind: 'group',
|
||||
namespace: group.namespace.toLocaleLowerCase('en-US'),
|
||||
name: group.name.toLocaleLowerCase('en-US'),
|
||||
}),
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,10 +101,12 @@ export const getDescendantGroupsFromGroup = async (
|
||||
export const getAllDesendantMembersForGroupEntity = async (
|
||||
groupEntity: GroupEntity,
|
||||
catalogApi: CatalogApi,
|
||||
relationship = 'memberof',
|
||||
) =>
|
||||
getMembersFromGroups(
|
||||
await getDescendantGroupsFromGroup(groupEntity, catalogApi),
|
||||
catalogApi,
|
||||
relationship,
|
||||
);
|
||||
|
||||
export const removeDuplicateEntitiesFrom = (entityArray: Entity[]) => {
|
||||
|
||||
@@ -5440,6 +5440,19 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-catalog-backend-module-logs@workspace:plugins/catalog-backend-module-logs"
|
||||
dependencies:
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/plugin-catalog-backend": "workspace:^"
|
||||
"@backstage/plugin-events-backend-test-utils": "workspace:^"
|
||||
"@backstage/plugin-events-node": "workspace:^"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/plugin-catalog-backend-module-msgraph@workspace:plugins/catalog-backend-module-msgraph":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/plugin-catalog-backend-module-msgraph@workspace:plugins/catalog-backend-module-msgraph"
|
||||
|
||||
Reference in New Issue
Block a user