Merge branch 'master' into rugvip/noloadrefs
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Builtin support for cookiecutter based templates has been removed from `@backstage/plugin-scaffolder-backend`. Due to this, the `containerRunner` argument to its `createRouter` has also been removed.
|
||||
|
||||
If you do not use cookiecutter templates and are fine with removing support from it in your own installation, update your `packages/backend/src/plugins/scaffolder.ts` file as follows:
|
||||
|
||||
```diff
|
||||
-import { DockerContainerRunner } from '@backstage/backend-common';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { createRouter } from '@backstage/plugin-scaffolder-backend';
|
||||
-import Docker from 'dockerode';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
export default async function createPlugin({
|
||||
reader,
|
||||
discovery,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
- const dockerClient = new Docker();
|
||||
- const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
-
|
||||
const catalogClient = new CatalogClient({ discoveryApi: discovery });
|
||||
-
|
||||
return await createRouter({
|
||||
- containerRunner,
|
||||
logger,
|
||||
config,
|
||||
// ...
|
||||
```
|
||||
|
||||
If you want to retain cookiecutter support, please use the `@backstage/plugin-scaffolder-backend-module-cookiecutter` package explicitly (see [its README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter) for installation instructions).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `reduceCatalogFilters` and `reduceEntityFilters` due to low external utility value.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `useEntityFromUrl`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `ScaffolderTaskOutput.entityRef` and `ScaffolderTaskOutput.remoteUrl`, which both have been deprecated for over a year. Please use the `links` output instead.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
**BREAKING**:
|
||||
|
||||
- Removed the `createFetchCookiecutterAction` export, please use the `@backstage/plugin-scaffolder-backend-module-cookiecutter` package explicitly (see [its README](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter) for installation instructions).
|
||||
- Removed the `containerRunner` argument from the types `RouterOptions` (as used by `createRouter`) and `CreateBuiltInActionsOptions` (as used by `createBuiltinActions`).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
**BREAKING**:
|
||||
|
||||
- Removed the previously deprecated `runPeriodically` export. Please use the `@backstage/backend-tasks` package instead, or copy [the actual implementation](https://github.com/backstage/backstage/blob/02875d4d56708c60f86f6b0a5b3da82e24988354/plugins/catalog-backend/src/util/runPeriodically.ts#L29) into your own code if you explicitly do not want coordination of task runs across your worker nodes.
|
||||
- Removed the previously deprecated `CatalogProcessorLocationResult.optional` field. Please set the corresponding `LocationSpec.presence` field to `'optional'` instead.
|
||||
- Related to the previous point, the `processingResult.location` function no longer has a second boolean `optional` argument. Please set the corresponding `LocationSpec.presence` field to `'optional'` instead.
|
||||
- Removed the previously deprecated `StaticLocationProcessor`. It has not been in use for some time; its functionality is covered by `ConfigLocationEntityProvider` instead.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-rollbar': patch
|
||||
---
|
||||
|
||||
Removed usage of removed hook.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `useEntityCompoundName`, use `useRouteRefParams(entityRouteRef)` instead.
|
||||
@@ -1,3 +1,10 @@
|
||||
{
|
||||
"currentReleaseVersion": {}
|
||||
"currentReleaseVersion": {
|
||||
"@backstage/backend-common": "0.12.1",
|
||||
"@backstage/catalog-model": "0.12.1",
|
||||
"@backstage/cli": "0.15.1",
|
||||
"@backstage/plugin-catalog-backend": "0.23.1",
|
||||
"@backstage/plugin-catalog-common": "0.2.1",
|
||||
"@backstage/plugin-catalog-react": "0.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Applied the fix from version `0.15.1` of this package, which was part of the `v0.70.1` release of Backstage.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/catalog-model': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-common': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Applied the fix for the `/alpha` entry point resolution that was part of the `v0.70.1` release of Backstage.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `useEntityListProvider` use `useEntityList` instead.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed the previously deprecated `OctokitProvider` class.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `useOwnedEntities` and moved its usage internally to the scaffolder-backend where it's used.
|
||||
|
||||
**BREAKING**: Removed `EntityTypeReturn` type which is now inlined.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Removed `CatalogClient.getLocationByEntity` and `CatalogClient.getOriginLocationByEntity` which had previously been deprecated. Please use `CatalogApi.getLocationByRef` instead. Note that this only affects you if you were using `CatalogClient` (the class) directly, rather than `CatalogApi` (the interface), since it has been removed from the interface in an earlier release.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Internalized usage of `useOwnedEntities` hook.
|
||||
@@ -84,6 +84,7 @@ dockerfiles
|
||||
Dockerize
|
||||
dockerode
|
||||
Docusaurus
|
||||
ecco
|
||||
env
|
||||
Env
|
||||
elasticsearch
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Avoid upgrade surprises with Backstage Upgrade Helper
|
||||
author: Vincenzo Scamporlino, Spotify
|
||||
authorURL: https://github.com/vinzscam
|
||||
authorImageURL: https://avatars.githubusercontent.com/u/8433119?v=4
|
||||
---
|
||||
|
||||

|
||||
|
||||
_TLDR;_ The Spotify team recently announced [Backstage Upgrade Helper](https://backstage.github.io/upgrade-helper): a tool that helps adopters keep their Backstage application up-to-date. **Spoiler alert**: this is also a hidden and heartwarming tale about the beauty of open source innovation.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## Upgrading Backstage was a common pain point
|
||||
|
||||
Without a doubt, one of the most exciting things about Backstage is our community. Step into our daily [Discord conversations](https://discord.gg/sBEF5VkG) and the monthly [Community Sessions](https://www.youtube.com/watch?v=0QMQYSTKAx0) and you’ll find a ton of great ideas, problem-solving, and support for one another.
|
||||
|
||||
During one of these discussions, we realized we all shared the same pain point — upgrading a Backstage application.
|
||||
|
||||
The Backstage open source project is in hyper-growth mode, where dozens of pull requests are merged each day. This means adopters need to go through the upgrade process more often in order to get the latest and greatest features. The Spotify team heard a lot of feedback about how painful and manual this process was — so we decided to tackle this problem during one of our internal Hack Days.
|
||||
|
||||
At Spotify, we run Hack Days every month and a lot of ideas come from those sessions (in fact, the idea to open source Backstage itself [was born on a Hack Day](https://open.spotify.com/episode/332yTwGiILGKTS7dsHCj2P)!). During a brainstorming session, we noticed that another open source community faced a similar upgrading problem years back and came up with a really good solution.
|
||||
|
||||
If you have ever worked on a React Native project, you might immediately recognize what we are referring to: The [React Native Upgrade Helper](https://github.com/react-native-community/upgrade-helper) — a web tool to support React Native developers in upgrading their apps — might have saved hours of your time. It certainly saved us hours of ours. So we looked into the project details and discovered it could fulfill our Backstage needs.
|
||||
|
||||
We started by creating a fork from React Native’s open source project, applying a few changes on top of it to introduce Backstage support, and — _ecco_ — we had a new Backstage Upgrade Helper! In just a few days with pretty minimal effort, we created a product that would save a lot of time for the entire Backstage community. And we had fun at the same time (happy developers make happy code 😁 ).
|
||||
|
||||
## So what is Backstage Upgrade Helper?
|
||||
|
||||
The Backstage Upgrade Helper tool enables adopters to easily upgrade their Backstage app by using the power of git to create a diff between different versions.
|
||||
|
||||
Whenever a new version of Backstage is released, the Helper scaffolds a new dummy Backstage app using the `backstage-create-app` cli utility and checks all the generated files in a specific git branch. After the branch is generated, it gets compared with all the existing ones, which results in generating specific git patches stored in specific files. By selecting the version of the current Backstage release together with the version you want to upgrade to, the UI knows which patch file needs to be picked up.
|
||||
|
||||
So, now you can update your Backstage application in three steps rather than manually reading the changelogs of all the Backstage modules in reverse order.
|
||||
|
||||
### To use the Upgrade Helper, follow these simple steps
|
||||
|
||||
1. Go to the [Backstage Upgrade Helper](https://backstage.github.io/upgrade-helper) and enter your current release and the release you would like to upgrade to.
|
||||
|
||||
2. Press **Show me how to upgrade!** After that, the feature spits out the changes between the two Backstage versions in a split-screen view for easy comparison.
|
||||
|
||||

|
||||
|
||||
3. Apply the suggested changes to your source code to correctly upgrade your app to the selected Backstage version. This will let you directly migrate from the version you’re currently using to the target version and skip all the intermediate steps you would have otherwise gone through manually reading all the changelogs.
|
||||
|
||||
[Watch the Upgrade Helper demo](https://youtu.be/nYjI2j-lWEM?t=410).
|
||||
|
||||
## Open source inspires
|
||||
|
||||
Thus far, the Backstage Upgrade Helper has gotten a lot of good feedback from the community and we’re sure to see awesome contributions in the future. But all the credit behind this idea goes to the React Native community.
|
||||
|
||||
With React Native’s open source contributions, we were able to quickly solve a tough problem for the entire Backstage community. Not only did React Native’s project save us time creating a new product, but it has also saved our adopters time upgrading Backstage.
|
||||
|
||||
This is why we love working in open source. The hard work done for one community has the power to influence and inspire another community. We hope Backstage can do the same and pay it forward to other open source projects.
|
||||
|
||||
For more Backstage Upgrade Helper resources, check out the [UI code](https://github.com/backstage/upgrade-helper) and the [git patches](https://github.com/backstage/upgrade-helper-diff).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 604 KiB |
@@ -1,5 +1,11 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed runtime resolution of the `/alpha` entry point.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -14,10 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DockerContainerRunner } from '@backstage/backend-common';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { createRouter } from '@backstage/plugin-scaffolder-backend';
|
||||
import Docker from 'dockerode';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
@@ -28,13 +26,8 @@ export default async function createPlugin({
|
||||
reader,
|
||||
discovery,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
const dockerClient = new Docker();
|
||||
const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
|
||||
const catalogClient = new CatalogClient({ discoveryApi: discovery });
|
||||
|
||||
return await createRouter({
|
||||
containerRunner,
|
||||
logger,
|
||||
config,
|
||||
database,
|
||||
|
||||
@@ -107,11 +107,6 @@ export class CatalogClient implements CatalogApi {
|
||||
request: GetEntityFacetsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<GetEntityFacetsResponse>;
|
||||
// @deprecated (undocumented)
|
||||
getLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
getLocationById(
|
||||
id: string,
|
||||
options?: CatalogRequestOptions,
|
||||
@@ -120,11 +115,6 @@ export class CatalogClient implements CatalogApi {
|
||||
locationRef: string,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
// @deprecated (undocumented)
|
||||
getOriginLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location_2 | undefined>;
|
||||
refreshEntity(
|
||||
entityRef: string,
|
||||
options?: CatalogRequestOptions,
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
Entity,
|
||||
CompoundEntityRef,
|
||||
parseEntityRef,
|
||||
@@ -308,49 +306,6 @@ export class CatalogClient implements CatalogApi {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated please use getLocationByRef instead
|
||||
*/
|
||||
async getOriginLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
const locationCompound =
|
||||
entity.metadata.annotations?.[ANNOTATION_ORIGIN_LOCATION];
|
||||
if (!locationCompound) {
|
||||
return undefined;
|
||||
}
|
||||
const all: { data: Location }[] = await this.requestRequired(
|
||||
'GET',
|
||||
'/locations',
|
||||
options,
|
||||
);
|
||||
return all
|
||||
.map(r => r.data)
|
||||
.find(l => locationCompound === stringifyLocationRef(l));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated please use getLocationByRef instead
|
||||
*/
|
||||
async getLocationByEntity(
|
||||
entity: Entity,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<Location | undefined> {
|
||||
const locationCompound = entity.metadata.annotations?.[ANNOTATION_LOCATION];
|
||||
if (!locationCompound) {
|
||||
return undefined;
|
||||
}
|
||||
const all: { data: Location }[] = await this.requestRequired(
|
||||
'GET',
|
||||
'/locations',
|
||||
options,
|
||||
);
|
||||
return all
|
||||
.map(r => r.data)
|
||||
.find(l => locationCompound === stringifyLocationRef(l));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc CatalogApi.getLocationByRef}
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed runtime resolution of the `/alpha` entry point.
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.15.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed an issue where the release stage entry point of packages were not resolved correctly.
|
||||
|
||||
## 0.15.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -23,6 +23,11 @@ const SKIPPED_KEYS = ['access', 'registry', 'tag', 'alphaTypes', 'betaTypes'];
|
||||
const PKG_PATH = 'package.json';
|
||||
const PKG_BACKUP_PATH = 'package.json-prepack';
|
||||
|
||||
function resolveEntrypoint(pkg: any, name: string) {
|
||||
const targetEntry = pkg.publishConfig[name] || pkg[name];
|
||||
return targetEntry && joinPath('..', targetEntry);
|
||||
}
|
||||
|
||||
// Writes e.g. alpha/package.json
|
||||
async function writeReleaseStageEntrypoint(pkg: any, stage: 'alpha' | 'beta') {
|
||||
await fs.ensureDir(paths.resolveTarget(stage));
|
||||
@@ -31,9 +36,9 @@ async function writeReleaseStageEntrypoint(pkg: any, stage: 'alpha' | 'beta') {
|
||||
{
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
main: (pkg.publishConfig.main || pkg.main) && '..',
|
||||
module: (pkg.publishConfig.module || pkg.module) && '..',
|
||||
browser: (pkg.publishConfig.browser || pkg.browser) && '..',
|
||||
main: resolveEntrypoint(pkg, 'main'),
|
||||
module: resolveEntrypoint(pkg, 'module'),
|
||||
browser: resolveEntrypoint(pkg, 'browser'),
|
||||
types: joinPath('..', pkg.publishConfig[`${stage}Types`]),
|
||||
},
|
||||
{ encoding: 'utf8', spaces: 2 },
|
||||
|
||||
@@ -20,6 +20,11 @@ import { join as joinPath, resolve as resolvePath } from 'path';
|
||||
|
||||
const SKIPPED_KEYS = ['access', 'registry', 'tag', 'alphaTypes', 'betaTypes'];
|
||||
|
||||
function resolveEntrypoint(pkg: any, name: string) {
|
||||
const targetEntry = pkg.publishConfig[name] || pkg[name];
|
||||
return targetEntry && joinPath('..', targetEntry);
|
||||
}
|
||||
|
||||
// Writes e.g. alpha/package.json
|
||||
async function writeReleaseStageEntrypoint(
|
||||
pkg: any,
|
||||
@@ -32,9 +37,9 @@ async function writeReleaseStageEntrypoint(
|
||||
{
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
main: (pkg.publishConfig.main || pkg.main) && '..',
|
||||
module: (pkg.publishConfig.module || pkg.module) && '..',
|
||||
browser: (pkg.publishConfig.browser || pkg.browser) && '..',
|
||||
main: resolveEntrypoint(pkg, 'main'),
|
||||
module: resolveEntrypoint(pkg, 'module'),
|
||||
browser: resolveEntrypoint(pkg, 'browser'),
|
||||
types: joinPath('..', pkg.publishConfig[`${stage}Types`]),
|
||||
},
|
||||
{ encoding: 'utf8', spaces: 2 },
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { DockerContainerRunner } from '@backstage/backend-common';
|
||||
import { CatalogClient } from '@backstage/catalog-client';
|
||||
import { createRouter } from '@backstage/plugin-scaffolder-backend';
|
||||
import Docker from 'dockerode';
|
||||
import { Router } from 'express';
|
||||
import type { PluginEnvironment } from '../types';
|
||||
|
||||
@@ -12,12 +10,8 @@ export default async function createPlugin({
|
||||
reader,
|
||||
discovery,
|
||||
}: PluginEnvironment): Promise<Router> {
|
||||
const dockerClient = new Docker();
|
||||
const containerRunner = new DockerContainerRunner({ dockerClient });
|
||||
const catalogClient = new CatalogClient({ discoveryApi: discovery });
|
||||
|
||||
return await createRouter({
|
||||
containerRunner,
|
||||
logger,
|
||||
config,
|
||||
database,
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/plugin-catalog-backend
|
||||
|
||||
## 0.23.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Marked `GithubMultiOrgReaderProcessor` as stable, as it was moved to `/alpha` by mistake.
|
||||
- Fixed runtime resolution of the `/alpha` entry point.
|
||||
- Updated dependencies
|
||||
- @backstage/backend-common@0.12.1
|
||||
- @backstage/catalog-model@0.12.1
|
||||
- @backstage/plugin-catalog-common@0.2.1
|
||||
|
||||
## 0.23.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -333,7 +333,6 @@ export type CatalogProcessorErrorResult = {
|
||||
export type CatalogProcessorLocationResult = {
|
||||
type: 'location';
|
||||
location: LocationSpec;
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -706,7 +705,7 @@ export type GithubMultiOrgConfig = Array<{
|
||||
userNamespace: string | undefined;
|
||||
}>;
|
||||
|
||||
// @alpha
|
||||
// @public
|
||||
export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
|
||||
constructor(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
@@ -812,7 +811,7 @@ function inputError(
|
||||
// @public @deprecated (undocumented)
|
||||
function location_2(
|
||||
newLocation: LocationSpec,
|
||||
optional?: boolean,
|
||||
_optional?: boolean,
|
||||
): CatalogProcessorResult;
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -1007,10 +1006,7 @@ export const processingResult: Readonly<{
|
||||
atLocation: LocationSpec,
|
||||
message: string,
|
||||
) => CatalogProcessorResult;
|
||||
readonly location: (
|
||||
newLocation: LocationSpec,
|
||||
optional?: boolean | undefined,
|
||||
) => CatalogProcessorResult;
|
||||
readonly location: (newLocation: LocationSpec) => CatalogProcessorResult;
|
||||
readonly entity: (
|
||||
atLocation: LocationSpec,
|
||||
newEntity: Entity,
|
||||
@@ -1074,22 +1070,6 @@ export interface RouterOptions {
|
||||
refreshService?: RefreshService;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export function runPeriodically(fn: () => any, delayMs: number): () => void;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export class StaticLocationProcessor implements StaticLocationProcessor {
|
||||
constructor(staticLocations: LocationSpec[]);
|
||||
// (undocumented)
|
||||
static fromConfig(config: Config): StaticLocationProcessor;
|
||||
// (undocumented)
|
||||
readLocation(
|
||||
location: LocationSpec,
|
||||
_optional: boolean,
|
||||
emit: CatalogProcessorEmit,
|
||||
): Promise<boolean>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export class UrlReaderProcessor implements CatalogProcessor {
|
||||
constructor(options: { reader: UrlReader; logger: Logger_2 });
|
||||
|
||||
@@ -68,9 +68,9 @@ export function generalError(
|
||||
*/
|
||||
export function location(
|
||||
newLocation: LocationSpec,
|
||||
optional?: boolean,
|
||||
_optional?: boolean,
|
||||
): CatalogProcessorResult {
|
||||
return { type: 'location', location: newLocation, optional };
|
||||
return { type: 'location', location: newLocation };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,11 +54,8 @@ export const processingResult = Object.freeze({
|
||||
return { type: 'error', location: atLocation, error: new Error(message) };
|
||||
},
|
||||
|
||||
location(
|
||||
newLocation: LocationSpec,
|
||||
optional?: boolean,
|
||||
): CatalogProcessorResult {
|
||||
return { type: 'location', location: newLocation, optional };
|
||||
location(newLocation: LocationSpec): CatalogProcessorResult {
|
||||
return { type: 'location', location: newLocation };
|
||||
},
|
||||
|
||||
entity(atLocation: LocationSpec, newEntity: Entity): CatalogProcessorResult {
|
||||
|
||||
@@ -147,8 +147,6 @@ export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void;
|
||||
export type CatalogProcessorLocationResult = {
|
||||
type: 'location';
|
||||
location: LocationSpec;
|
||||
/** @deprecated Set `location.presence = 'optional'` instead */
|
||||
optional?: boolean;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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 { Config } from '@backstage/config';
|
||||
import {
|
||||
processingResult,
|
||||
CatalogProcessorEmit,
|
||||
LocationSpec,
|
||||
} from '../../api';
|
||||
|
||||
/**
|
||||
* @deprecated no longer in use, replaced by the ConfigLocationEntityProvider.
|
||||
* @public
|
||||
*/
|
||||
export class StaticLocationProcessor implements StaticLocationProcessor {
|
||||
static fromConfig(config: Config): StaticLocationProcessor {
|
||||
const locations: LocationSpec[] = [];
|
||||
|
||||
const lConfigs = config.getOptionalConfigArray('catalog.locations') ?? [];
|
||||
for (const lConfig of lConfigs) {
|
||||
const type = lConfig.getString('type');
|
||||
const target = lConfig.getString('target');
|
||||
locations.push({ type, target });
|
||||
}
|
||||
|
||||
return new StaticLocationProcessor(locations);
|
||||
}
|
||||
|
||||
constructor(private readonly staticLocations: LocationSpec[]) {}
|
||||
|
||||
async readLocation(
|
||||
location: LocationSpec,
|
||||
_optional: boolean,
|
||||
emit: CatalogProcessorEmit,
|
||||
): Promise<boolean> {
|
||||
if (location.type !== 'bootstrap') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const staticLocation of this.staticLocations) {
|
||||
emit(processingResult.location(staticLocation));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,5 @@ export type {
|
||||
PlaceholderResolverRead,
|
||||
PlaceholderResolverResolveUrl,
|
||||
} from './PlaceholderProcessor';
|
||||
export { StaticLocationProcessor } from './StaticLocationProcessor';
|
||||
export { UrlReaderProcessor } from './UrlReaderProcessor';
|
||||
export { parseEntityYaml } from '../util/parse';
|
||||
|
||||
@@ -40,10 +40,11 @@ import {
|
||||
import { buildOrgHierarchy } from '../util/org';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
* Extracts teams and users out of a multiple GitHub orgs namespaced per org.
|
||||
*
|
||||
* Be aware that this processor may not be compatible with future org structures in the catalog.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GithubMultiOrgReaderProcessor implements CatalogProcessor {
|
||||
private readonly integrations: ScmIntegrationRegistry;
|
||||
|
||||
@@ -106,9 +106,8 @@ export class ProcessorOutputCollector {
|
||||
|
||||
this.deferredEntities.push({ entity, locationKey: location });
|
||||
} else if (i.type === 'location') {
|
||||
const presence = i.optional ? 'optional' : 'required';
|
||||
const entity = locationSpecToLocationEntity(
|
||||
{ presence, ...i.location },
|
||||
i.location,
|
||||
this.parentEntity,
|
||||
);
|
||||
const locationKey = getEntityLocationRef(entity);
|
||||
|
||||
@@ -14,5 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './runPeriodically';
|
||||
export * from './RecursivePartial';
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Runs a function repeatedly, with a fixed wait between invocations.
|
||||
*
|
||||
* Supports async functions, and silently ignores exceptions and rejections.
|
||||
*
|
||||
* @param fn - The function to run. May return a Promise.
|
||||
* @param delayMs - The delay between a completed function invocation and the
|
||||
* next.
|
||||
* @returns A function that, when called, stops the invocation loop.
|
||||
* @deprecated use \@backstage/backend-tasks package instead.
|
||||
* @public
|
||||
*/
|
||||
export function runPeriodically(fn: () => any, delayMs: number): () => void {
|
||||
let cancel: () => void;
|
||||
let cancelled = false;
|
||||
const cancellationPromise = new Promise<void>(resolve => {
|
||||
cancel = () => {
|
||||
resolve();
|
||||
cancelled = true;
|
||||
};
|
||||
});
|
||||
|
||||
const startRefresh = async () => {
|
||||
while (!cancelled) {
|
||||
try {
|
||||
await fn();
|
||||
} catch {
|
||||
// ignore intentionally
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
new Promise(resolve => setTimeout(resolve, delayMs)),
|
||||
cancellationPromise,
|
||||
]);
|
||||
}
|
||||
};
|
||||
startRefresh();
|
||||
|
||||
return cancel!;
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/plugin-catalog-common
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed runtime resolution of the `/alpha` entry point.
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/plugin-catalog-react
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fixed runtime resolution of the `/alpha` entry point.
|
||||
- Updated dependencies
|
||||
- @backstage/catalog-model@0.12.1
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -387,15 +387,6 @@ export interface EntityTypePickerProps {
|
||||
initialFilter?: string;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export type EntityTypeReturn = {
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
availableTypes: string[];
|
||||
selectedTypes: string[];
|
||||
setSelectedTypes: (types: string[]) => void;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const FavoriteEntity: (props: FavoriteEntityProps) => JSX.Element;
|
||||
|
||||
@@ -470,16 +461,6 @@ export class MockStarredEntitiesApi implements StarredEntitiesApi {
|
||||
toggleStarred(entityRef: string): Promise<void>;
|
||||
}
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function reduceCatalogFilters(
|
||||
filters: EntityFilter[],
|
||||
): Record<string, string | symbol | (string | symbol)[]>;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export function reduceEntityFilters(
|
||||
filters: EntityFilter[],
|
||||
): (entity: Entity) => boolean;
|
||||
|
||||
// @public
|
||||
export interface StarredEntitiesApi {
|
||||
starredEntitie$(): Observable<Set<string>>;
|
||||
@@ -515,16 +496,6 @@ export function useEntity<TEntity extends Entity = Entity>(): {
|
||||
refresh?: VoidFunction;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export const useEntityCompoundName: () => {
|
||||
kind: string;
|
||||
namespace: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const useEntityFromUrl: () => EntityLoadingStatus;
|
||||
|
||||
// @public @deprecated
|
||||
export function useEntityKinds(): {
|
||||
error: Error | undefined;
|
||||
@@ -537,11 +508,6 @@ export function useEntityList<
|
||||
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
|
||||
>(): EntityListContextProps<EntityFilters>;
|
||||
|
||||
// @public @deprecated
|
||||
export function useEntityListProvider<
|
||||
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
|
||||
>(): EntityListContextProps<EntityFilters>;
|
||||
|
||||
// @public
|
||||
export function useEntityOwnership(): {
|
||||
loading: boolean;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
export {
|
||||
useEntity,
|
||||
useEntityFromUrl,
|
||||
EntityProvider,
|
||||
AsyncEntityProvider,
|
||||
useAsyncEntity,
|
||||
@@ -25,11 +24,9 @@ export type {
|
||||
EntityProviderProps,
|
||||
AsyncEntityProviderProps,
|
||||
} from './useEntity';
|
||||
export { useEntityCompoundName } from './useEntityCompoundName';
|
||||
export {
|
||||
EntityListContext,
|
||||
EntityListProvider,
|
||||
useEntityListProvider,
|
||||
useEntityList,
|
||||
} from './useEntityListProvider';
|
||||
export type {
|
||||
@@ -37,7 +34,6 @@ export type {
|
||||
EntityListContextProps,
|
||||
} from './useEntityListProvider';
|
||||
export { useEntityTypeFilter } from './useEntityTypeFilter';
|
||||
export type { EntityTypeReturn } from './useEntityTypeFilter';
|
||||
export { useEntityKinds } from './useEntityKinds';
|
||||
export { useOwnUser } from './useOwnUser';
|
||||
export { useRelatedEntities } from './useRelatedEntities';
|
||||
|
||||
@@ -14,17 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
useVersionedContext,
|
||||
} from '@backstage/version-bridge';
|
||||
import React, { ReactNode, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import useAsyncRetry from 'react-use/lib/useAsyncRetry';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { useEntityCompoundName } from './useEntityCompoundName';
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
/** @public */
|
||||
export type EntityLoadingStatus<TEntity extends Entity = Entity> = {
|
||||
@@ -100,35 +95,6 @@ export const EntityProvider = ({ entity, children }: EntityProviderProps) => (
|
||||
/>
|
||||
);
|
||||
|
||||
/** @public
|
||||
* @deprecated will be deleted shortly due to low external usage, re-implement if needed.
|
||||
*/
|
||||
export const useEntityFromUrl = (): EntityLoadingStatus => {
|
||||
const { kind, namespace, name } = useEntityCompoundName();
|
||||
const navigate = useNavigate();
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const {
|
||||
value: entity,
|
||||
error,
|
||||
loading,
|
||||
retry: refresh,
|
||||
} = useAsyncRetry(
|
||||
() => catalogApi.getEntityByRef({ kind, namespace, name }),
|
||||
[catalogApi, kind, namespace, name],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!name) {
|
||||
errorApi.post(new Error('No name provided!'));
|
||||
navigate('/');
|
||||
}
|
||||
}, [errorApi, navigate, error, loading, entity, name]);
|
||||
|
||||
return { entity, loading, error, refresh };
|
||||
};
|
||||
|
||||
/**
|
||||
* Grab the current entity from the context, throws if the entity has not yet been loaded
|
||||
* or is not available.
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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 { entityRouteRef } from '../routes';
|
||||
import { useRouteRefParams } from '@backstage/core-plugin-api';
|
||||
|
||||
/**
|
||||
* Grabs entity kind, namespace, and name from the location
|
||||
* @public
|
||||
* @deprecated use {@link @backstage/core-plugin-api#useRouteRefParams} instead
|
||||
*/
|
||||
export const useEntityCompoundName = () => {
|
||||
const { kind, namespace, name } = useRouteRefParams(entityRouteRef);
|
||||
return { kind, namespace, name };
|
||||
};
|
||||
@@ -249,20 +249,6 @@ export const EntityListProvider = <EntityFilters extends DefaultEntityFilters>({
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for interacting with the entity list context provided by the {@link EntityListProvider}.
|
||||
* @public
|
||||
* @deprecated use {@link useEntityList} instead.
|
||||
*/
|
||||
export function useEntityListProvider<
|
||||
EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,
|
||||
>(): EntityListContextProps<EntityFilters> {
|
||||
const context = useContext(EntityListContext);
|
||||
if (!context)
|
||||
throw new Error('useEntityList must be used within EntityListProvider');
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for interacting with the entity list context provided by the {@link EntityListProvider}.
|
||||
* @public
|
||||
|
||||
@@ -23,17 +23,6 @@ import { catalogApiRef } from '../api';
|
||||
import { useEntityList } from './useEntityListProvider';
|
||||
import { EntityTypeFilter } from '../filters';
|
||||
|
||||
/** @public
|
||||
* @deprecated type inlined with {@link useEntityTypeFilter}.
|
||||
*/
|
||||
export type EntityTypeReturn = {
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
availableTypes: string[];
|
||||
selectedTypes: string[];
|
||||
setSelectedTypes: (types: string[]) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook built on top of `useEntityList` for enabling selection of valid `spec.type` values
|
||||
* based on the selected EntityKindFilter.
|
||||
|
||||
@@ -30,5 +30,12 @@ export * from './filters';
|
||||
export { entityRouteParams, entityRouteRef } from './routes';
|
||||
export * from './testUtils';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
export * from './overridableComponents';
|
||||
export {
|
||||
getEntityMetadataEditUrl,
|
||||
getEntityMetadataViewUrl,
|
||||
getEntityRelations,
|
||||
getEntitySourceLocation,
|
||||
isOwnerOf,
|
||||
} from './utils';
|
||||
export type { EntitySourceLocation } from './utils';
|
||||
|
||||
@@ -17,10 +17,6 @@
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityFilter } from '../types';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated will be made private.
|
||||
*/
|
||||
export function reduceCatalogFilters(
|
||||
filters: EntityFilter[],
|
||||
): Record<string, string | symbol | (string | symbol)[]> {
|
||||
@@ -32,10 +28,6 @@ export function reduceCatalogFilters(
|
||||
}, {} as Record<string, string | symbol | (string | symbol)[]>);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated will be made private.
|
||||
*/
|
||||
export function reduceEntityFilters(
|
||||
filters: EntityFilter[],
|
||||
): (entity: Entity) => boolean {
|
||||
|
||||
@@ -33,15 +33,16 @@ import {
|
||||
attachComponentData,
|
||||
IconComponent,
|
||||
useElementFilter,
|
||||
useRouteRefParams,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityRefLinks,
|
||||
entityRouteRef,
|
||||
FavoriteEntity,
|
||||
getEntityRelations,
|
||||
InspectEntityDialog,
|
||||
UnregisterEntityDialog,
|
||||
useAsyncEntity,
|
||||
useEntityCompoundName,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Box, TabProps } from '@material-ui/core';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
@@ -176,7 +177,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
|
||||
UNSTABLE_contextMenuOptions,
|
||||
children,
|
||||
} = props;
|
||||
const { kind, namespace, name } = useEntityCompoundName();
|
||||
const { kind, namespace, name } = useRouteRefParams(entityRouteRef);
|
||||
const { entity, loading, error } = useAsyncEntity();
|
||||
const location = useLocation();
|
||||
const routes = useElementFilter(
|
||||
|
||||
@@ -14,16 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
catalogApiRef,
|
||||
useEntityCompoundName,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { useApi, useRouteRefParams } from '@backstage/core-plugin-api';
|
||||
|
||||
export function useCatalogEntity() {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const { namespace, name } = useEntityCompoundName();
|
||||
const { namespace, name } = useRouteRefParams(entityRouteRef);
|
||||
|
||||
const {
|
||||
value: entity,
|
||||
|
||||
@@ -9,8 +9,6 @@ import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { CatalogProcessor } from '@backstage/plugin-catalog-backend';
|
||||
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
import { createPullRequest } from 'octokit-plugin-create-pull-request';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import express from 'express';
|
||||
@@ -21,7 +19,6 @@ import { Knex } from 'knex';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-backend';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { Octokit } from 'octokit';
|
||||
import { PluginDatabaseManager } from '@backstage/backend-common';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
@@ -63,8 +60,6 @@ export interface CreateBuiltInActionsOptions {
|
||||
catalogClient: CatalogApi;
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// @deprecated (undocumented)
|
||||
containerRunner?: ContainerRunner;
|
||||
// (undocumented)
|
||||
integrations: ScmIntegrations;
|
||||
// (undocumented)
|
||||
@@ -99,8 +94,6 @@ export function createDebugLogAction(): TemplateAction<{
|
||||
listWorkspace?: boolean | undefined;
|
||||
}>;
|
||||
|
||||
export { createFetchCookiecutterAction };
|
||||
|
||||
// @public
|
||||
export function createFetchPlainAction(options: {
|
||||
reader: UrlReader;
|
||||
@@ -388,25 +381,6 @@ export function fetchContents({
|
||||
outputPath: string;
|
||||
}): Promise<void>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OctokitProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public @deprecated
|
||||
export class OctokitProvider {
|
||||
constructor(
|
||||
integrations: ScmIntegrationRegistry,
|
||||
githubCredentialsProvider?: GithubCredentialsProvider,
|
||||
);
|
||||
// Warning: (ae-forgotten-export) The symbol "OctokitIntegration" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @deprecated
|
||||
getOctokit(
|
||||
repoUrl: string,
|
||||
options?: {
|
||||
token?: string;
|
||||
},
|
||||
): Promise<OctokitIntegration>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface OctokitWithPullRequestPluginClient {
|
||||
// (undocumented)
|
||||
@@ -428,8 +402,6 @@ export interface RouterOptions {
|
||||
// (undocumented)
|
||||
config: Config;
|
||||
// (undocumented)
|
||||
containerRunner?: ContainerRunner;
|
||||
// (undocumented)
|
||||
database: PluginDatabaseManager;
|
||||
// (undocumented)
|
||||
logger: Logger_2;
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"@backstage/integration": "^0.8.0",
|
||||
"@backstage/plugin-catalog-backend": "^0.23.0",
|
||||
"@backstage/plugin-scaffolder-common": "^0.2.3",
|
||||
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.2.3",
|
||||
"@backstage/types": "^0.1.3",
|
||||
"@gitbeaker/core": "^34.6.0",
|
||||
"@gitbeaker/node": "^35.1.0",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContainerRunner, UrlReader } from '@backstage/backend-common';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
|
||||
import { createDebugLogAction } from './debug';
|
||||
import { createFetchPlainAction, createFetchTemplateAction } from './fetch';
|
||||
import { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
import {
|
||||
createFilesystemDeleteAction,
|
||||
createFilesystemRenameAction,
|
||||
@@ -49,7 +48,6 @@ import {
|
||||
} from './github';
|
||||
import { TemplateFilter } from '../../../lib';
|
||||
import { TemplateAction } from '../types';
|
||||
import { getRootLogger } from '@backstage/backend-common';
|
||||
|
||||
/**
|
||||
* The options passed to {@link createBuiltinActions}
|
||||
@@ -59,8 +57,6 @@ export interface CreateBuiltInActionsOptions {
|
||||
reader: UrlReader;
|
||||
integrations: ScmIntegrations;
|
||||
catalogClient: CatalogApi;
|
||||
/** @deprecated when the cookiecutter action is removed this won't be necessary */
|
||||
containerRunner?: ContainerRunner;
|
||||
config: Config;
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
}
|
||||
@@ -78,7 +74,6 @@ export const createBuiltinActions = (
|
||||
const {
|
||||
reader,
|
||||
integrations,
|
||||
containerRunner,
|
||||
catalogClient,
|
||||
config,
|
||||
additionalTemplateFilters,
|
||||
@@ -135,20 +130,5 @@ export const createBuiltinActions = (
|
||||
}),
|
||||
];
|
||||
|
||||
if (containerRunner) {
|
||||
getRootLogger().warn(
|
||||
`[DEPRECATED] The fetch:cookiecutter action will be removed part of the default scaffolder actions in later versions.
|
||||
You can install the package seperately and remove the containerRunner from the createBuiltInActions to remove this warning,
|
||||
or you can migrate to using fetch:template https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetchcookiecutter-to-fetchtemplate`,
|
||||
);
|
||||
actions.push(
|
||||
createFetchCookiecutterAction({
|
||||
reader,
|
||||
integrations,
|
||||
containerRunner,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return actions as TemplateAction<JsonObject>[];
|
||||
};
|
||||
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OctokitProvider } from './OctokitProvider';
|
||||
import {
|
||||
ScmIntegrations,
|
||||
DefaultGithubCredentialsProvider,
|
||||
} from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('getOctokit', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{ host: 'github.com', token: 'tokenlols' },
|
||||
{ host: 'ghe.github.com' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const githubCredentialsProvider =
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
|
||||
const octokitProvider = new OctokitProvider(
|
||||
integrations,
|
||||
githubCredentialsProvider,
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should throw an error when the repoUrl is not well formed', async () => {
|
||||
await expect(
|
||||
octokitProvider.getOctokit('github.com?repo=bob'),
|
||||
).rejects.toThrow(/missing owner/);
|
||||
|
||||
await expect(
|
||||
octokitProvider.getOctokit('github.com?owner=owner'),
|
||||
).rejects.toThrow(/missing repo/);
|
||||
});
|
||||
|
||||
it('should throw if there is no integration config provided', async () => {
|
||||
await expect(
|
||||
octokitProvider.getOctokit('missing.com?repo=bob&owner=owner'),
|
||||
).rejects.toThrow(/No matching integration configuration/);
|
||||
});
|
||||
|
||||
it('should throw if there is no token in the integration config that is returned', async () => {
|
||||
await expect(
|
||||
octokitProvider.getOctokit('ghe.github.com?repo=bob&owner=owner'),
|
||||
).rejects.toThrow(/No token available for host/);
|
||||
});
|
||||
|
||||
it('should return proper Octokit', async () => {
|
||||
const { client, token, owner, repo } = await octokitProvider.getOctokit(
|
||||
'github.com?repo=bob&owner=owner',
|
||||
);
|
||||
expect(client).toBeDefined();
|
||||
expect(token).toBe('tokenlols');
|
||||
expect(owner).toBe('owner');
|
||||
expect(repo).toBe('bob');
|
||||
});
|
||||
|
||||
it('should return an octokit client with the passed in token if it is provided', async () => {
|
||||
const { client, token, owner, repo } = await octokitProvider.getOctokit(
|
||||
'github.com?repo=bob&owner=owner',
|
||||
{ token: 'tokenlols2' },
|
||||
);
|
||||
expect(client).toBeDefined();
|
||||
expect(token).toBe('tokenlols2');
|
||||
expect(owner).toBe('owner');
|
||||
expect(repo).toBe('bob');
|
||||
});
|
||||
});
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import {
|
||||
DefaultGithubCredentialsProvider,
|
||||
GithubCredentialsProvider,
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { Octokit } from 'octokit';
|
||||
import { parseRepoUrl } from '../publish/util';
|
||||
|
||||
export type OctokitIntegration = {
|
||||
client: Octokit;
|
||||
token: string;
|
||||
owner: string;
|
||||
repo: string;
|
||||
};
|
||||
/**
|
||||
* OctokitProvider provides Octokit client based on ScmIntegrationsRegistry configuration.
|
||||
* OctokitProvider supports GitHub credentials caching out of the box.
|
||||
*
|
||||
* @deprecated we are no longer providing a way from the scaffolder to generate octokit instances.
|
||||
* Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead
|
||||
*/
|
||||
export class OctokitProvider {
|
||||
private readonly integrations: ScmIntegrationRegistry;
|
||||
private readonly githubCredentialsProvider: GithubCredentialsProvider;
|
||||
|
||||
constructor(
|
||||
integrations: ScmIntegrationRegistry,
|
||||
githubCredentialsProvider?: GithubCredentialsProvider,
|
||||
) {
|
||||
this.integrations = integrations;
|
||||
this.githubCredentialsProvider =
|
||||
githubCredentialsProvider ||
|
||||
DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets standard Octokit client based on repository URL.
|
||||
*
|
||||
* @param repoUrl - Repository URL
|
||||
*
|
||||
* @deprecated we are no longer providing a way from the scaffolder to generate octokit instances.
|
||||
* Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead
|
||||
*/
|
||||
async getOctokit(
|
||||
repoUrl: string,
|
||||
options?: { token?: string },
|
||||
): Promise<OctokitIntegration> {
|
||||
const { owner, repo, host } = parseRepoUrl(repoUrl, this.integrations);
|
||||
|
||||
if (!owner) {
|
||||
throw new InputError(`No owner provided for repo ${repoUrl}`);
|
||||
}
|
||||
|
||||
const integrationConfig = this.integrations.github.byHost(host)?.config;
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(`No integration for host ${host}`);
|
||||
}
|
||||
|
||||
// Short circuit the internal Github Token provider the token provided
|
||||
// by the action or the caller.
|
||||
if (options?.token) {
|
||||
const client = new Octokit({
|
||||
auth: options.token,
|
||||
baseUrl: integrationConfig.apiBaseUrl,
|
||||
previews: ['nebula-preview'],
|
||||
});
|
||||
|
||||
return { client, token: options.token, owner, repo };
|
||||
}
|
||||
|
||||
// TODO(blam): Consider changing this API to have owner, repoo interface instead of URL as the it's
|
||||
// needless to create URL and then parse again the other side.
|
||||
const { token } = await this.githubCredentialsProvider.getCredentials({
|
||||
url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
|
||||
repo,
|
||||
)}`,
|
||||
});
|
||||
|
||||
if (!token) {
|
||||
throw new InputError(
|
||||
`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`,
|
||||
);
|
||||
}
|
||||
|
||||
const client = new Octokit({
|
||||
auth: token,
|
||||
baseUrl: integrationConfig.apiBaseUrl,
|
||||
previews: ['nebula-preview'],
|
||||
});
|
||||
|
||||
return { client, token, owner, repo };
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,3 @@
|
||||
|
||||
export { createGithubActionsDispatchAction } from './githubActionsDispatch';
|
||||
export { createGithubWebhookAction } from './githubWebhook';
|
||||
export { OctokitProvider } from './OctokitProvider';
|
||||
|
||||
@@ -22,8 +22,5 @@ export * from './filesystem';
|
||||
export * from './publish';
|
||||
export * from './github';
|
||||
|
||||
/** @deprecated please add this package to your own installation manually */
|
||||
export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter';
|
||||
|
||||
export { runCommand, executeShellCommand } from './helpers';
|
||||
export type { RunCommandOptions } from './helpers';
|
||||
|
||||
@@ -30,7 +30,6 @@ jest.doMock('fs-extra', () => ({
|
||||
|
||||
import {
|
||||
DatabaseManager,
|
||||
DockerContainerRunner,
|
||||
getVoidLogger,
|
||||
PluginDatabaseManager,
|
||||
UrlReaders,
|
||||
@@ -122,7 +121,6 @@ describe('createRouter', () => {
|
||||
config: new ConfigReader({}),
|
||||
database: createDatabase(),
|
||||
catalogClient: createCatalogClient(template),
|
||||
containerRunner: new DockerContainerRunner({} as any),
|
||||
reader: mockUrlReader,
|
||||
taskBroker,
|
||||
});
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
ContainerRunner,
|
||||
PluginDatabaseManager,
|
||||
UrlReader,
|
||||
} from '@backstage/backend-common';
|
||||
import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
@@ -61,7 +57,6 @@ export interface RouterOptions {
|
||||
catalogClient: CatalogApi;
|
||||
actions?: TemplateAction<any>[];
|
||||
taskWorkers?: number;
|
||||
containerRunner?: ContainerRunner;
|
||||
taskBroker?: TaskBroker;
|
||||
additionalTemplateFilters?: Record<string, TemplateFilter>;
|
||||
}
|
||||
@@ -89,7 +84,6 @@ export async function createRouter(
|
||||
database,
|
||||
catalogClient,
|
||||
actions,
|
||||
containerRunner,
|
||||
taskWorkers,
|
||||
additionalTemplateFilters,
|
||||
} = options;
|
||||
@@ -128,7 +122,6 @@ export async function createRouter(
|
||||
: createBuiltinActions({
|
||||
integrations,
|
||||
catalogClient,
|
||||
containerRunner,
|
||||
reader,
|
||||
config,
|
||||
additionalTemplateFilters,
|
||||
|
||||
@@ -314,6 +314,14 @@ export interface ScaffolderGetIntegrationsListResponse {
|
||||
}[];
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ScaffolderOutputLink = {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
url?: string;
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ScaffolderPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -372,12 +380,8 @@ export type ScaffolderTask = {
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ScaffolderTaskOutput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type ScaffolderTaskOutput = {
|
||||
entityRef?: string;
|
||||
remoteUrl?: string;
|
||||
links?: ScaffolderOutputLink[];
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
@@ -451,8 +455,4 @@ export const TemplateTypePicker: () => JSX.Element | null;
|
||||
|
||||
// @public
|
||||
export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/types.d.ts:32:5 - (ae-forgotten-export) The symbol "ScaffolderOutputLink" needs to be exported by the entry point index.d.ts
|
||||
```
|
||||
|
||||
@@ -217,12 +217,8 @@ export const TaskStatusStepper = memo(
|
||||
},
|
||||
);
|
||||
|
||||
const hasLinks = ({
|
||||
entityRef,
|
||||
remoteUrl,
|
||||
links = [],
|
||||
}: ScaffolderTaskOutput): boolean =>
|
||||
!!(entityRef || remoteUrl || links.length > 0);
|
||||
const hasLinks = ({ links = [] }: ScaffolderTaskOutput): boolean =>
|
||||
links.length > 0;
|
||||
|
||||
/**
|
||||
* TaskPageProps for constructing a TaskPage
|
||||
|
||||
@@ -26,43 +26,6 @@ describe('TaskPageLinks', () => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('renders the entityRef link', async () => {
|
||||
const output = { entityRef: 'Component:default/my-app' };
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TaskPageLinks output={output} />,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const element = await findByText('Open in catalog');
|
||||
|
||||
expect(element).toBeInTheDocument();
|
||||
expect(element).toHaveAttribute(
|
||||
'href',
|
||||
'/catalog/default/Component/my-app',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders the remoteUrl link', async () => {
|
||||
const output = { remoteUrl: 'https://remote.url' };
|
||||
const { findByText } = await renderInTestApp(
|
||||
<TaskPageLinks output={output} />,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const element = await findByText('Repo');
|
||||
|
||||
expect(element).toBeInTheDocument();
|
||||
expect(element).toHaveAttribute('href', 'https://remote.url');
|
||||
});
|
||||
|
||||
it('renders further links', async () => {
|
||||
const output = {
|
||||
links: [
|
||||
|
||||
@@ -28,29 +28,13 @@ type TaskPageLinksProps = {
|
||||
};
|
||||
|
||||
export const TaskPageLinks = ({ output }: TaskPageLinksProps) => {
|
||||
const { entityRef: entityRefOutput, remoteUrl } = output;
|
||||
let { links = [] } = output;
|
||||
const { links = [] } = output;
|
||||
const app = useApp();
|
||||
const entityRoute = useRouteRef(entityRouteRef);
|
||||
|
||||
const iconResolver = (key?: string): IconComponent =>
|
||||
key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon;
|
||||
|
||||
if (remoteUrl) {
|
||||
links = [{ url: remoteUrl, title: 'Repo' }, ...links];
|
||||
}
|
||||
|
||||
if (entityRefOutput) {
|
||||
links = [
|
||||
{
|
||||
entityRef: entityRefOutput,
|
||||
title: 'Open in catalog',
|
||||
icon: 'catalog',
|
||||
},
|
||||
...links,
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<Box px={3} pb={3}>
|
||||
{links
|
||||
|
||||
@@ -13,14 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { GetEntitiesResponse } from '@backstage/catalog-client';
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
humanizeEntityRef,
|
||||
useOwnedEntities,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { TextField } from '@material-ui/core';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
|
||||
import { FieldExtensionComponentProps } from '../../../extensions';
|
||||
|
||||
@@ -86,3 +90,45 @@ export const OwnedEntityPicker = (
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes the relevant parts of the Backstage identity, and translates them into
|
||||
* a list of entities which are owned by the user. Takes an optional parameter
|
||||
* to filter the entities based on allowedKinds
|
||||
*
|
||||
*
|
||||
* @param allowedKinds - Array of allowed kinds to filter the entities
|
||||
*/
|
||||
function useOwnedEntities(allowedKinds?: string[]): {
|
||||
loading: boolean;
|
||||
ownedEntities: GetEntitiesResponse | undefined;
|
||||
} {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const { loading, value: refs } = useAsync(async () => {
|
||||
const identity = await identityApi.getBackstageIdentity();
|
||||
const identityRefs = identity.ownershipEntityRefs;
|
||||
const catalogs = await catalogApi.getEntities(
|
||||
allowedKinds
|
||||
? {
|
||||
filter: {
|
||||
kind: allowedKinds,
|
||||
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
|
||||
},
|
||||
}
|
||||
: {
|
||||
filter: {
|
||||
[`relations.${RELATION_OWNED_BY}`]: identityRefs || [],
|
||||
},
|
||||
},
|
||||
);
|
||||
return catalogs;
|
||||
}, []);
|
||||
|
||||
const ownedEntities = useMemo(() => {
|
||||
return refs;
|
||||
}, [refs]);
|
||||
|
||||
return useMemo(() => ({ loading, ownedEntities }), [loading, ownedEntities]);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export type {
|
||||
ScaffolderApi,
|
||||
ScaffolderGetIntegrationsListOptions,
|
||||
ScaffolderGetIntegrationsListResponse,
|
||||
ScaffolderOutputLink,
|
||||
ScaffolderScaffoldOptions,
|
||||
ScaffolderScaffoldResponse,
|
||||
ScaffolderStreamLogsOptions,
|
||||
|
||||
@@ -43,18 +43,16 @@ export type ListActionsResponse = Array<{
|
||||
};
|
||||
}>;
|
||||
|
||||
type ScaffolderOutputLink = {
|
||||
/** @public */
|
||||
export type ScaffolderOutputLink = {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
url?: string;
|
||||
entityRef?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type ScaffolderTaskOutput = {
|
||||
/** @deprecated use the `links` property to link out to relevant resources */
|
||||
entityRef?: string;
|
||||
/** @deprecated use the `links` property to link out to relevant resources */
|
||||
remoteUrl?: string;
|
||||
links?: ScaffolderOutputLink[];
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
|
||||
Reference in New Issue
Block a user