Merge remote-tracking branch 'origin/master' into shutdown-tasks-on-restart

This commit is contained in:
Bogdan Nechyporenko
2023-10-26 19:13:02 +02:00
56 changed files with 328 additions and 126 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog-backend': minor
'@backstage/plugin-catalog-node': minor
---
Support adding location analyzers in new catalog analysis extension point and move `AnalyzeOptions` and `ScmLocationAnalyzer` types to `@backstage/plugin-catalog-node`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': patch
---
Fix bug where `retrieveAll` method wasn't fetching visits
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend-module-microsoft-provider': patch
---
Added support for specifying a domain hint on the Microsoft authentication provider configuration.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-github': patch
---
Import `AnalyzeOptions` and `ScmLocationAnalyzer` types from `@backstage/plugin-catalog-node`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-react': patch
---
Minor refactor of search bar analytics capture
@@ -27,7 +27,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
with:
# Fetch the commit that's merged into the base rather than the target ref
# This will let us diff only the contents of the PR, without fetching more history
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
with:
ref: '${{ github.event.pull_request.merge_commit_sha }}'
+6 -6
View File
@@ -31,10 +31,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -67,10 +67,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -196,12 +196,12 @@ jobs:
INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }}
steps:
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: fetch branch master
run: git fetch origin master
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -19,13 +19,13 @@ jobs:
egress-policy: audit
- name: checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
path: backstage
ref: v${{ github.event.client_payload.version }}
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -28,10 +28,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js 18.x
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -19,10 +19,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: use node.js 18.x
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3.8.2
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
+4 -4
View File
@@ -60,10 +60,10 @@ jobs:
INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }}
steps:
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -142,10 +142,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
egress-policy: audit
- name: 'Checkout code'
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
persist-credentials: false
+2 -2
View File
@@ -14,13 +14,13 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
# Fetch changes to previous commit - required for 'only_changed' in Prettier action
fetch-depth: 0
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -16,7 +16,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
fetch-depth: 2
ref: ${{ github.head_ref }}
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
egress-policy: audit
# Setup node & install deps before checkout, keeping install quick
- uses: actions/setup-node@v3.8.1
- uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
- name: Install dependencies
@@ -21,7 +21,7 @@ jobs:
run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
path: backstage
# 'v' prefix is added here for the tag, we keep it out of the manifest logic
@@ -29,7 +29,7 @@ jobs:
# Checkout backstage/versions into /backstage/versions, which is where store the output
- name: Checkout versions
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
repository: backstage/versions
path: backstage/versions
@@ -16,7 +16,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
fetch-depth: 2
ref: ${{ github.head_ref }}
@@ -16,10 +16,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js 18.x
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/ # Needed for auth
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: Monitor and Synchronize Snyk Policies
uses: snyk/actions/node@39091e69b560da335383b404e50d65b408f4f812 # master
with:
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 20000
fetch-tags: true
+3 -3
View File
@@ -25,10 +25,10 @@ jobs:
egress-policy: audit
- name: checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
- name: setup-node
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
registry-url: https://registry.npmjs.org/
@@ -87,7 +87,7 @@ jobs:
egress-policy: audit
- name: Checkout git repo
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
- name: Render Compose File
run: |
BACKSTAGE_IMAGE=$(echo ${{ needs.build-backstage.outputs.tags }})
+2 -2
View File
@@ -24,9 +24,9 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: Use Node.js 18.x
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
- name: yarn install
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
# Vale does not support file excludes, so we use the script to generate a list of files instead
# The action also does not allow args or a local config file to be passed in, so the files array
+2 -2
View File
@@ -26,10 +26,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -44,7 +44,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: Configure Git
run: |
@@ -52,7 +52,7 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -34,8 +34,8 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/setup-python@v4.7.0
- uses: actions/checkout@v4.1.1
- uses: actions/setup-python@v4.7.1
with:
python-version: '3.9'
+3 -3
View File
@@ -42,7 +42,7 @@ jobs:
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: Configure Git
run: |
@@ -50,13 +50,13 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: setup python
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v4.7.1
with:
python-version: '3.10'
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
egress-policy: audit
- name: Checkout
uses: actions/checkout@v3.6.0
uses: actions/checkout@v4.1.1
- name: Install Fossa
run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash"
+2 -2
View File
@@ -28,10 +28,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js 18.x
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: 18.x
+2 -2
View File
@@ -32,12 +32,12 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0 # Required to retrieve git history
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+2 -2
View File
@@ -33,10 +33,10 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@v3.6.0
- uses: actions/checkout@v4.1.1
- name: use node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3.8.1
uses: actions/setup-node@v3.8.2
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
+6 -1
View File
@@ -38,13 +38,18 @@ auth:
clientId: ${AUTH_MICROSOFT_CLIENT_ID}
clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET}
tenantId: ${AUTH_MICROSOFT_TENANT_ID}
domainHint: ${AZURE_TENANT_ID}
```
The Microsoft provider is a structure with three configuration keys:
The Microsoft provider is a structure with three mandatory configuration keys:
- `clientId`: Application (client) ID, found on App Registration > Overview
- `clientSecret`: Secret, found on App Registration > Certificates & secrets
- `tenantId`: Directory (tenant) ID, found on App Registration > Overview
- `domainHint` (optional): Typically the same as `tenantId`.
Leave blank if your app registration is multi tenant.
When specified, this reduces login friction for users with accounts in multiple tenants by automatically filtering away accounts from other tenants.
For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy)
## Outbound Network Access
-2
View File
@@ -4,8 +4,6 @@ title: Migration to Yarn 3
description: Guide for how to migrate a Backstage project to use Yarn 3
---
> NOTE: We do not yet recommend all projects to migrate to Yarn 3. Only do so if you have specific reasons for it.
While Backstage projects created with `@backstage/create-app` use [Yarn 1](https://classic.yarnpkg.com/) by default, it
is possible to switch them to instead use [Yarn 3](https://yarnpkg.com/). Tools like `yarn backstage-cli versions:bump` will
still work, as they recognize both lockfile formats.
@@ -24,8 +24,8 @@ export class CommonJSModuleLoader implements ModuleLoader {
backstageRoot: string,
dynamicPluginsPaths: string[],
): Promise<void> {
const allowedNodeModulesPaths = [
`${backstageRoot}/node_modules`,
const backstageRootNodeModulesPath = `${backstageRoot}/node_modules`;
const dynamicNodeModulesPaths = [
...dynamicPluginsPaths.map(p => path.resolve(p, 'node_modules')),
];
const Module = require('module');
@@ -35,8 +35,12 @@ export class CommonJSModuleLoader implements ModuleLoader {
if (!dynamicPluginsPaths.some(p => from.startsWith(p))) {
return result;
}
const filtered = result.filter(p => allowedNodeModulesPaths.includes(p));
const filtered = result.filter(nodeModulePath => {
return (
nodeModulePath === backstageRootNodeModulesPath ||
dynamicNodeModulesPaths.some(p => nodeModulePath.startsWith(p))
);
});
this.logger.debug(
`Overriding node_modules search path for dynamic plugin ${from} to: ${filtered}`,
);
@@ -15,7 +15,10 @@ export const authModuleMicrosoftProvider: () => BackendFeature;
// @public (undocumented)
export const microsoftAuthenticator: OAuthAuthenticator<
PassportOAuthAuthenticatorHelper,
{
helper: PassportOAuthAuthenticatorHelper;
domainHint: string | undefined;
},
PassportProfile
>;
@@ -26,6 +26,7 @@ export interface Config {
* @visibility secret
*/
clientSecret: string;
domainHint?: string;
callbackUrl?: string;
};
};
@@ -30,8 +30,9 @@ export const microsoftAuthenticator = createOAuthAuthenticator({
const clientId = config.getString('clientId');
const clientSecret = config.getString('clientSecret');
const tenantId = config.getString('tenantId');
const domainHint = config.getOptionalString('domainHint');
return PassportOAuthAuthenticatorHelper.from(
const helper = PassportOAuthAuthenticatorHelper.from(
new MicrosoftStrategy(
{
clientID: clientId,
@@ -55,19 +56,30 @@ export const microsoftAuthenticator = createOAuthAuthenticator({
},
),
);
return {
helper,
domainHint,
};
},
async start(input, helper) {
return helper.start(input, {
async start(input, ctx) {
const options: Record<string, string> = {
accessType: 'offline',
});
};
if (ctx.domainHint !== undefined) {
options.domain_hint = ctx.domainHint;
}
return ctx.helper.start(input, options);
},
async authenticate(input, helper) {
return helper.authenticate(input);
async authenticate(input, ctx) {
return ctx.helper.authenticate(input);
},
async refresh(input, helper) {
return helper.refresh(input);
async refresh(input, ctx) {
return ctx.helper.refresh(input);
},
});
@@ -21,7 +21,7 @@ import request from 'supertest';
import { authModuleMicrosoftProvider } from './module';
describe('authModuleMicrosoftProvider', () => {
it('should start', async () => {
it('should start without domain hint', async () => {
const { server } = await startTestBackend({
features: [
authPlugin,
@@ -77,4 +77,63 @@ describe('authModuleMicrosoftProvider', () => {
nonce: decodeURIComponent(nonceCookie.value),
});
});
it('should start with domain hint', async () => {
const { server } = await startTestBackend({
features: [
authPlugin,
authModuleMicrosoftProvider,
mockServices.rootConfig.factory({
data: {
app: {
baseUrl: 'http://localhost:3000',
},
auth: {
providers: {
microsoft: {
development: {
clientId: 'another-client-id',
clientSecret: 'another-client-secret',
tenantId: 'another-tenant-id',
domainHint: 'somedomain',
},
},
},
},
},
}),
],
});
const agent = request.agent(server);
const res = await agent.get('/api/auth/microsoft/start?env=development');
expect(res.status).toEqual(302);
const nonceCookie = agent.jar.getCookie('microsoft-nonce', {
domain: 'localhost',
path: '/api/auth/microsoft/handler',
script: false,
secure: false,
});
expect(nonceCookie).toBeDefined();
const startUrl = new URL(res.get('location'));
expect(startUrl.origin).toBe('https://login.microsoftonline.com');
expect(startUrl.pathname).toBe('/another-tenant-id/oauth2/v2.0/authorize');
expect(Object.fromEntries(startUrl.searchParams)).toEqual({
response_type: 'code',
scope: 'user.read',
client_id: 'another-client-id',
redirect_uri: `http://localhost:${server.port()}/api/auth/microsoft/handler/frame`,
state: expect.any(String),
domain_hint: 'somedomain',
});
expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({
env: 'development',
nonce: decodeURIComponent(nonceCookie.value),
});
});
});
@@ -3,7 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AnalyzeOptions } from '@backstage/plugin-catalog-backend';
import { AnalyzeOptions } from '@backstage/plugin-catalog-node';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
@@ -21,7 +21,7 @@ import { Logger } from 'winston';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend';
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node';
import { TaskRunner } from '@backstage/backend-tasks';
import { TokenManager } from '@backstage/backend-common';
import { UserEntity } from '@backstage/catalog-model';
@@ -27,7 +27,7 @@ import parseGitUrl from 'git-url-parse';
import {
AnalyzeOptions,
ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-backend';
} from '@backstage/plugin-catalog-node';
import {
PluginEndpointDiscovery,
TokenManager,
+7 -13
View File
@@ -10,6 +10,7 @@ import { AnalyzeLocationExistingEntity as AnalyzeLocationExistingEntity_2 } from
import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeOptions as AnalyzeOptions_2 } from '@backstage/plugin-catalog-node';
import { CatalogApi } from '@backstage/catalog-client';
import type { CatalogCollatorEntityTransformer as CatalogCollatorEntityTransformer_2 } from '@backstage/plugin-search-backend-module-catalog';
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
@@ -50,6 +51,7 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { Router } from 'express';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
import { TokenManager } from '@backstage/backend-common';
import { UrlReader } from '@backstage/backend-common';
import { Validators } from '@backstage/catalog-model';
@@ -69,11 +71,8 @@ export type AnalyzeLocationRequest = AnalyzeLocationRequest_2;
// @public @deprecated (undocumented)
export type AnalyzeLocationResponse = AnalyzeLocationResponse_2;
// @public (undocumented)
export type AnalyzeOptions = {
url: string;
catalogFilename?: string;
};
// @public @deprecated (undocumented)
export type AnalyzeOptions = AnalyzeOptions_2;
// @public (undocumented)
export class AnnotateLocationEntityProcessor implements CatalogProcessor_2 {
@@ -134,7 +133,7 @@ export class CatalogBuilder {
...providers: Array<EntityProvider_2 | Array<EntityProvider_2>>
): CatalogBuilder;
addLocationAnalyzers(
...analyzers: Array<ScmLocationAnalyzer | Array<ScmLocationAnalyzer>>
...analyzers: Array<ScmLocationAnalyzer_2 | Array<ScmLocationAnalyzer_2>>
): CatalogBuilder;
addPermissionRules(
...permissionRules: Array<
@@ -468,13 +467,8 @@ export const processingResult: Readonly<{
readonly refresh: (key: string) => CatalogProcessorResult_2;
}>;
// @public (undocumented)
export type ScmLocationAnalyzer = {
supports(url: string): boolean;
analyze(options: AnalyzeOptions): Promise<{
existing: AnalyzeLocationExistingEntity[];
}>;
};
// @public @deprecated (undocumented)
export type ScmLocationAnalyzer = ScmLocationAnalyzer_2;
// @public (undocumented)
export class UrlReaderProcessor implements CatalogProcessor_2 {
+12
View File
@@ -33,6 +33,8 @@ import {
type EntityProvider as _EntityProvider,
type EntityProviderConnection as _EntityProviderConnection,
type EntityProviderMutation as _EntityProviderMutation,
type AnalyzeOptions as _AnalyzeOptions,
type ScmLocationAnalyzer as _ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
import { type LocationSpec as _LocationSpec } from '@backstage/plugin-catalog-common';
@@ -141,3 +143,13 @@ export type EntityProviderMutation = _EntityProviderMutation;
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type LocationSpec = _LocationSpec;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type AnalyzeOptions = _AnalyzeOptions;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type ScmLocationAnalyzer = _ScmLocationAnalyzer;
@@ -18,11 +18,12 @@ import { Logger } from 'winston';
import parseGitUrl from 'git-url-parse';
import { Entity } from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { LocationAnalyzer, ScmLocationAnalyzer } from './types';
import { LocationAnalyzer } from './types';
import {
AnalyzeLocationRequest,
AnalyzeLocationResponse,
} from '@backstage/plugin-catalog-common';
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node';
export class RepoLocationAnalyzer implements LocationAnalyzer {
private readonly logger: Logger;
@@ -21,6 +21,4 @@ export type {
AnalyzeLocationRequest,
AnalyzeLocationResponse,
LocationAnalyzer,
ScmLocationAnalyzer,
AnalyzeOptions,
} from './types';
@@ -78,19 +78,3 @@ export type LocationAnalyzer = {
location: AnalyzeLocationRequest,
): Promise<AnalyzeLocationResponse>;
};
/** @public */
export type AnalyzeOptions = {
url: string;
catalogFilename?: string;
};
/** @public */
export type ScmLocationAnalyzer = {
/** The method that decides if this analyzer can work with the provided url */
supports(url: string): boolean;
/** This function can return an array of already existing entities */
analyze(options: AnalyzeOptions): Promise<{
/** Existing entities in the analyzed location */
existing: AnalyzeLocationExistingEntity[];
}>;
};
@@ -38,6 +38,7 @@ import {
CatalogProcessor,
CatalogProcessorParser,
EntityProvider,
ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
import {
AnnotateLocationEntityProcessor,
@@ -57,7 +58,7 @@ import {
yamlPlaceholderResolver,
} from '../modules/core/PlaceholderProcessor';
import { defaultEntityDataParser } from '../modules/util/parse';
import { LocationAnalyzer, ScmLocationAnalyzer } from '../ingestion/types';
import { LocationAnalyzer } from '../ingestion/types';
import { CatalogProcessingEngine } from '../processing';
import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase';
import { applyDatabaseMigrations } from '../database/migrations';
@@ -19,17 +19,22 @@ import {
} from '@backstage/backend-plugin-api';
import { CatalogBuilder } from './CatalogBuilder';
import {
CatalogAnalysisExtensionPoint,
catalogAnalysisExtensionPoint,
CatalogProcessingExtensionPoint,
catalogProcessingExtensionPoint,
} from '@backstage/plugin-catalog-node/alpha';
import {
CatalogProcessor,
EntityProvider,
ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
import { loggerToWinstonLogger } from '@backstage/backend-common';
import { PlaceholderResolver } from '../modules';
class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint {
class CatalogProcessingExtensionPointImpl
implements CatalogProcessingExtensionPoint
{
#processors = new Array<CatalogProcessor>();
#entityProviders = new Array<EntityProvider>();
#placeholderResolvers: Record<string, PlaceholderResolver> = {};
@@ -67,6 +72,20 @@ class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint {
}
}
class CatalogAnalysisExtensionPointImpl
implements CatalogAnalysisExtensionPoint
{
#locationAnalyzers = new Array<ScmLocationAnalyzer>();
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void {
this.#locationAnalyzers.push(analyzer);
}
get locationAnalyzers() {
return this.#locationAnalyzers;
}
}
/**
* Catalog plugin
* @alpha
@@ -74,13 +93,19 @@ class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint {
export const catalogPlugin = createBackendPlugin({
pluginId: 'catalog',
register(env) {
const processingExtensions = new CatalogExtensionPointImpl();
const processingExtensions = new CatalogProcessingExtensionPointImpl();
// plugins depending on this API will be initialized before this plugins init method is executed.
env.registerExtensionPoint(
catalogProcessingExtensionPoint,
processingExtensions,
);
const analysisExtensions = new CatalogAnalysisExtensionPointImpl();
env.registerExtensionPoint(
catalogAnalysisExtensionPoint,
analysisExtensions,
);
env.registerInit({
deps: {
logger: coreServices.logger,
@@ -116,6 +141,7 @@ export const catalogPlugin = createBackendPlugin({
Object.entries(processingExtensions.placeholderResolvers).forEach(
([key, resolver]) => builder.setPlaceholderResolver(key, resolver),
);
builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers);
const { processingEngine, router } = await builder.build();
+10
View File
@@ -8,8 +8,18 @@ import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { EntityProvider } from '@backstage/plugin-catalog-node';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { PlaceholderResolver } from '@backstage/plugin-catalog-node';
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node';
import { ServiceRef } from '@backstage/backend-plugin-api';
// @alpha (undocumented)
export interface CatalogAnalysisExtensionPoint {
// (undocumented)
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
}
// @alpha (undocumented)
export const catalogAnalysisExtensionPoint: ExtensionPoint<CatalogAnalysisExtensionPoint>;
// @alpha (undocumented)
export interface CatalogProcessingExtensionPoint {
// (undocumented)
+15
View File
@@ -5,12 +5,19 @@
```ts
/// <reference types="node" />
import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common';
import { CompoundEntityRef } from '@backstage/catalog-model';
import { Entity } from '@backstage/catalog-model';
import { JsonValue } from '@backstage/types';
import { LocationEntityV1alpha1 } from '@backstage/catalog-model';
import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common';
// @public (undocumented)
export type AnalyzeOptions = {
url: string;
catalogFilename?: string;
};
// @public (undocumented)
export type CatalogProcessor = {
getProcessorName(): string;
@@ -198,4 +205,12 @@ export const processingResult: Readonly<{
readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult;
readonly refresh: (key: string) => CatalogProcessorResult;
}>;
// @public (undocumented)
export type ScmLocationAnalyzer = {
supports(url: string): boolean;
analyze(options: AnalyzeOptions): Promise<{
existing: AnalyzeLocationExistingEntity[];
}>;
};
```
+2
View File
@@ -17,3 +17,5 @@
export { catalogServiceRef } from './catalogService';
export type { CatalogProcessingExtensionPoint } from './extensions';
export { catalogProcessingExtensionPoint } from './extensions';
export type { CatalogAnalysisExtensionPoint } from './extensions';
export { catalogAnalysisExtensionPoint } from './extensions';
+16
View File
@@ -18,6 +18,7 @@ import {
EntityProvider,
CatalogProcessor,
PlaceholderResolver,
ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
/**
@@ -40,3 +41,18 @@ export const catalogProcessingExtensionPoint =
createExtensionPoint<CatalogProcessingExtensionPoint>({
id: 'catalog.processing',
});
/**
* @alpha
*/
export interface CatalogAnalysisExtensionPoint {
addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void;
}
/**
* @alpha
*/
export const catalogAnalysisExtensionPoint =
createExtensionPoint<CatalogAnalysisExtensionPoint>({
id: 'catalog.analysis',
});
@@ -15,9 +15,11 @@
*/
export type {
AnalyzeOptions,
DeferredEntity,
PlaceholderResolver,
PlaceholderResolverParams,
PlaceholderResolverRead,
PlaceholderResolverResolveUrl,
ScmLocationAnalyzer,
} from './types';
@@ -15,6 +15,7 @@
*/
import { Entity } from '@backstage/catalog-model';
import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common';
import { JsonValue } from '@backstage/types';
import { CatalogProcessorEmit } from '../api';
@@ -50,3 +51,20 @@ export type PlaceholderResolverParams = {
export type PlaceholderResolver = (
params: PlaceholderResolverParams,
) => Promise<JsonValue>;
/** @public */
export type AnalyzeOptions = {
url: string;
catalogFilename?: string;
};
/** @public */
export type ScmLocationAnalyzer = {
/** The method that decides if this analyzer can work with the provided url */
supports(url: string): boolean;
/** This function can return an array of already existing entities */
analyze(options: AnalyzeOptions): Promise<{
/** Existing entities in the analyzed location */
existing: AnalyzeLocationExistingEntity[];
}>;
};
+27 -11
View File
@@ -117,23 +117,39 @@ export class VisitsStorageApi implements VisitsApi {
}
private async persistAll(visits: Array<Visit>) {
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
const storageKey = await this.getStorageKey();
return this.storageApi.set<Array<Visit>>(storageKey, visits);
}
private async retrieveAll(): Promise<Array<Visit>> {
const storageKey = await this.getStorageKey();
// Handles for case when snapshot is and is not referenced per storaged type used
const snapshot = this.storageApi.snapshot<Array<Visit>>(storageKey);
if (snapshot?.presence !== 'unknown') {
return snapshot?.value ?? [];
}
return new Promise((resolve, reject) => {
const subsription = this.storageApi
.observe$<Visit[]>(storageKey)
.subscribe({
next: next => {
const visits = next.value ?? [];
subsription.unsubscribe();
resolve(visits);
},
error: err => {
subsription.unsubscribe();
reject(err);
},
});
});
}
private async getStorageKey(): Promise<string> {
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
const storageKey = `${this.storageKeyPrefix}:${userEntityRef}`;
let visits: Array<Visit>;
try {
visits = this.storageApi.snapshot<Array<Visit>>(storageKey).value ?? [];
} catch {
visits = [];
}
return visits;
return storageKey;
}
// This assumes Visit fields are either numbers or strings
@@ -19,23 +19,22 @@ import { useAnalytics } from '@backstage/core-plugin-api';
import { useSearch } from '../../context';
import usePrevious from 'react-use/lib/usePrevious';
function useFallingEdge(next: boolean) {
const prev = usePrevious(next);
return prev && !next;
}
/**
* Capture search event on term change.
*/
export const TrackSearch = ({ children }: { children: React.ReactChild }) => {
const useHasChanged = (value: any) => {
const previousVal = usePrevious(value);
return previousVal !== value;
};
const analytics = useAnalytics();
const { term, result } = useSearch();
const numberOfResults = result.value?.numberOfResults ?? undefined;
// Stops the analtyics event from firing before the new search engine response is returned
const hasStartedLoading = useHasChanged(result.loading);
const hasFinishedLoading = hasStartedLoading && !result.loading;
const hasFinishedLoading = useFallingEdge(result.loading);
useEffect(() => {
if (term && hasFinishedLoading) {