Merge branch 'backstage:master' into master
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
'@backstage/cli': minor
|
||||
---
|
||||
|
||||
The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior, set `LEGACY_BACKEND_START` instead.
|
||||
**BREAKING** The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior set `LEGACY_BACKEND_START`, which is recommended if you haven't migrated to the new backend system.
|
||||
|
||||
This new command is no longer based on Webpack, but instead uses Node.js loaders to transpile on the fly. Rather than hot reloading modules the entire backend is now restarted on change, but the SQLite database state is still maintained across restarts via a parent process.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': minor
|
||||
---
|
||||
|
||||
Added experimental support for declarative integration via the `/alpha` subpath.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-github': patch
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Request slightly smaller pages of data from GitHub
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/dev-utils': patch
|
||||
---
|
||||
|
||||
Moving development `dependencies` to `devDependencies`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-app-api': patch
|
||||
---
|
||||
|
||||
Fixed a bug in `TranslationApi` implementation where in some cases it wouldn't notify subscribers of language changes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend': patch
|
||||
---
|
||||
|
||||
Set the default length limit to search query to 100. To override it, define `search.maxTermLength` in the config file.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7077dbf131: The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior, set `LEGACY_BACKEND_START` instead.
|
||||
- 7077dbf131: **BREAKING** The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior set `LEGACY_BACKEND_START`, which is recommended if you haven't migrated to the new backend system.
|
||||
|
||||
This new command is no longer based on Webpack, but instead uses Node.js loaders to transpile on the fly. Rather than hot reloading modules the entire backend is now restarted on change, but the SQLite database state is still maintained across restarts via a parent process.
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
createPageExtension,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import techdocsPlugin from '@backstage/plugin-techdocs/alpha';
|
||||
|
||||
/*
|
||||
|
||||
@@ -67,6 +68,7 @@ const app = createApp({
|
||||
graphiqlPlugin,
|
||||
pagesPlugin,
|
||||
techRadarPlugin,
|
||||
techdocsPlugin,
|
||||
userSettingsPlugin,
|
||||
createExtensionOverrides({
|
||||
extensions: [entityPageExtension],
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 7077dbf131: The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior, set `LEGACY_BACKEND_START` instead.
|
||||
- 7077dbf131: **BREAKING** The new backend start command that used to be enabled by setting `EXPERIMENTAL_BACKEND_START` is now the default. To revert to the old behavior set `LEGACY_BACKEND_START`, which is recommended if you haven't migrated to the new backend system.
|
||||
|
||||
This new command is no longer based on Webpack, but instead uses Node.js loaders to transpile on the fly. Rather than hot reloading modules the entire backend is now restarted on change, but the SQLite database state is still maintained across restarts via a parent process.
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ export class GithubDiscoveryProvider implements Provider {
|
||||
const query = `query repositories($org: String!, $cursor: String) {
|
||||
repositoryOwner(login: $org) {
|
||||
login
|
||||
repositories(first: 100, after: $cursor) {
|
||||
repositories(first: 50, after: $cursor) {
|
||||
nodes {
|
||||
name
|
||||
url
|
||||
|
||||
+1
-2
@@ -377,8 +377,7 @@ describe('I18nextTranslationApi', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(translations).toEqual(['foo', null, 'Föö', null, 'Føø']);
|
||||
expect(translations).toEqual(['foo', 'Föö', 'Føø']);
|
||||
});
|
||||
|
||||
describe('formatting', () => {
|
||||
|
||||
+3
-8
@@ -241,7 +241,6 @@ export class I18nextTranslationApi implements TranslationApi {
|
||||
|
||||
return new ObservableImpl<TranslationSnapshot<TMessages>>(subscriber => {
|
||||
let loadTicket = {}; // To check for stale loads
|
||||
let lastSnapshotWasReady = false;
|
||||
|
||||
const loadResource = () => {
|
||||
loadTicket = {};
|
||||
@@ -250,8 +249,7 @@ export class I18nextTranslationApi implements TranslationApi {
|
||||
() => {
|
||||
if (ticket === loadTicket) {
|
||||
const snapshot = this.#createSnapshot(internalRef);
|
||||
if (snapshot.ready || lastSnapshotWasReady) {
|
||||
lastSnapshotWasReady = snapshot.ready;
|
||||
if (snapshot.ready) {
|
||||
subscriber.next(snapshot);
|
||||
}
|
||||
}
|
||||
@@ -266,12 +264,9 @@ export class I18nextTranslationApi implements TranslationApi {
|
||||
|
||||
const onChange = () => {
|
||||
const snapshot = this.#createSnapshot(internalRef);
|
||||
if (lastSnapshotWasReady && !snapshot.ready) {
|
||||
lastSnapshotWasReady = snapshot.ready;
|
||||
if (snapshot.ready) {
|
||||
subscriber.next(snapshot);
|
||||
}
|
||||
|
||||
if (!snapshot.ready) {
|
||||
} else {
|
||||
loadResource();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -103,6 +103,7 @@ describe('tasks', () => {
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
|
||||
const origCwd = process.cwd();
|
||||
const realChdir = process.chdir;
|
||||
// If anyone calls chdir then make it resolve within the tmpdir
|
||||
const mockChdir = jest.spyOn(process, 'chdir');
|
||||
@@ -130,6 +131,10 @@ describe('tasks', () => {
|
||||
mockChdir.mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
realChdir(origCwd);
|
||||
});
|
||||
|
||||
describe('checkAppExistsTask', () => {
|
||||
it('should do nothing if the directory does not exist', async () => {
|
||||
const dir = 'projects/';
|
||||
|
||||
@@ -40,17 +40,11 @@
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
"@backstage/plugin-catalog-react": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/react": "^16.13.1 || ^17.0.0",
|
||||
"react-use": "^17.2.4",
|
||||
"zen-observable": "^0.10.0"
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.13.1 || ^17.0.0",
|
||||
@@ -58,7 +52,13 @@
|
||||
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/dom": "^9.0.0",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^12.1.3",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"zen-observable": "^0.10.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -462,7 +462,7 @@ export async function getOrganizationRepositories(
|
||||
query repositories($org: String!, $catalogPathRef: String!, $cursor: String) {
|
||||
repositoryOwner(login: $org) {
|
||||
login
|
||||
repositories(first: 100, after: $cursor) {
|
||||
repositories(first: 50, after: $cursor) {
|
||||
nodes {
|
||||
name
|
||||
catalogInfoFile: object(expression: $catalogPathRef) {
|
||||
|
||||
Vendored
+5
@@ -22,6 +22,11 @@ export interface Config {
|
||||
*/
|
||||
maxPageLimit?: number;
|
||||
|
||||
/**
|
||||
* Sets the maximum term length for the search string. Defaults to 100.
|
||||
*/
|
||||
maxTermLength?: number;
|
||||
|
||||
/**
|
||||
* Options related to the search integration with the Backstage permissions system
|
||||
*/
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('createRouter', () => {
|
||||
},
|
||||
config: new ConfigReader({
|
||||
permissions: { enabled: false },
|
||||
search: { maxPageLimit: 200 },
|
||||
search: { maxPageLimit: 200, maxTermLength: 20 },
|
||||
}),
|
||||
permissions: mockPermissionEvaluator,
|
||||
logger,
|
||||
@@ -162,6 +162,19 @@ describe('createRouter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject term length over configured max', async () => {
|
||||
const response = await request(app).get(
|
||||
`/query?term=HelloWorld1234567890!`,
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
expect(response.body).toMatchObject({
|
||||
error: {
|
||||
message: /The term length "21" is greater than "20"/i,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('removes backend-only properties from search documents', async () => {
|
||||
mockSearchEngine.query.mockResolvedValue({
|
||||
results: [
|
||||
|
||||
@@ -63,6 +63,7 @@ export type RouterOptions = {
|
||||
};
|
||||
|
||||
const defaultMaxPageLimit = 100;
|
||||
const defaultMaxTermLength = 100;
|
||||
const allowedLocationProtocols = ['http:', 'https:'];
|
||||
|
||||
/**
|
||||
@@ -77,8 +78,19 @@ export async function createRouter(
|
||||
const maxPageLimit =
|
||||
config.getOptionalNumber('search.maxPageLimit') ?? defaultMaxPageLimit;
|
||||
|
||||
const maxTermLength =
|
||||
config.getOptionalNumber('search.maxTermLength') ?? defaultMaxTermLength;
|
||||
|
||||
const requestSchema = z.object({
|
||||
term: z.string().default(''),
|
||||
term: z
|
||||
.string()
|
||||
.refine(
|
||||
term => term.length <= maxTermLength,
|
||||
term => ({
|
||||
message: `The term length "${term.length}" is greater than "${maxTermLength}"`,
|
||||
}),
|
||||
)
|
||||
.default(''),
|
||||
filters: jsonObjectSchema.optional(),
|
||||
types: z
|
||||
.array(z.string().refine(type => Object.keys(types).includes(type)))
|
||||
|
||||
@@ -18,8 +18,32 @@ import React from 'react';
|
||||
import {
|
||||
createPlugin,
|
||||
createSchemaFromZod,
|
||||
createApiExtension,
|
||||
createPageExtension,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha';
|
||||
import {
|
||||
configApiRef,
|
||||
createApiFactory,
|
||||
createRouteRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
techdocsApiRef,
|
||||
techdocsStorageApiRef,
|
||||
} from '@backstage/plugin-techdocs-react';
|
||||
import { TechDocsClient, TechDocsStorageClient } from './client';
|
||||
|
||||
const rootRouteRef = createRouteRef({
|
||||
id: 'plugin.techdocs.indexPage',
|
||||
});
|
||||
|
||||
const rootDocsRouteRef = createRouteRef({
|
||||
id: 'plugin.techdocs.readerPage',
|
||||
params: ['namespace', 'kind', 'name'],
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export const TechDocsSearchResultListItemExtension =
|
||||
@@ -44,8 +68,89 @@ export const TechDocsSearchResultListItemExtension =
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Responsible for rendering the provided router element
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
const TechDocsIndexPage = createPageExtension({
|
||||
id: 'plugin.techdocs.indexPage',
|
||||
defaultPath: '/docs',
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('./home/components/TechDocsIndexPage').then(m => (
|
||||
<m.TechDocsIndexPage />
|
||||
)),
|
||||
});
|
||||
|
||||
/**
|
||||
* Component responsible for composing a TechDocs reader page experience
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
const TechDocsReaderPage = createPageExtension({
|
||||
id: 'plugin.techdocs.readerPage',
|
||||
loader: () =>
|
||||
import('./reader/components/TechDocsReaderPage').then(m => (
|
||||
<m.TechDocsReaderPage />
|
||||
)),
|
||||
routeRef: rootDocsRouteRef,
|
||||
defaultPath: '/docs/:namespace/:kind/:name/*',
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
const techDocsStorage = createApiExtension({
|
||||
api: techdocsStorageApiRef,
|
||||
|
||||
factory() {
|
||||
return createApiFactory({
|
||||
api: techdocsStorageApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi, fetchApi }) =>
|
||||
new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
const techDocsClient = createApiExtension({
|
||||
api: techdocsApiRef,
|
||||
factory() {
|
||||
return createApiFactory({
|
||||
api: techdocsApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, fetchApi }) =>
|
||||
new TechDocsClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
fetchApi,
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
/** @alpha */
|
||||
export default createPlugin({
|
||||
id: 'techdocs',
|
||||
extensions: [TechDocsSearchResultListItemExtension],
|
||||
extensions: [
|
||||
TechDocsIndexPage,
|
||||
TechDocsReaderPage,
|
||||
techDocsClient,
|
||||
techDocsStorage,
|
||||
TechDocsSearchResultListItemExtension,
|
||||
],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user