Merge remote-tracking branch 'upstream/master' into expose-reading-msgraph-provider-config

This commit is contained in:
Marc Rooding
2023-11-06 18:03:52 +01:00
61 changed files with 692 additions and 321 deletions
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/create-app': patch
---
`knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support.
You can do the same in your own Backstage repository to ensure that you get future node 18+ relevant updates, by having the following lines in your `packages/backend/package.json`:
```
"dependencies": {
// ...
"knex": "^3.0.0"
},
"devDependencies": {
// ...
"better-sqlite3": "^9.0.0",
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-bazaar': patch
---
Updated Readme document in bazaar plugin
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-shortcuts': patch
---
Ensure that shortcuts aren't duplicate-checked against themselves
+24
View File
@@ -0,0 +1,24 @@
---
'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
'@backstage/plugin-catalog-backend-module-unprocessed': patch
'@backstage/plugin-search-backend-module-pg': patch
'@backstage/plugin-entity-feedback-backend': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-tech-insights-backend': patch
'@backstage/plugin-user-settings-backend': patch
'@backstage/backend-plugin-api': patch
'@backstage/backend-test-utils': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-linguist-backend': patch
'@backstage/plugin-playlist-backend': patch
'@backstage/plugin-techdocs-backend': patch
'@backstage/backend-common': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/backend-tasks': patch
'@backstage/plugin-badges-backend': patch
'@backstage/plugin-bazaar-backend': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-app-backend': patch
---
`knex` has been bumped to major version 3 and `better-sqlite3` to major version 9, which deprecate node 16 support.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
Implement new `AppTreeApi`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Add new `AppTreeApi`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Added missing node-gyp dependency to fix Docker image build
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-react': patch
---
Removed unnecessary `@backstage/integration` dependency, replaced by `@backstage/integration-react`.
+2 -5
View File
@@ -1,11 +1,8 @@
---
blank_issues_enabled: false
contact_links:
- about: 'Please ask and answer usage questions in GitHub Discussions'
name: Question
url: 'https://github.com/backstage/backstage/discussions'
- about: 'Alternatively, you can use the Backstage Community Discord'
name: Chat
- about: 'Use the Backstage Community Discord for questions & discussions'
name: Questions
url: 'https://discord.gg/backstage-687207715902193673'
- about: 'Please check the FAQ before filing new issues'
name: 'Backstage FAQ'
+2 -2
View File
@@ -39,7 +39,7 @@ jobs:
persist-credentials: false
- name: 'Run analysis'
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
@@ -66,6 +66,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
with:
sarif_file: results.sarif
+1 -1
View File
@@ -58,6 +58,6 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
NODE_OPTIONS: --max-old-space-size=7168
- name: Upload Snyk report
uses: github/codeql-action/upload-sarif@v2.22.4
uses: github/codeql-action/upload-sarif@v2.22.5
with:
sarif_file: snyk.sarif
+3 -3
View File
@@ -55,7 +55,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2.22.4
uses: github/codeql-action/init@v2.22.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +66,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2.22.4
uses: github/codeql-action/autobuild@v2.22.5
# ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -80,4 +80,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2.22.4
uses: github/codeql-action/analyze@v2.22.5
+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
- run: yarn build-storybook
- uses: chromaui/action@c7e9d129ad2b8e728e10522270e14596473a7958 # v1
- uses: chromaui/action@d726e4e790a99e876f71b8e09d3053bfe783d6b8 # v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
-2
View File
@@ -194,8 +194,6 @@ WORKDIR /app
COPY --from=packages --chown=node:node /app .
# Stop cypress from downloading it's massive binary.
ENV CYPRESS_INSTALL_BINARY=0
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
yarn install --frozen-lockfile --network-timeout 600000
@@ -0,0 +1,10 @@
---
title: Nexus Repository Manager
author: Red Hat
authorUrl: https://redhat.com
category: Image
description: View information about the build artifacts in your Nexus Repository Manager in Backstage.
documentation: https://janus-idp.io/plugins/nexus-repository-manager
iconUrl: https://janus-idp.io/images/plugins/nexus-repository-manager.svg
npmPackageName: '@janus-idp/backstage-plugin-nexus-repository-manager'
addedDate: '2023-10-25'
+6 -6
View File
@@ -3055,9 +3055,9 @@ __metadata:
linkType: hard
"@types/luxon@npm:^3.0.0":
version: 3.3.2
resolution: "@types/luxon@npm:3.3.2"
checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8
version: 3.3.3
resolution: "@types/luxon@npm:3.3.3"
checksum: 072dd39eea3f63453788fab2fcfc83eb33917afcaffe178ce08ecd8b016824b8ab3bfa991f66266f2fc1927768a56b4334945f2eb1d83638e325c0c43d7d0e86
languageName: node
linkType: hard
@@ -3265,9 +3265,9 @@ __metadata:
linkType: hard
"@types/webpack-env@npm:^1.18.0":
version: 1.18.2
resolution: "@types/webpack-env@npm:1.18.2"
checksum: 883908ade827d35a10efc574fb6f2728a7c520d4296cf1507633ac7457204ccd697bc6c8cadac99bc5d96074a6109c658ebfde59f42ba5ba0fdfffc538892b0f
version: 1.18.3
resolution: "@types/webpack-env@npm:1.18.3"
checksum: f24e82485d8e325b1875608766ba6dad2b2f53a0fb182bc96173b4590110c6b791163402cdf19b50c04e8c05292f227a08aafe9230b2bba52c40c5f7ceccc101
languageName: node
linkType: hard
+2 -1
View File
@@ -32,6 +32,7 @@ import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api';
import { IdentityService } from '@backstage/backend-plugin-api';
import { isChildPath } from '@backstage/cli-common';
import { Knex } from 'knex';
import knexFactory from 'knex';
import { KubeConfig } from '@kubernetes/client-node';
import { LifecycleService } from '@backstage/backend-plugin-api';
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
@@ -222,7 +223,7 @@ export function createDatabaseClient(
lifecycle: LifecycleService;
pluginMetadata: PluginMetadataService;
},
): Knex<any, any[]>;
): knexFactory.Knex<any, any[]>;
// @public
export function createRootLogger(
+3 -3
View File
@@ -89,14 +89,14 @@
"isomorphic-git": "^1.23.0",
"jose": "^4.6.0",
"keyv": "^4.5.2",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"logform": "^2.3.2",
"luxon": "^3.0.0",
"minimatch": "^5.0.0",
"mysql2": "^2.2.5",
"node-fetch": "^2.6.7",
"pg": "^8.3.0",
"pg": "^8.11.3",
"raw-body": "^2.4.1",
"tar": "^6.1.12",
"uuid": "^8.3.2",
@@ -129,7 +129,7 @@
"@types/webpack-env": "^1.15.2",
"@types/yauzl": "^2.10.0",
"aws-sdk-client-mock": "^2.0.0",
"better-sqlite3": "^8.0.0",
"better-sqlite3": "^9.0.0",
"http-errors": "^2.0.0",
"msw": "^1.0.0",
"mysql2": "^2.2.5",
+1 -1
View File
@@ -52,7 +52,7 @@
"@backstage/types": "workspace:^",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"knex": "^2.0.0"
"knex": "^3.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
+1 -1
View File
@@ -39,7 +39,7 @@
"@opentelemetry/api": "^1.3.0",
"@types/luxon": "^3.0.0",
"cron": "^2.0.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"uuid": "^8.0.0",
+3 -3
View File
@@ -49,13 +49,13 @@
"@backstage/errors": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/types": "workspace:^",
"better-sqlite3": "^8.0.0",
"better-sqlite3": "^9.0.0",
"express": "^4.17.1",
"fs-extra": "^10.0.1",
"knex": "^2.0.0",
"knex": "^3.0.0",
"msw": "^1.0.0",
"mysql2": "^2.2.5",
"pg": "^8.3.0",
"pg": "^8.11.3",
"testcontainers": "^8.1.2",
"textextensions": "^5.16.0",
"uuid": "^8.0.0"
+2 -2
View File
@@ -84,7 +84,7 @@
"@opentelemetry/exporter-prometheus": "^0.44.0",
"@opentelemetry/sdk-metrics": "^1.13.0",
"azure-devops-node-api": "^11.0.1",
"better-sqlite3": "^8.0.0",
"better-sqlite3": "^9.0.0",
"dockerode": "^3.3.1",
"example-app": "link:../app",
"express": "^4.17.1",
@@ -92,7 +92,7 @@
"express-promise-router": "^4.1.0",
"luxon": "^3.0.0",
"mysql2": "^2.2.5",
"pg": "^8.3.0",
"pg": "^8.11.3",
"pg-connection-string": "^2.3.0",
"prom-client": "^14.0.1",
"winston": "^3.2.1"
@@ -55,8 +55,12 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
apply(next: typeof fetch): typeof fetch {
return async (input, init) => {
// Skip this middleware if the header already exists, or if the URL
// doesn't match any of the allowlist items, or if there was no token
const request = new Request(input, init);
// doesn't match any of the allowlist items, or if there was no token.
// NOTE(freben): The "as any" casts here and below are because of subtle
// undici type differences that happened in a node types bump. Those are
// immaterial to the code at hand at runtime, as the global fetch and
// Request are always taken from the same place.
const request = new Request(input as any, init);
const { token } = await this.identityApi.getCredentials();
if (
request.headers.get(this.headerName) ||
@@ -64,7 +68,7 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
!token ||
!this.allowUrl(request.url)
) {
return next(input, init);
return next(input as any, init);
}
request.headers.set(this.headerName, this.headerValue(token));
@@ -34,11 +34,15 @@ export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware {
apply(next: typeof fetch): typeof fetch {
return async (input, init) => {
const request = new Request(input, init);
// NOTE(freben): The "as any" casts here and below are because of subtle
// undici type differences that happened in a node types bump. Those are
// immaterial to the code at hand at runtime, as the global fetch and
// Request are always taken from the same place.
const request = new Request(input as any, init);
const prefix = 'plugin://';
if (!request.url.startsWith(prefix)) {
return next(input, init);
return next(input as any, init);
}
// Switch to a known protocol, since browser URL parsing misbehaves wildly
@@ -57,7 +61,7 @@ export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware {
const target = `${join(base, pathname)}${search}${hash}`;
return next(
target,
typeof input === 'string' || isUrl(input) ? init : input,
typeof input === 'string' || isUrl(input) ? init : (input as any),
);
};
}
@@ -34,11 +34,12 @@
"@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}",
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
"app": "link:../app",
"better-sqlite3": "^8.0.0",
"better-sqlite3": "^9.0.0",
"dockerode": "^3.3.1",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"pg": "^8.3.0",
"node-gyp": "^9.0.0",
"pg": "^8.11.3",
"winston": "^3.2.1"
},
"devDependencies": {
@@ -18,7 +18,7 @@ import { RouteRef, coreExtensionData } from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { toLegacyPlugin } from '../wiring/createApp';
import { BackstageRouteObject } from './types';
import { AppNode } from '../tree';
import { AppNode } from '@backstage/frontend-plugin-api';
// We always add a child that matches all subroutes but without any route refs. This makes
// sure that we're always able to match each route no matter how deep the navigation goes.
@@ -22,7 +22,7 @@ import {
import { readAppExtensionsConfig } from './readAppExtensionsConfig';
import { resolveAppTree } from './resolveAppTree';
import { resolveAppNodeSpecs } from './resolveAppNodeSpecs';
import { AppTree } from './types';
import { AppTree } from '@backstage/frontend-plugin-api';
import { Config } from '@backstage/config';
import { instantiateAppNodeTree } from './instantiateAppNodeTree';
@@ -14,10 +14,4 @@
* limitations under the License.
*/
export type {
AppNode,
AppNodeEdges,
AppNodeInstance,
AppNodeSpec,
} from './types';
export { createAppTree } from './createAppTree';
@@ -25,7 +25,7 @@ import {
createAppNodeInstance,
instantiateAppNodeTree,
} from './instantiateAppNodeTree';
import { AppNodeInstance, AppNodeSpec } from './types';
import { AppNodeInstance, AppNodeSpec } from '@backstage/frontend-plugin-api';
import { resolveAppTree } from './resolveAppTree';
const testDataRef = createExtensionDataRef<string>('test');
@@ -20,7 +20,11 @@ import {
ExtensionDataRef,
} from '@backstage/frontend-plugin-api';
import mapValues from 'lodash/mapValues';
import { AppNode, AppNodeInstance, AppNodeSpec } from './types';
import {
AppNode,
AppNodeInstance,
AppNodeSpec,
} from '@backstage/frontend-plugin-api';
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
@@ -22,7 +22,7 @@ import {
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides';
import { ExtensionParameters } from './readAppExtensionsConfig';
import { AppNodeSpec } from './types';
import { AppNodeSpec } from '@backstage/frontend-plugin-api';
/** @internal */
export function resolveAppNodeSpecs(options: {
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import { AppTree, AppNode, AppNodeInstance, AppNodeSpec } from './types';
import {
AppTree,
AppNode,
AppNodeInstance,
AppNodeSpec,
} from '@backstage/frontend-plugin-api';
function indent(str: string) {
return str.replace(/^/gm, ' ');
@@ -15,6 +15,8 @@
*/
import {
AppTreeApi,
appTreeApiRef,
createPageExtension,
createPlugin,
createThemeExtension,
@@ -23,6 +25,7 @@ import { screen, waitFor } from '@testing-library/react';
import { createApp } from './createApp';
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
import React from 'react';
import { useApi } from '@backstage/core-plugin-api';
describe('createApp', () => {
it('should allow themes to be installed', async () => {
@@ -90,4 +93,58 @@ describe('createApp', () => {
expect(screen.getByText('Last Page')).toBeInTheDocument(),
);
});
it('should make the app structure available through the AppTreeApi', async () => {
let appTreeApi: AppTreeApi | undefined = undefined;
const app = createApp({
configLoader: async () => new MockConfigApi({}),
features: [
createPlugin({
id: 'my-plugin',
extensions: [
createPageExtension({
id: 'plugin.my-plugin.page',
defaultPath: '/',
loader: async () => {
const Component = () => {
appTreeApi = useApi(appTreeApiRef);
return <div>My Plugin Page</div>;
};
return <Component />;
},
}),
],
}),
],
});
await renderWithEffects(app.createRoot());
expect(appTreeApi).toBeDefined();
const { tree } = appTreeApi!.getTree();
expect(String(tree.root)).toMatchInlineSnapshot(`
"<core out=[core.reactElement]>
root [
<core.layout out=[core.reactElement]>
content [
<core.routes out=[core.reactElement]>
routes [
<plugin.my-plugin.page out=[core.routing.path, core.routing.ref, core.reactElement] />
]
</core.routes>
]
nav [
<core.nav out=[core.reactElement] />
]
</core.layout>
]
themes [
<themes.light out=[core.theme] />
<themes.dark out=[core.theme] />
]
</core>"
`);
});
});
@@ -17,6 +17,8 @@
import React, { JSX } from 'react';
import { ConfigReader, Config } from '@backstage/config';
import {
AppTree,
appTreeApiRef,
BackstagePlugin,
coreExtensionData,
ExtensionDataRef,
@@ -87,7 +89,8 @@ import { AppRouteBinder } from '../routing';
import { RoutingProvider } from '../routing/RoutingProvider';
import { resolveRouteBindings } from '../routing/resolveRouteBindings';
import { collectRouteIds } from '../routing/collectRouteIds';
import { AppNode, createAppTree } from '../tree';
import { createAppTree } from '../tree';
import { AppNode } from '@backstage/frontend-plugin-api';
const builtinExtensions = [
Core,
@@ -262,7 +265,7 @@ export function createApp(options: {
const routeIds = collectRouteIds(allFeatures);
const App = () => (
<ApiProvider apis={createApiHolder(tree.root, config)}>
<ApiProvider apis={createApiHolder(tree, config)}>
<AppContextProvider appContext={appContext}>
<AppThemeProvider>
<RoutingProvider
@@ -356,17 +359,17 @@ function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext {
};
}
function createApiHolder(core: AppNode, configApi: ConfigApi): ApiHolder {
function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder {
const factoryRegistry = new ApiFactoryRegistry();
const pluginApis =
core.edges.attachments
tree.root.edges.attachments
.get('apis')
?.map(e => e.instance?.getData(coreExtensionData.apiFactory))
.filter((x): x is AnyApiFactory => !!x) ?? [];
const themeExtensions =
core.edges.attachments
tree.root.edges.attachments
.get('themes')
?.map(e => e.instance?.getData(coreExtensionData.theme))
.filter((x): x is AppTheme => !!x) ?? [];
@@ -414,6 +417,14 @@ function createApiHolder(core: AppNode, configApi: ConfigApi): ApiHolder {
},
});
factoryRegistry.register('static', {
api: appTreeApiRef,
deps: {},
factory: () => ({
getTree: () => ({ tree }),
}),
});
factoryRegistry.register('static', {
api: appThemeApiRef,
deps: {},
@@ -7,6 +7,7 @@
import { AnyApiFactory } from '@backstage/core-plugin-api';
import { AnyApiRef } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/core-plugin-api';
import { AppTheme } from '@backstage/core-plugin-api';
import { IconComponent } from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
@@ -55,6 +56,66 @@ export type AnyRoutes = {
[name in string]: RouteRef;
};
// @public
export interface AppNode {
readonly edges: AppNodeEdges;
readonly instance?: AppNodeInstance;
readonly spec: AppNodeSpec;
}
// @public
export interface AppNodeEdges {
// (undocumented)
readonly attachedTo?: {
node: AppNode;
input: string;
};
// (undocumented)
readonly attachments: ReadonlyMap<string, AppNode[]>;
}
// @public
export interface AppNodeInstance {
getData<T>(ref: ExtensionDataRef<T>): T | undefined;
getDataRefs(): Iterable<ExtensionDataRef<unknown>>;
}
// @public
export interface AppNodeSpec {
// (undocumented)
readonly attachTo: {
id: string;
input: string;
};
// (undocumented)
readonly config?: unknown;
// (undocumented)
readonly disabled: boolean;
// (undocumented)
readonly extension: Extension<unknown>;
// (undocumented)
readonly id: string;
// (undocumented)
readonly source?: BackstagePlugin;
}
// @public
export interface AppTree {
readonly nodes: ReadonlyMap<string, AppNode>;
readonly orphans: Iterable<AppNode>;
readonly root: AppNode;
}
// @public
export interface AppTreeApi {
getTree(): {
tree: AppTree;
};
}
// @public
export const appTreeApiRef: ApiRef<AppTreeApi>;
// @public (undocumented)
export interface BackstagePlugin<
Routes extends AnyRoutes = AnyRoutes,
@@ -14,20 +14,13 @@
* limitations under the License.
*/
import {
BackstagePlugin,
Extension,
ExtensionDataRef,
} from '@backstage/frontend-plugin-api';
/*
NOTE: These types are marked as @internal for now, but the intention is for this to be a public API in the future.
*/
import { createApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin, Extension, ExtensionDataRef } from '../../wiring';
/**
* The specification for this node in the app tree.
* The specification for this {@link AppNode} in the {@link AppTree}.
*
* @internal
* @public
* @remarks
*
* The specifications for a collection of app nodes is all the information needed
@@ -43,9 +36,9 @@ export interface AppNodeSpec {
}
/**
* The connections from this node to other nodes.
* The connections from this {@link AppNode} to other nodes.
*
* @internal
* @public
* @remarks
*
* The app node edges are resolved based on the app node specs, regardless of whether
@@ -57,9 +50,9 @@ export interface AppNodeEdges {
}
/**
* The instance of this node in the app tree.
* The instance of this {@link AppNode} in the {@link AppTree}.
*
* @internal
* @public
* @remarks
*
* The app node instance is created when the `factory` function of an extension is called.
@@ -74,8 +67,9 @@ export interface AppNodeInstance {
}
/**
* A node in the {@link AppTree}.
*
* @internal
* @public
*/
export interface AppNode {
/** The specification for how this node should be instantiated */
@@ -87,15 +81,34 @@ export interface AppNode {
}
/**
* The app tree containing all nodes of the app.
* The app tree containing all {@link AppNode}s of the app.
*
* @internal
* @public
*/
export interface AppTree {
/** The root node of the app */
root: AppNode;
readonly root: AppNode;
/** A map of all nodes in the app by ID, including orphaned or disabled nodes */
nodes: ReadonlyMap<string /* id */, AppNode>;
readonly nodes: ReadonlyMap<string /* id */, AppNode>;
/** A sequence of all nodes with a parent that is not reachable from the app root node */
orphans: Iterable<AppNode>;
readonly orphans: Iterable<AppNode>;
}
/**
* The API for interacting with the {@link AppTree}.
*
* @public
*/
export interface AppTreeApi {
/**
* Get the {@link AppTree} for the app.
*/
getTree(): { tree: AppTree };
}
/**
* The `ApiRef` of {@link AppTreeApi}.
*
* @public
*/
export const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });
@@ -0,0 +1,25 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
appTreeApiRef,
type AppNode,
type AppNodeEdges,
type AppNodeInstance,
type AppNodeSpec,
type AppTree,
type AppTreeApi,
} from './AppTreeApi';
@@ -0,0 +1,17 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './definitions';
@@ -20,6 +20,7 @@
* @packageDocumentation
*/
export * from './apis';
export * from './components';
export * from './extensions';
export * from './routing';
+1 -1
View File
@@ -57,7 +57,7 @@
"fs-extra": "10.1.0",
"globby": "^11.0.0",
"helmet": "^6.0.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"winston": "^3.2.1",
+2 -2
View File
@@ -51,7 +51,7 @@
"@types/express": "^4.17.6",
"@types/passport": "^1.0.3",
"compression": "^1.7.4",
"connect-session-knex": "^3.0.1",
"connect-session-knex": "^4.0.0",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"express": "^4.17.1",
@@ -61,7 +61,7 @@
"google-auth-library": "^8.0.0",
"jose": "^4.6.0",
"jwt-decode": "^3.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"minimatch": "^5.0.0",
+1 -1
View File
@@ -45,7 +45,7 @@
"cors": "^2.8.5",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.4.2",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"supertest": "^6.3.3",
"uuid": "^9.0.0",
+1 -1
View File
@@ -49,7 +49,7 @@
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
+3 -3
View File
@@ -2,15 +2,15 @@
### What is the Bazaar?
The Bazaar is a place where teams can propose projects for cross-functional team development. Essentially a marketplace for internal projects suitable for [Inner Sourcing](https://en.wikipedia.org/wiki/Inner_source). With "Inner Sourcing", we mean projects that are developed internally within a company, but with Open Source best practices.
The Bazaar is a place where teams can propose projects for cross-functional team development. Essentially, its a marketplace for internal projects suitable for [Inner Sourcing](https://en.wikipedia.org/wiki/Inner_source). By “Inner Sourcing, we mean projects that are developed internally within a company but follow Open Source best practices.
### Why?
Many companies today are of high need to increase the ease of cross-team cooperation. In large organizations, engineers often have limited ways of discovering or announcing the projects which could benefit from a wider development effort in terms of different expertise, experiences, and teams spread across the organization. With no good way to find these existing internal projects to join, the possibility of working with Inner Sourcing practices suffers.
Many companies today have a high need to increase the ease of cross-team cooperation. In large organizations, engineers often have limited ways of discovering or announcing projects that could benefit from a wider development effort in terms of different expertise, experiences, and teams spread across the organization. With no good way to find these existing internal projects to join, the possibility of working with Inner Sourcing practices suffers.
### How?
The Bazaar allows engineers and teams to open up and announce their new and exciting projects for transparent cooperation in other parts of larger organizations. The Bazaar ensures that new Inner Sourcing friendly projects gain visibility through Backstage and a way for interested engineers to show their interest and in the future contribute with their specific skill set. The Bazaar also provides an easy way to manage, catalog, and browse these Inner Sourcing friendly projects and components.
The Bazaar allows engineers and teams to open up and announce their new and exciting projects for transparent cooperation in other parts of larger organizations. The Bazaar ensures that new Inner Sourcing-friendly projects gain visibility through Backstage and a way for interested engineers to show their interest and, in the future, contribute with their specific skill set. The Bazaar also provides an easy way to manage, catalog, and browse these Inner Sourcing-friendly projects and components.
# Note
@@ -59,7 +59,7 @@
"@types/luxon": "^3.0.0",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"luxon": "^3.0.0",
"uuid": "^8.3.2",
"winston": "^3.2.1"
@@ -38,6 +38,6 @@
"@backstage/catalog-model": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"express-promise-router": "^4.1.1",
"knex": "^2.4.2"
"knex": "^3.0.0"
}
}
+2 -2
View File
@@ -71,7 +71,7 @@
"fs-extra": "10.1.0",
"git-url-parse": "^13.0.0",
"glob": "^7.1.6",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"minimatch": "^5.0.0",
@@ -93,7 +93,7 @@
"@types/lodash": "^4.14.151",
"@types/supertest": "^2.0.8",
"@types/uuid": "^8.0.0",
"better-sqlite3": "^8.0.0",
"better-sqlite3": "^9.0.0",
"luxon": "^3.0.0",
"msw": "^1.0.0",
"supertest": "^6.1.3",
+2 -2
View File
@@ -22,7 +22,7 @@ import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { StyleRules } from '@material-ui/core/styles/withStyles';
import { SystemEntity } from '@backstage/catalog-model';
import { TableColumn } from '@backstage/core-components';
@@ -597,7 +597,7 @@ export function getEntityRelations(
// @public (undocumented)
export function getEntitySourceLocation(
entity: Entity,
scmIntegrationsApi: ScmIntegrationRegistry,
scmIntegrationsApi: typeof scmIntegrationsApiRef.T,
): EntitySourceLocation | undefined;
// @public (undocumented)
+1 -1
View File
@@ -52,7 +52,7 @@
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
@@ -19,7 +19,7 @@ import {
Entity,
parseLocationRef,
} from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
/** @public */
export type EntitySourceLocation = {
@@ -30,7 +30,7 @@ export type EntitySourceLocation = {
/** @public */
export function getEntitySourceLocation(
entity: Entity,
scmIntegrationsApi: ScmIntegrationRegistry,
scmIntegrationsApi: typeof scmIntegrationsApiRef.T,
): EntitySourceLocation | undefined {
const sourceLocation =
entity.metadata.annotations?.[ANNOTATION_SOURCE_LOCATION];
+1 -1
View File
@@ -42,7 +42,7 @@
"body-parser-xml": "^2.0.5",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"uuid": "^8.3.2",
"winston": "^3.2.1",
"yn": "^4.0.0"
+1 -1
View File
@@ -38,7 +38,7 @@
"@types/express": "*",
"express": "^4.18.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"node-fetch": "^2.6.7",
"winston": "^3.2.1",
"yn": "^4.0.0"
+1 -1
View File
@@ -43,7 +43,7 @@
"express": "^4.18.1",
"express-promise-router": "^4.1.0",
"fs-extra": "^10.0.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"linguist-js": "^2.5.3",
"luxon": "^2.0.2",
"node-fetch": "^2.6.7",
+1 -1
View File
@@ -41,7 +41,7 @@
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"node-fetch": "^2.6.7",
"uuid": "^8.2.0",
"winston": "^3.2.1",
+1 -1
View File
@@ -82,7 +82,7 @@
"isolated-vm": "^4.5.0",
"isomorphic-git": "^1.23.0",
"jsonschema": "^1.2.6",
"knex": "^2.0.0",
"knex": "^3.0.0",
"libsodium-wrappers": "^0.7.11",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
@@ -47,7 +47,7 @@
"@backstage/config": "workspace:^",
"@backstage/plugin-search-backend-node": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"uuid": "^8.3.2",
"winston": "^3.2.1"
+14 -7
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { useEffect } from 'react';
import React, { useEffect, useRef } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { useForm, SubmitHandler, Controller } from 'react-hook-form';
import {
@@ -58,6 +58,10 @@ export const ShortcutForm = ({
shortcutApi.shortcut$(),
shortcutApi.get(),
);
const { current: originalValues } = useRef({
url: formValues?.url ?? '',
title: formValues?.title ?? '',
});
const {
handleSubmit,
reset,
@@ -65,20 +69,23 @@ export const ShortcutForm = ({
formState: { errors },
} = useForm<FormValues>({
mode: 'onChange',
defaultValues: {
url: formValues?.url ?? '',
title: formValues?.title ?? '',
},
defaultValues: originalValues,
});
const titleIsUnique = (title: string) => {
if (shortcutData.some(shortcutTitle => shortcutTitle.title === title))
if (
title !== originalValues.title &&
shortcutData.some(shortcutTitle => shortcutTitle.title === title)
)
return 'A shortcut with this title already exists';
return true;
};
const urlIsUnique = (url: string) => {
if (shortcutData.some(shortcutUrl => shortcutUrl.url === url))
if (
url !== originalValues.url &&
shortcutData.some(shortcutUrl => shortcutUrl.url === url)
)
return 'A shortcut with this url already exists';
return true;
};
+1 -1
View File
@@ -46,7 +46,7 @@
"@types/luxon": "^3.0.0",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"luxon": "^3.0.0",
"semver": "^7.5.3",
+1 -1
View File
@@ -63,7 +63,7 @@
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"fs-extra": "10.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"node-fetch": "^2.6.7",
"p-limit": "^3.1.0",
+1 -1
View File
@@ -52,7 +52,7 @@
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^2.0.0",
"knex": "^3.0.0",
"winston": "^3.2.1",
"yn": "^4.0.0"
},
+301 -211
View File
File diff suppressed because it is too large Load Diff