Merge branch 'master' of https://github.com/backstage/backstage
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-azure-devops-backend': patch
|
||||
---
|
||||
|
||||
Updated installation documentation
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-react': minor
|
||||
---
|
||||
|
||||
The `value` of a search analytics event is now set as the total number of search results (when available)
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend': minor
|
||||
'@backstage/plugin-search-backend-module-elasticsearch': minor
|
||||
'@backstage/plugin-search-backend-node': minor
|
||||
---
|
||||
|
||||
numberOfResults is now provided alongside the query result
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-explore': patch
|
||||
---
|
||||
|
||||
Update search links to only have header as linkable text
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-catalog-node': minor
|
||||
---
|
||||
|
||||
Make it possible for entity providers to supply only entity refs, instead of full entities, in `delta` mutation deletions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-bazaar': patch
|
||||
---
|
||||
|
||||
Add `title` as optional parameter to `BazaarOverviewCard`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Made AlertDisplay not crash on undefined messages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updated dependency `minimatch` to `5.1.1` and switch version range to `^`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updated dependency `@rollup/plugin-commonjs` to `^23.0.0`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Updated dependency `@rollup/plugin-json` to `^5.0.0`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
---
|
||||
|
||||
Set the default `maxDepth` prop for `EntityRelationsGraph` to a smaller value to provide better readability.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-common': minor
|
||||
---
|
||||
|
||||
numberOfResults (total number of results for a given query) can now be provided by each search engine and consumed as part of the search results response
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/cli': patch
|
||||
'@backstage/cli-common': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/dev-utils': patch
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Tweaked wording to use inclusive terminology
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
The search table also holds the original entity value now and the facets endpoint fetches the filtered entity data from the search table.
|
||||
@@ -27,8 +27,8 @@ jobs:
|
||||
with:
|
||||
ref: '${{ github.event.pull_request.merge_commit_sha }}'
|
||||
|
||||
- name: fetch base
|
||||
run: git fetch --depth 1 origin ${{ github.event.pull_request.base.sha }}
|
||||
- name: fetch head & base
|
||||
run: git fetch --depth 1 origin ${{ github.event.pull_request.head.sha }} ${{ github.event.pull_request.base.sha }}
|
||||
|
||||
# We avoid using the in-source script since this workflow has elevated permissions that we don't want to expose
|
||||
- name: Generate Message
|
||||
|
||||
@@ -48,7 +48,7 @@ Once you've saved the action, you should see an authentication flow be triggered
|
||||
### Frontend
|
||||
|
||||
The Backstage App needs a SignInPage when authentication is required.
|
||||
When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token.
|
||||
When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a placeholder SignIn component that can refresh the token.
|
||||
|
||||
- edit `packages/app/src/App.tsx`
|
||||
- import the following two additional definitions from `@backstage/core-plugin-api`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB
|
||||
@@ -59,7 +59,7 @@ import React from 'react';
|
||||
import { UserIdentity } from '@backstage/core-components';
|
||||
import { SignInPageProps } from '@backstage/core-app-api';
|
||||
|
||||
const DummySignInComponent: any = (props: SignInPageProps) => {
|
||||
const SampleSignInComponent: any = (props: SignInPageProps) => {
|
||||
const [error, setError] = React.useState<string | undefined>();
|
||||
const config = useApi(configApiRef);
|
||||
React.useEffect(() => {
|
||||
@@ -102,13 +102,13 @@ const DummySignInComponent: any = (props: SignInPageProps) => {
|
||||
};
|
||||
```
|
||||
|
||||
- add `DummySignInComponent` as `SignInPage`:
|
||||
- add `SampleSignInComponent` as `SignInPage`:
|
||||
|
||||
```ts
|
||||
const app = createApp({
|
||||
...
|
||||
components: {
|
||||
SignInPage: DummySignInComponent,
|
||||
SignInPage: SampleSignInComponent,
|
||||
...
|
||||
},
|
||||
...
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ begun work on various aspects of all three phases. Looking at the
|
||||
[milestones for active issues](https://github.com/backstage/backstage/milestones)
|
||||
will also give you a sense of our progress.
|
||||
|
||||
### My company doesn't have thousands of developers or services. Is Backstage overkill?
|
||||
### My company doesn't have thousands of developers or services. Is using Backstage excessive for our needs?
|
||||
|
||||
Not at all! A core reason to adopt Backstage is to standardize how software is
|
||||
built at your company. It's easier to decide on those standards as a small
|
||||
|
||||
@@ -10,7 +10,7 @@ description: Architecture Decision Record (ADR) for Luxon Date Library
|
||||
Date formatting (e.g. `a day ago`) and calculations are common within Backstage.
|
||||
Some of these useful features are not supported by the standard JavaScript
|
||||
`Date` object. The popular [Moment.js](https://momentjs.com/) library has been
|
||||
commonly used to fill this gap but suffers from large bundle sizes and mutable
|
||||
commonly used to fill this gap but is affected by large bundle sizes and mutable
|
||||
state issues. On top of this, `momentjs` is
|
||||
[being sunset](https://momentjs.com/docs/#/-project-status/) and the project
|
||||
recommends using one of the more modern alternative libraries.
|
||||
|
||||
@@ -63,7 +63,7 @@ export default async function createPlugin(
|
||||
// and the access token and produces the Backstage token with the
|
||||
// relevant user info.
|
||||
async resolver({ profile, result }, ctx) {
|
||||
// Somehow compute the Backstage token claims. Just some dummy code
|
||||
// Somehow compute the Backstage token claims. Just some sample code
|
||||
// shown here, but you may want to query your LDAP server, or
|
||||
// https://<teamName>.cloudflareaccess.com/cdn-cgi/access/get-identity
|
||||
// https://developers.cloudflare.com/cloudflare-one/identity/users/validating-json/#groups-within-a-jwt
|
||||
|
||||
@@ -70,7 +70,7 @@ export default async function createPlugin(
|
||||
// and the IAP token and produces the Backstage token with the
|
||||
// relevant user info.
|
||||
async resolver({ profile, result: { iapToken } }, ctx) {
|
||||
// Somehow compute the Backstage token claims. Just some dummy code
|
||||
// Somehow compute the Backstage token claims. Just some sample code
|
||||
// shown here, but you may want to query your LDAP server, or
|
||||
// GSuite or similar, based on the IAP token sub/email claims
|
||||
const id = iapToken.email.split('@')[0];
|
||||
|
||||
@@ -256,7 +256,7 @@ bash-5.1# exit
|
||||
|
||||
The database pod is running, but how does another pod connect to it?
|
||||
|
||||
Kubernetes pods are transient - they can be killed, restarted, or created
|
||||
Kubernetes pods are transient - they can be stopped, restarted, or created
|
||||
dynamically. Therefore we don't want to try to connect to pods directly, but
|
||||
rather create a Kubernetes Service. Services keep track of pods and direct
|
||||
traffic to the right place.
|
||||
|
||||
@@ -50,7 +50,7 @@ components need to be in a known machine-readable format so we can build further
|
||||
tooling and analysis on top.
|
||||
|
||||
APIs have a visibility: they are either public (making them available for any
|
||||
other component to consume), restricted (only available to a whitelisted set of
|
||||
other component to consume), restricted (only available to an allowlisted set of
|
||||
consumers), or private (only available within their system). As public APIs are
|
||||
going to be the primary way interaction between components, Backstage supports
|
||||
documenting, indexing and searching all APIs so we can browse them as
|
||||
|
||||
@@ -260,7 +260,7 @@ development dependency only.
|
||||
|
||||
It can sometimes be difficult to decide where to place your plugin code. For example
|
||||
should it go directly in the `-backend` plugin package or in the `-node` package?
|
||||
As a rule of thumb you should try to keep the exposure of your code as low
|
||||
As a general guideline you should try to keep the exposure of your code as low
|
||||
as possible. If it doesn't need to be public API, it's best to avoid. If you don't
|
||||
need it to be used by other plugins, then keep it directly in the plugin packages.
|
||||
|
||||
|
||||
@@ -52,13 +52,13 @@ learn how to contribute the integration yourself!
|
||||
The following table summarizes events that, depending on the plugins you have
|
||||
installed, may be captured.
|
||||
|
||||
| Action | Subject | Other Notes |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| `navigate` | The URL of the page that was navigated to | |
|
||||
| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to |
|
||||
| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) |
|
||||
| `search` | The search term entered in any search bar component | The context holds `searchTypes`, representing `types` constraining the search |
|
||||
| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided |
|
||||
| Action | Subject | Other Notes |
|
||||
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `navigate` | The URL of the page that was navigated to | |
|
||||
| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to |
|
||||
| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) |
|
||||
| `search` | The search term entered in any search bar component | - The context holds `searchTypes`, representing `types` constraining the search. The `value` represents the total number of search results for the query. This may not be visible if the permission framework is being used. |
|
||||
| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided |
|
||||
|
||||
If there is an event you'd like to see captured, please [open an
|
||||
issue][add-event] describing the event you want to see and the questions it
|
||||
|
||||
@@ -51,7 +51,7 @@ terminal window, now run
|
||||
curl localhost:7007/carmen/health
|
||||
```
|
||||
|
||||
This should return `{"status":"ok"}`. Success! Press `Ctrl + c` to kill it
|
||||
This should return `{"status":"ok"}`. Success! Press `Ctrl + c` to stop it
|
||||
again.
|
||||
|
||||
## Developing your Backend Plugin
|
||||
|
||||
@@ -123,7 +123,7 @@ than simply letting them know they had an invalid input.
|
||||
> input change.
|
||||
|
||||
This piggybacks the Scalability Principle and applies primarily to frontend
|
||||
development. As a general rule of thumb, frontends should be flexible enough so
|
||||
development. As a general guideline, frontends should be flexible enough so
|
||||
that the UX or design can change while touching the least amount of code
|
||||
possible. So for example, a poor unit test would verify the color of a button
|
||||
when it is hovered. This would be a poor unit test, because if you decide to
|
||||
@@ -353,7 +353,7 @@ For more information:
|
||||
### Accessing `store`, `theme`, routing, browser history, etc.
|
||||
|
||||
The Backstage application has several core providers at its root. To run your
|
||||
test wrapped in a "dummy" Backstage application, you can use our utility
|
||||
test wrapped in a "sample" Backstage application, you can use our utility
|
||||
functions:
|
||||
|
||||
**`wrapInTestApp`**
|
||||
|
||||
@@ -268,7 +268,7 @@ async function main() {
|
||||
```
|
||||
|
||||
This will be run every time you restart the backend. Note that after any change
|
||||
in the URL Reader code, you need to kill the backend and restart, since the
|
||||
in the URL Reader code, you need to stop the backend and restart, since the
|
||||
`reader` instance is memoized and does not update on hot module reloading. Also,
|
||||
there are a lot of unit tests written for the URL Readers, which you can make
|
||||
use of.
|
||||
|
||||
@@ -29,7 +29,7 @@ We started by creating a fork from React Native’s open source project, applyin
|
||||
|
||||
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.
|
||||
Whenever a new version of Backstage is released, the Helper scaffolds a new sample 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.
|
||||
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ export class CacheManager {
|
||||
getClient: (opts = {}): CacheClient => {
|
||||
const concreteClient = this.getClientWithTtl(pluginId, opts.defaultTtl);
|
||||
|
||||
// Always provide an error handler to avoid killing the process.
|
||||
// Always provide an error handler to avoid stopping the process.
|
||||
concreteClient.on('error', (err: Error) => {
|
||||
// In all cases, just log the error.
|
||||
this.logger.error(err);
|
||||
|
||||
@@ -133,7 +133,7 @@ describe('AwsS3UrlReader', () => {
|
||||
});
|
||||
};
|
||||
|
||||
it('creates a dummy reader without the awsS3 field', () => {
|
||||
it('creates a sample reader without the awsS3 field', () => {
|
||||
const entries = createReader({
|
||||
integrations: {},
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@ const TOKEN_EXPIRY_AFTER = Duration.fromObject({ hours: 1 });
|
||||
const TOKEN_REISSUE_AFTER = Duration.fromObject({ minutes: 10 });
|
||||
|
||||
/**
|
||||
* A token manager that issues static dummy tokens and never fails
|
||||
* A token manager that issues static fake tokens and never fails
|
||||
* authentication. This can be useful for testing.
|
||||
*/
|
||||
class NoopTokenManager implements TokenManager {
|
||||
@@ -66,7 +66,7 @@ export class ServerTokenManager implements TokenManager {
|
||||
private currentTokenPromise: Promise<{ token: string }> | undefined;
|
||||
|
||||
/**
|
||||
* Creates a token manager that issues static dummy tokens and never fails
|
||||
* Creates a token manager that issues static fake tokens and never fails
|
||||
* authentication. This can be useful for testing.
|
||||
*/
|
||||
static noop(): TokenManager {
|
||||
|
||||
@@ -64,7 +64,7 @@ export function findRootPath(
|
||||
): string | undefined {
|
||||
let path = searchDir;
|
||||
|
||||
// Some sanity check to avoid infinite loop
|
||||
// Some confidence check to avoid infinite loop
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const packagePath = resolvePath(path, 'package.json');
|
||||
const exists = fs.existsSync(packagePath);
|
||||
|
||||
@@ -231,7 +231,7 @@ function createConfigForRole(dir, role, extraConfig = {}) {
|
||||
},
|
||||
restrictedImports: [
|
||||
{
|
||||
// Importing the entire MUI icons packages kills build performance as the list of icons is huge.
|
||||
// Importing the entire MUI icons packages impedes build performance as the list of icons is huge.
|
||||
name: '@material-ui/icons',
|
||||
message: "Please import '@material-ui/icons/<Icon>' instead.",
|
||||
},
|
||||
|
||||
@@ -82,7 +82,7 @@ async function getProjectConfig(targetPath, displayName) {
|
||||
let closestPkgJson = undefined;
|
||||
let currentPath = targetPath;
|
||||
|
||||
// Some sanity check to avoid infinite loop
|
||||
// Some confidence check to avoid infinite loop
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const packagePath = path.resolve(currentPath, 'package.json');
|
||||
const exists = fs.pathExistsSync(packagePath);
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@octokit/request": "^6.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-commonjs": "^23.0.0",
|
||||
"@rollup/plugin-json": "^5.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.6",
|
||||
"@rollup/plugin-yaml": "^3.1.0",
|
||||
"@spotify/eslint-config-base": "^14.0.0",
|
||||
@@ -96,7 +96,7 @@
|
||||
"json-schema": "^0.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mini-css-extract-plugin": "^2.4.2",
|
||||
"minimatch": "5.1.0",
|
||||
"minimatch": "^5.1.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"npm-packlist": "^5.0.0",
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('formatErrorMessage with esbuild plugin error', () => {
|
||||
plugin: 'esbuild',
|
||||
message: 'test',
|
||||
id: 'index.js',
|
||||
errors: [{ text: 'Dummy', location: { line: 1, column: 1 } }],
|
||||
errors: [{ text: 'Sample', location: { line: 1, column: 1 } }],
|
||||
});
|
||||
expect(msg).toContain('test\n\n');
|
||||
});
|
||||
|
||||
@@ -114,7 +114,7 @@ export function forwardFileImports(options: ForwardFileImportsOptions): Plugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sanity check, dunno if this can happen
|
||||
// Confidence check, dunno if this can happen
|
||||
if (!importer) {
|
||||
throw new Error(`Unknown importer of file module ${id}`);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('LinkedPackageResolvePlugin', () => {
|
||||
const callbackFalse = jest.fn();
|
||||
tap(
|
||||
{
|
||||
request: 'dummy',
|
||||
request: 'sample',
|
||||
path: false,
|
||||
},
|
||||
'some-context',
|
||||
|
||||
@@ -116,7 +116,7 @@ function getBasePath(configApi: Config) {
|
||||
function readBasePath(configApi: ConfigApi) {
|
||||
let { pathname } = new URL(
|
||||
configApi.getOptionalString('app.baseUrl') ?? '/',
|
||||
'http://dummy.dev', // baseUrl can be specified as just a path
|
||||
'http://sample.dev', // baseUrl can be specified as just a path
|
||||
);
|
||||
pathname = pathname.replace(/\/*$/, '');
|
||||
return pathname;
|
||||
|
||||
@@ -83,7 +83,7 @@ export function AlertDisplay(props: AlertDisplayProps) {
|
||||
severity={firstMessage.severity}
|
||||
>
|
||||
<span>
|
||||
{firstMessage.message.toString()}
|
||||
{String(firstMessage.message)}
|
||||
{messages.length > 1 && (
|
||||
<em>{` (${messages.length - 1} older ${pluralize(
|
||||
'message',
|
||||
|
||||
@@ -80,7 +80,7 @@ const useBaseUrl = () => {
|
||||
*/
|
||||
const useBasePath = () => {
|
||||
// baseUrl can be specified as just a path
|
||||
const base = 'http://dummy.dev';
|
||||
const base = 'http://sample.dev';
|
||||
const url = useBaseUrl() ?? '/';
|
||||
const { pathname } = new URL(url, base);
|
||||
return trimEnd(pathname, '/');
|
||||
|
||||
@@ -23,7 +23,7 @@ export default {
|
||||
};
|
||||
|
||||
const text =
|
||||
'Lorem Ipsum is simply dummy text of the printing and typesetting industry.';
|
||||
'Lorem Ipsum is simply sample text of the printing and typesetting industry.';
|
||||
|
||||
export const Default = () => (
|
||||
<Box maxWidth="200px">
|
||||
|
||||
@@ -912,7 +912,7 @@
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5aa4ceea6: Make sure to provide dummy routes for all external routes of plugins given to DevApp
|
||||
- 5aa4ceea6: Make sure to provide sample routes for all external routes of plugins given to DevApp
|
||||
- Updated dependencies [3a58084b6]
|
||||
- Updated dependencies [e799e74d4]
|
||||
- Updated dependencies [dc12852c9]
|
||||
|
||||
@@ -165,9 +165,9 @@ export class DevAppBuilder {
|
||||
* Build a DevApp component using the resources registered so far
|
||||
*/
|
||||
build(): ComponentType<{}> {
|
||||
const dummyRouteRef = createRouteRef({ id: 'dummy' });
|
||||
const DummyPage = () => <Box p={3}>Page belonging to another plugin.</Box>;
|
||||
attachComponentData(DummyPage, 'core.mountPoint', dummyRouteRef);
|
||||
const fakeRouteRef = createRouteRef({ id: 'fake' });
|
||||
const FakePage = () => <Box p={3}>Page belonging to another plugin.</Box>;
|
||||
attachComponentData(FakePage, 'core.mountPoint', fakeRouteRef);
|
||||
|
||||
const apis = [...this.apis];
|
||||
if (!apis.some(api => api.api.id === scmIntegrationsApiRef.id)) {
|
||||
@@ -188,7 +188,7 @@ export class DevAppBuilder {
|
||||
for (const plugin of this.plugins ?? []) {
|
||||
const targets: Record<string, RouteRef<any>> = {};
|
||||
for (const routeKey of Object.keys(plugin.externalRoutes)) {
|
||||
targets[routeKey] = dummyRouteRef;
|
||||
targets[routeKey] = fakeRouteRef;
|
||||
}
|
||||
bind(plugin.externalRoutes, targets);
|
||||
}
|
||||
@@ -215,7 +215,7 @@ export class DevAppBuilder {
|
||||
</Sidebar>
|
||||
<FlatRoutes>
|
||||
{this.routes}
|
||||
<Route path="/_external_route" element={<DummyPage />} />
|
||||
<Route path="/_external_route" element={<FakePage />} />
|
||||
</FlatRoutes>
|
||||
</SidebarPage>
|
||||
</AppRouter>
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('end-to-end', () => {
|
||||
|
||||
afterEach(async () => {
|
||||
// On Windows the pid of a spawned process may be wrong
|
||||
// Because of this, we should be kill the MKDocs after the test
|
||||
// Because of this, we should stop the MKDocs after the test
|
||||
// (e.g. https://github.com/nodejs/node/issues/4289#issuecomment-854270414)
|
||||
if (process.platform === 'win32') {
|
||||
const procs = await findProcess('name', 'mkdocs', true);
|
||||
|
||||
@@ -63,7 +63,7 @@ export const run = async (
|
||||
return childProcess;
|
||||
};
|
||||
|
||||
// Block indefinitely and wait for a signal to kill the child process(es)
|
||||
// Block indefinitely and wait for a signal to stop the child process(es)
|
||||
// Throw error if any child process errors
|
||||
// Resolves only when all processes exit with status code 0
|
||||
export async function waitForSignal(
|
||||
|
||||
@@ -49,6 +49,7 @@ Here's how to get the backend up and running:
|
||||
return createRouter({
|
||||
logger: env.logger,
|
||||
config: env.config,
|
||||
reader: env.reader,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -80,13 +80,15 @@ export const homePage = (
|
||||
+ </Grid>
|
||||
|
||||
+ <Grid item xs={12} >
|
||||
+ <BazaarOverviewCard order='random' fullWidth fullHeight />
|
||||
+ <BazaarOverviewCard title='My Orgs Projects' order='random' fullWidth fullHeight />
|
||||
+ </Grid>
|
||||
|
||||
{/* ...other homepage items */}
|
||||
```
|
||||
|
||||
The properties `fullHeight` and `fullWidth` are optional and can be used to adjust the cards styling.
|
||||
The property `title` is optional and can be used to customize the title in the card header. If no title is submitted the default titles `Bazaar Random Projects` or `Bazaar Latest Projects` are displayed.
|
||||
|
||||
The properties `fullHeight` and `fullWidth` are also optional and can be used to adjust the cards styling.
|
||||
|
||||
## How does the Bazaar work?
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const BazaarOverviewCard: (
|
||||
|
||||
// @public (undocumented)
|
||||
export type BazaarOverviewCardProps = {
|
||||
title?: string;
|
||||
order: 'latest' | 'random';
|
||||
fullWidth?: boolean;
|
||||
fullHeight?: boolean;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
@@ -31,6 +31,7 @@ import StorefrontIcon from '@material-ui/icons/Storefront';
|
||||
|
||||
/** @public */
|
||||
export type BazaarOverviewCardProps = {
|
||||
title?: string;
|
||||
order: 'latest' | 'random';
|
||||
fullWidth?: boolean;
|
||||
fullHeight?: boolean;
|
||||
@@ -51,11 +52,14 @@ const getUnlinkedCatalogEntities = (
|
||||
|
||||
/** @public */
|
||||
export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => {
|
||||
const { order, fullWidth = false, fullHeight = false } = props;
|
||||
const { title, order, fullWidth = false, fullHeight = false } = props;
|
||||
const bazaarApi = useApi(bazaarApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const root = useRouteRef(bazaarPlugin.routes.root);
|
||||
|
||||
const defaultTitle =
|
||||
order === 'latest' ? 'Bazaar Latest Projects' : 'Bazaar Random Projects';
|
||||
|
||||
const bazaarLink = {
|
||||
title: 'Go to Bazaar',
|
||||
link: `${root()}`,
|
||||
@@ -127,9 +131,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => {
|
||||
|
||||
return (
|
||||
<InfoCard
|
||||
title={
|
||||
order === 'latest' ? 'Bazaar Latest Projects' : 'Bazaar Random Projects'
|
||||
}
|
||||
title={title ?? defaultTitle}
|
||||
action={
|
||||
<IconButton>
|
||||
<Link to={bazaarLink.link} title={bazaarLink.title}>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
*/
|
||||
exports.up = async function up(knex) {
|
||||
// Start out with an original_value column that's equal to the value column
|
||||
await knex.schema.alterTable('search', table => {
|
||||
table
|
||||
.string('original_value')
|
||||
.nullable()
|
||||
.comment('Holds the corresponding original case sensitive value');
|
||||
});
|
||||
await knex('search').update({ original_value: knex.ref('value') });
|
||||
|
||||
// Make sure to reprocess everything, to make sure that the original_value
|
||||
// column is populated with values with the proper casing. It's unfortunately
|
||||
// not enough to just reset the final_entities hash, since stitching is driven
|
||||
// only by processing resulting in data that isn't matching the refresh_state
|
||||
// hash.
|
||||
await knex('refresh_state').update({
|
||||
result_hash: '',
|
||||
next_update_at: knex.fn.now(),
|
||||
});
|
||||
await knex('final_entities').update({ hash: '' });
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
*/
|
||||
exports.down = async function down(knex) {
|
||||
await knex.schema.alterTable('search', table => {
|
||||
table.dropColumn('original_value');
|
||||
});
|
||||
};
|
||||
@@ -991,13 +991,7 @@ describe('Default Processing Database', () => {
|
||||
added: [],
|
||||
removed: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: '1.0.0',
|
||||
metadata: {
|
||||
name: 'new-root',
|
||||
},
|
||||
kind: 'Location',
|
||||
} as Entity,
|
||||
entityRef: 'location:default/new-root',
|
||||
locationKey: 'file:/tmp/foobar',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -737,7 +737,7 @@ export class DefaultProcessingDatabase implements ProcessingDatabase {
|
||||
deferred: e,
|
||||
hash: generateStableHash(e.entity),
|
||||
})),
|
||||
toRemove: options.removed.map(e => stringifyEntityRef(e.entity)),
|
||||
toRemove: options.removed.map(e => e.entityRef),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -71,5 +71,6 @@ export type DbFinalEntitiesRow = {
|
||||
export type DbSearchRow = {
|
||||
entity_id: string;
|
||||
key: string;
|
||||
original_value: string | null;
|
||||
value: string | null;
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ export type ReplaceUnprocessedEntitiesOptions =
|
||||
| {
|
||||
sourceKey: string;
|
||||
added: DeferredEntity[];
|
||||
removed: DeferredEntity[];
|
||||
removed: { entityRef: string; locationKey?: string }[];
|
||||
type: 'delta';
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import {
|
||||
Entity,
|
||||
entityEnvelopeSchemaValidator,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ProcessingDatabase } from '../database/types';
|
||||
import {
|
||||
@@ -50,13 +51,24 @@ class Connection implements EntityProviderConnection {
|
||||
});
|
||||
} else if (mutation.type === 'delta') {
|
||||
this.check(mutation.added.map(e => e.entity));
|
||||
this.check(mutation.removed.map(e => e.entity));
|
||||
this.check(
|
||||
mutation.removed
|
||||
.map(e => ('entity' in e ? e.entity : undefined))
|
||||
.filter((e): e is Entity => Boolean(e)),
|
||||
);
|
||||
await db.transaction(async tx => {
|
||||
await db.replaceUnprocessedEntities(tx, {
|
||||
sourceKey: this.config.id,
|
||||
type: 'delta',
|
||||
added: mutation.added,
|
||||
removed: mutation.removed,
|
||||
removed: mutation.removed.map(r =>
|
||||
'entityRef' in r
|
||||
? r
|
||||
: {
|
||||
entityRef: stringifyEntityRef(r.entity),
|
||||
locationKey: r.locationKey,
|
||||
},
|
||||
),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
DbSearchRow,
|
||||
} from '../database/tables';
|
||||
import { Stitcher } from '../stitching/Stitcher';
|
||||
import { buildEntitySearch } from '../stitching/buildEntitySearch';
|
||||
import { DefaultEntitiesCatalog } from './DefaultEntitiesCatalog';
|
||||
|
||||
describe('DefaultEntitiesCatalog', () => {
|
||||
@@ -100,29 +101,14 @@ describe('DefaultEntitiesCatalog', () => {
|
||||
stitch_ticket: '',
|
||||
});
|
||||
|
||||
await insertSearchRow(knex, id, null, entity);
|
||||
}
|
||||
|
||||
async function insertSearchRow(
|
||||
knex: Knex,
|
||||
id: string,
|
||||
previousKey: string | null,
|
||||
previousValue: Object,
|
||||
) {
|
||||
return Promise.all(
|
||||
Object.entries(previousValue).map(async ([key, value]) => {
|
||||
const currentKey = `${previousKey ? `${previousKey}.` : ``}${key}`;
|
||||
if (typeof value === 'object') {
|
||||
await insertSearchRow(knex, id, currentKey, value);
|
||||
} else {
|
||||
await knex<DbSearchRow>('search').insert({
|
||||
entity_id: id,
|
||||
key: currentKey,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
for (const row of buildEntitySearch(id, entity)) {
|
||||
await knex<DbSearchRow>('search').insert({
|
||||
entity_id: id,
|
||||
key: row.key,
|
||||
value: row.value,
|
||||
original_value: row.original_value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -792,8 +778,8 @@ describe('DefaultEntitiesCatalog', () => {
|
||||
facets: {
|
||||
'metadata.tags': expect.arrayContaining([
|
||||
{ value: 'java', count: 2 },
|
||||
{ value: 'rust', count: 1 },
|
||||
{ value: 'node', count: 1 },
|
||||
{ value: 'rust', count: 1 },
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { InputError, NotFoundError } from '@backstage/errors';
|
||||
import { Knex } from 'knex';
|
||||
import lodash from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
EntitiesBatchRequest,
|
||||
EntitiesBatchResponse,
|
||||
@@ -91,12 +91,13 @@ function addCondition(
|
||||
db: Knex,
|
||||
filter: EntitiesSearchFilter,
|
||||
negate: boolean = false,
|
||||
entityIdField = 'entity_id',
|
||||
) {
|
||||
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
|
||||
// make a lot of sense. However, it had abysmal performance on sqlite
|
||||
// when datasets grew large, so we're using IN instead.
|
||||
const matchQuery = db<DbSearchRow>('search')
|
||||
.select('entity_id')
|
||||
.select(entityIdField)
|
||||
.where({ key: filter.key.toLowerCase() })
|
||||
.andWhere(function keyFilter() {
|
||||
if (filter.values) {
|
||||
@@ -111,7 +112,7 @@ function addCondition(
|
||||
}
|
||||
}
|
||||
});
|
||||
queryBuilder.andWhere('entity_id', negate ? 'not in' : 'in', matchQuery);
|
||||
queryBuilder.andWhere(entityIdField, negate ? 'not in' : 'in', matchQuery);
|
||||
}
|
||||
|
||||
function isEntitiesSearchFilter(
|
||||
@@ -137,25 +138,30 @@ function parseFilter(
|
||||
query: Knex.QueryBuilder,
|
||||
db: Knex,
|
||||
negate: boolean = false,
|
||||
entityIdField = 'entity_id',
|
||||
): Knex.QueryBuilder {
|
||||
if (isEntitiesSearchFilter(filter)) {
|
||||
return query.andWhere(function filterFunction() {
|
||||
addCondition(this, db, filter, negate);
|
||||
addCondition(this, db, filter, negate, entityIdField);
|
||||
});
|
||||
}
|
||||
|
||||
if (isNegationEntityFilter(filter)) {
|
||||
return parseFilter(filter.not, query, db, !negate);
|
||||
return parseFilter(filter.not, query, db, !negate, entityIdField);
|
||||
}
|
||||
|
||||
return query[negate ? 'andWhereNot' : 'andWhere'](function filterFunction() {
|
||||
if (isOrEntityFilter(filter)) {
|
||||
for (const subFilter of filter.anyOf ?? []) {
|
||||
this.orWhere(subQuery => parseFilter(subFilter, subQuery, db));
|
||||
this.orWhere(subQuery =>
|
||||
parseFilter(subFilter, subQuery, db, false, entityIdField),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (const subFilter of filter.allOf ?? []) {
|
||||
this.andWhere(subQuery => parseFilter(subFilter, subQuery, db));
|
||||
this.andWhere(subQuery =>
|
||||
parseFilter(subFilter, subQuery, db, false, entityIdField),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -190,7 +196,6 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
|
||||
}
|
||||
|
||||
let rows = await entitiesQuery;
|
||||
|
||||
let pageInfo: DbPageInfo;
|
||||
if (limit === undefined || rows.length <= limit) {
|
||||
pageInfo = { hasNextPage: false };
|
||||
@@ -392,44 +397,26 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
|
||||
}
|
||||
|
||||
async facets(request: EntityFacetsRequest): Promise<EntityFacetsResponse> {
|
||||
const { entities } = await this.entities({
|
||||
filter: request.filter,
|
||||
authorizationToken: request.authorizationToken,
|
||||
});
|
||||
|
||||
const facets: EntityFacetsResponse['facets'] = {};
|
||||
const db = this.database;
|
||||
|
||||
for (const facet of request.facets) {
|
||||
const values = entities
|
||||
.map(entity => {
|
||||
// TODO(freben): Generalize this code to handle any field that may
|
||||
// have dots in its key?
|
||||
if (facet.startsWith('metadata.annotations.')) {
|
||||
return entity.metadata.annotations?.[
|
||||
facet.substring('metadata.annotations.'.length)
|
||||
];
|
||||
} else if (facet.startsWith('metadata.labels.')) {
|
||||
return entity.metadata.labels?.[
|
||||
facet.substring('metadata.labels.'.length)
|
||||
];
|
||||
}
|
||||
return lodash.get(entity, facet);
|
||||
})
|
||||
.flatMap(field => {
|
||||
if (typeof field === 'string') {
|
||||
return [field];
|
||||
} else if (Array.isArray(field)) {
|
||||
return field.filter(i => typeof i === 'string');
|
||||
}
|
||||
return [];
|
||||
})
|
||||
.sort();
|
||||
const dbQuery = db<DbSearchRow>('search')
|
||||
.join('final_entities', 'search.entity_id', 'final_entities.entity_id')
|
||||
.where('search.key', facet.toLocaleLowerCase('en-US'))
|
||||
.count('search.entity_id as count')
|
||||
.select({ value: 'search.original_value' })
|
||||
.groupBy('search.original_value');
|
||||
|
||||
const counts = lodash.countBy(values, lodash.identity);
|
||||
if (request?.filter) {
|
||||
parseFilter(request.filter, dbQuery, db, false, 'search.entity_id');
|
||||
}
|
||||
|
||||
facets[facet] = Object.entries(counts).map(([value, count]) => ({
|
||||
value,
|
||||
count,
|
||||
const result = await dbQuery;
|
||||
|
||||
facets[facet] = result.map(data => ({
|
||||
value: String(data.value),
|
||||
count: Number(data.count),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -115,13 +115,43 @@ describe('Stitcher', () => {
|
||||
const search = await db<DbSearchRow>('search');
|
||||
expect(search).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ entity_id: 'my-id', key: 'relations.looksat', value: 'k:ns/other' },
|
||||
{ entity_id: 'my-id', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'my-id', key: 'kind', value: 'k' },
|
||||
{ entity_id: 'my-id', key: 'metadata.name', value: 'n' },
|
||||
{ entity_id: 'my-id', key: 'metadata.namespace', value: 'ns' },
|
||||
{ entity_id: 'my-id', key: 'metadata.uid', value: 'my-id' },
|
||||
{ entity_id: 'my-id', key: 'spec.k', value: 'v' },
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'relations.looksat',
|
||||
original_value: 'k:ns/other',
|
||||
value: 'k:ns/other',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'apiversion',
|
||||
original_value: 'a',
|
||||
value: 'a',
|
||||
},
|
||||
{ entity_id: 'my-id', key: 'kind', original_value: 'k', value: 'k' },
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.name',
|
||||
original_value: 'n',
|
||||
value: 'n',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.namespace',
|
||||
original_value: 'ns',
|
||||
value: 'ns',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.uid',
|
||||
original_value: 'my-id',
|
||||
value: 'my-id',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'spec.k',
|
||||
original_value: 'v',
|
||||
value: 'v',
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -179,14 +209,49 @@ describe('Stitcher', () => {
|
||||
|
||||
expect(await db<DbSearchRow>('search')).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ entity_id: 'my-id', key: 'relations.looksat', value: 'k:ns/other' },
|
||||
{ entity_id: 'my-id', key: 'relations.looksat', value: 'k:ns/third' },
|
||||
{ entity_id: 'my-id', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'my-id', key: 'kind', value: 'k' },
|
||||
{ entity_id: 'my-id', key: 'metadata.name', value: 'n' },
|
||||
{ entity_id: 'my-id', key: 'metadata.namespace', value: 'ns' },
|
||||
{ entity_id: 'my-id', key: 'metadata.uid', value: 'my-id' },
|
||||
{ entity_id: 'my-id', key: 'spec.k', value: 'v' },
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'relations.looksat',
|
||||
original_value: 'k:ns/other',
|
||||
value: 'k:ns/other',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'relations.looksat',
|
||||
original_value: 'k:ns/third',
|
||||
value: 'k:ns/third',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'apiversion',
|
||||
original_value: 'a',
|
||||
value: 'a',
|
||||
},
|
||||
{ entity_id: 'my-id', key: 'kind', original_value: 'k', value: 'k' },
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.name',
|
||||
original_value: 'n',
|
||||
value: 'n',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.namespace',
|
||||
original_value: 'ns',
|
||||
value: 'ns',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'metadata.uid',
|
||||
original_value: 'my-id',
|
||||
value: 'my-id',
|
||||
},
|
||||
{
|
||||
entity_id: 'my-id',
|
||||
key: 'spec.k',
|
||||
original_value: 'v',
|
||||
value: 'v',
|
||||
},
|
||||
]),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -88,19 +88,26 @@ describe('buildEntitySearch', () => {
|
||||
];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'a', value: 'true' },
|
||||
{ entity_id: 'eid', key: 'b', value: 'false' },
|
||||
{ entity_id: 'eid', key: 'c', value: '7' },
|
||||
{ entity_id: 'eid', key: 'd', value: 'string' },
|
||||
{ entity_id: 'eid', key: 'e', value: null },
|
||||
{ entity_id: 'eid', key: 'f', value: null },
|
||||
{ entity_id: 'eid', key: 'a', original_value: 'true', value: 'true' },
|
||||
{ entity_id: 'eid', key: 'b', original_value: 'false', value: 'false' },
|
||||
{ entity_id: 'eid', key: 'c', original_value: '7', value: '7' },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'd',
|
||||
original_value: 'string',
|
||||
value: 'string',
|
||||
},
|
||||
{ entity_id: 'eid', key: 'e', original_value: null, value: null },
|
||||
{ entity_id: 'eid', key: 'f', original_value: null, value: null },
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits lowercase version of keys and values', () => {
|
||||
it('emits lowercase version of keys and values and also keeps the original value', () => {
|
||||
const input = [{ key: 'fOo', value: 'BaR' }];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: 'bar' }]);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'foo', original_value: 'BaR', value: 'bar' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('skips very large keys', () => {
|
||||
@@ -112,7 +119,9 @@ describe('buildEntitySearch', () => {
|
||||
it('replaces very large values with null', () => {
|
||||
const input = [{ key: 'foo', value: 'a'.repeat(10000) }];
|
||||
const output = mapToRows(input, 'eid');
|
||||
expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: null }]);
|
||||
expect(output).toEqual([
|
||||
{ entity_id: 'eid', key: 'foo', original_value: null, value: null },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,14 +133,35 @@ describe('buildEntitySearch', () => {
|
||||
metadata: { name: 'n' },
|
||||
};
|
||||
expect(buildEntitySearch('eid', input)).toEqual([
|
||||
{ entity_id: 'eid', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'eid', key: 'kind', value: 'b' },
|
||||
{ entity_id: 'eid', key: 'metadata.name', value: 'n' },
|
||||
{ entity_id: 'eid', key: 'metadata.namespace', value: null },
|
||||
{ entity_id: 'eid', key: 'metadata.uid', value: null },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'apiversion',
|
||||
original_value: 'a',
|
||||
value: 'a',
|
||||
},
|
||||
{ entity_id: 'eid', key: 'kind', original_value: 'b', value: 'b' },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.name',
|
||||
original_value: 'n',
|
||||
value: 'n',
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.namespace',
|
||||
original_value: null,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.uid',
|
||||
original_value: null,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.namespace',
|
||||
original_value: DEFAULT_NAMESPACE,
|
||||
value: DEFAULT_NAMESPACE,
|
||||
},
|
||||
]);
|
||||
@@ -154,18 +184,49 @@ describe('buildEntitySearch', () => {
|
||||
metadata: { name: 'n' },
|
||||
};
|
||||
expect(buildEntitySearch('eid', input)).toEqual([
|
||||
{ entity_id: 'eid', key: 'apiversion', value: 'a' },
|
||||
{ entity_id: 'eid', key: 'kind', value: 'b' },
|
||||
{ entity_id: 'eid', key: 'metadata.name', value: 'n' },
|
||||
{ entity_id: 'eid', key: 'metadata.namespace', value: null },
|
||||
{ entity_id: 'eid', key: 'metadata.uid', value: null },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'apiversion',
|
||||
original_value: 'a',
|
||||
value: 'a',
|
||||
},
|
||||
{ entity_id: 'eid', key: 'kind', original_value: 'b', value: 'b' },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.name',
|
||||
original_value: 'n',
|
||||
value: 'n',
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.namespace',
|
||||
original_value: null,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.uid',
|
||||
original_value: null,
|
||||
value: null,
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'metadata.namespace',
|
||||
original_value: DEFAULT_NAMESPACE,
|
||||
value: DEFAULT_NAMESPACE,
|
||||
},
|
||||
{ entity_id: 'eid', key: 'relations.t1', value: 'k:ns/a' },
|
||||
{ entity_id: 'eid', key: 'relations.t2', value: 'k:ns/b' },
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'relations.t1',
|
||||
original_value: 'k:ns/a',
|
||||
value: 'k:ns/a',
|
||||
},
|
||||
{
|
||||
entity_id: 'eid',
|
||||
key: 'relations.t2',
|
||||
original_value: 'k:ns/b',
|
||||
value: 'k:ns/b',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -134,15 +134,30 @@ export function mapToRows(input: Kv[], entityId: string): DbSearchRow[] {
|
||||
for (const { key: rawKey, value: rawValue } of input) {
|
||||
const key = rawKey.toLocaleLowerCase('en-US');
|
||||
if (rawValue === undefined || rawValue === null) {
|
||||
result.push({ entity_id: entityId, key, value: null });
|
||||
result.push({
|
||||
entity_id: entityId,
|
||||
key,
|
||||
original_value: null,
|
||||
value: null,
|
||||
});
|
||||
} else {
|
||||
const value = String(rawValue).toLocaleLowerCase('en-US');
|
||||
if (key.length <= MAX_KEY_LENGTH) {
|
||||
result.push({
|
||||
entity_id: entityId,
|
||||
key,
|
||||
value: value.length <= MAX_VALUE_LENGTH ? value : null,
|
||||
});
|
||||
if (value.length <= MAX_VALUE_LENGTH) {
|
||||
result.push({
|
||||
entity_id: entityId,
|
||||
key,
|
||||
original_value: String(rawValue),
|
||||
value: value,
|
||||
});
|
||||
} else {
|
||||
result.push({
|
||||
entity_id: entityId,
|
||||
key,
|
||||
original_value: null,
|
||||
value: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ export const EntityRelationsGraph = (props: {
|
||||
}) => {
|
||||
const {
|
||||
rootEntityNames,
|
||||
maxDepth = Number.POSITIVE_INFINITY,
|
||||
maxDepth = 2,
|
||||
unidirectional = true,
|
||||
mergeRelations = true,
|
||||
kinds,
|
||||
|
||||
@@ -139,10 +139,16 @@ export type EntityProviderMutation =
|
||||
| {
|
||||
type: 'delta';
|
||||
added: DeferredEntity[];
|
||||
removed: DeferredEntity[];
|
||||
removed: (
|
||||
| DeferredEntity
|
||||
| {
|
||||
entityRef: string;
|
||||
locationKey?: string;
|
||||
}
|
||||
)[];
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
// @public
|
||||
export type EntityProviderRefreshOptions = {
|
||||
keys: string[];
|
||||
};
|
||||
|
||||
@@ -17,16 +17,26 @@
|
||||
import { DeferredEntity } from '../processing';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* A 'full' mutation replaces all existing entities created by this entity provider with new ones.
|
||||
* A 'delta' mutation can both add and remove entities provided by this provider. Previously provided
|
||||
* entities from a 'full' mutation are not removed.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityProviderMutation =
|
||||
| { type: 'full'; entities: DeferredEntity[] }
|
||||
| { type: 'delta'; added: DeferredEntity[]; removed: DeferredEntity[] };
|
||||
| {
|
||||
type: 'full';
|
||||
entities: DeferredEntity[];
|
||||
}
|
||||
| {
|
||||
type: 'delta';
|
||||
added: DeferredEntity[];
|
||||
removed: (DeferredEntity | { entityRef: string; locationKey?: string })[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The options given to an entity refresh operation.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityProviderRefreshOptions = {
|
||||
@@ -34,30 +44,38 @@ export type EntityProviderRefreshOptions = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The EntityProviderConnection is the connection between the catalog and the entity provider.
|
||||
* The EntityProvider use this connection to add and remove entities from the catalog.
|
||||
* The connection between the catalog and the entity provider.
|
||||
* Entity providers use this connection to add and remove entities from the catalog.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface EntityProviderConnection {
|
||||
/**
|
||||
* Applies either a full or delta update to the catalog engine.
|
||||
* Applies either a full or a delta update to the catalog engine.
|
||||
*/
|
||||
applyMutation(mutation: EntityProviderMutation): Promise<void>;
|
||||
|
||||
/**
|
||||
* Schedules a refresh on all of the entities that has a matching refresh key associated with the provided keys.
|
||||
* Schedules a refresh on all of the entities that have a matching refresh key associated with the provided keys.
|
||||
*/
|
||||
refresh(options: EntityProviderRefreshOptions): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An EntityProvider is able to provide entities to the catalog.
|
||||
* An entity provider is able to provide entities to the catalog.
|
||||
* See https://backstage.io/docs/features/software-catalog/life-of-an-entity for more details.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface EntityProvider {
|
||||
/** Unique provider name used internally for caching. */
|
||||
/**
|
||||
* The name of the provider, which must be unique for all providers that are
|
||||
* active in a catalog, and stable over time since emitted entities are
|
||||
* related to the provider by this name.
|
||||
*/
|
||||
getProviderName(): string;
|
||||
/** Connect is called upon initialization by the catalog engine. */
|
||||
/**
|
||||
* Called upon initialization by the catalog engine.
|
||||
*/
|
||||
connect(connection: EntityProviderConnection): Promise<void>;
|
||||
}
|
||||
|
||||
+13
-11
@@ -69,7 +69,7 @@ export function ToolSearchResultListItem(props: ToolSearchResultListItemProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Link noTrack to={result.location} onClick={handleClick}>
|
||||
<>
|
||||
<ListItem alignItems="flex-start">
|
||||
{props.icon && <ListItemIcon>{props.icon}</ListItemIcon>}
|
||||
<div className={classes.flexContainer}>
|
||||
@@ -77,15 +77,17 @@ export function ToolSearchResultListItem(props: ToolSearchResultListItemProps) {
|
||||
className={classes.itemText}
|
||||
primaryTypographyProps={{ variant: 'h6' }}
|
||||
primary={
|
||||
props.highlight?.fields.title ? (
|
||||
<HighlightedSearchResultText
|
||||
text={props.highlight.fields.title}
|
||||
preTag={props.highlight.preTag}
|
||||
postTag={props.highlight.postTag}
|
||||
/>
|
||||
) : (
|
||||
result.title
|
||||
)
|
||||
<Link noTrack to={result.location} onClick={handleClick}>
|
||||
{props.highlight?.fields.title ? (
|
||||
<HighlightedSearchResultText
|
||||
text={props.highlight.fields.title}
|
||||
preTag={props.highlight.preTag}
|
||||
postTag={props.highlight.postTag}
|
||||
/>
|
||||
) : (
|
||||
result.title
|
||||
)}
|
||||
</Link>
|
||||
}
|
||||
secondary={
|
||||
props.highlight?.fields.text ? (
|
||||
@@ -108,6 +110,6 @@ export function ToolSearchResultListItem(props: ToolSearchResultListItemProps) {
|
||||
</div>
|
||||
</ListItem>
|
||||
<Divider component="li" />
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -335,6 +335,7 @@ export class ElasticSearchSearchEngine implements SearchEngine {
|
||||
),
|
||||
nextPageCursor,
|
||||
previousPageCursor,
|
||||
numberOfResults: result.body.hits.total.value,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error.meta?.body?.error?.type === 'index_not_found_exception') {
|
||||
|
||||
@@ -238,6 +238,7 @@ export class LunrSearchEngine implements SearchEngine {
|
||||
}),
|
||||
},
|
||||
})),
|
||||
numberOfResults: results.length,
|
||||
nextPageCursor,
|
||||
previousPageCursor,
|
||||
};
|
||||
|
||||
@@ -205,6 +205,7 @@ export class AuthorizedSearchEngine implements SearchEngine {
|
||||
(nextPageCursor || filteredResults.length > targetResults)
|
||||
? encodePageCursor({ page: page + 1 })
|
||||
: undefined,
|
||||
numberOfResults: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ export interface ResultSet<TDocument extends SearchDocument> {
|
||||
// (undocumented)
|
||||
nextPageCursor?: string;
|
||||
// (undocumented)
|
||||
numberOfResults?: number;
|
||||
// (undocumented)
|
||||
previousPageCursor?: string;
|
||||
// (undocumented)
|
||||
results: Result<TDocument>[];
|
||||
|
||||
@@ -87,6 +87,7 @@ export interface ResultSet<TDocument extends SearchDocument> {
|
||||
results: Result<TDocument>[];
|
||||
nextPageCursor?: string;
|
||||
previousPageCursor?: string;
|
||||
numberOfResults?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,20 +17,34 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { useSearch } from '../../context';
|
||||
import usePrevious from 'react-use/lib/usePrevious';
|
||||
|
||||
/**
|
||||
* 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 } = useSearch();
|
||||
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;
|
||||
|
||||
useEffect(() => {
|
||||
if (term) {
|
||||
// Capture analytics search event with search term provided as value
|
||||
analytics.captureEvent('search', term);
|
||||
if (term && hasFinishedLoading) {
|
||||
// Capture analytics search event with search term and numberOfResults (provided as value)
|
||||
analytics.captureEvent('search', term, {
|
||||
value: numberOfResults,
|
||||
});
|
||||
}
|
||||
}, [analytics, term]);
|
||||
}, [analytics, term, numberOfResults, hasFinishedLoading]);
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
@@ -1170,7 +1170,7 @@
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- bc9d62f4f: Move the sanity checks of the publisher configurations to a dedicated `PublisherBase#getReadiness()` method instead of throwing an error when doing `Publisher.fromConfig(...)`.
|
||||
- bc9d62f4f: Move the confidence checks of the publisher configurations to a dedicated `PublisherBase#getReadiness()` method instead of throwing an error when doing `Publisher.fromConfig(...)`.
|
||||
You should include the check when your backend to get early feedback about a potential misconfiguration:
|
||||
|
||||
```diff
|
||||
|
||||
@@ -3610,8 +3610,8 @@ __metadata:
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
"@octokit/request": ^6.0.0
|
||||
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.7
|
||||
"@rollup/plugin-commonjs": ^22.0.0
|
||||
"@rollup/plugin-json": ^4.1.0
|
||||
"@rollup/plugin-commonjs": ^23.0.0
|
||||
"@rollup/plugin-json": ^5.0.0
|
||||
"@rollup/plugin-node-resolve": ^13.0.6
|
||||
"@rollup/plugin-yaml": ^3.1.0
|
||||
"@spotify/eslint-config-base": ^14.0.0
|
||||
@@ -3684,7 +3684,7 @@ __metadata:
|
||||
json-schema: ^0.4.0
|
||||
lodash: ^4.17.21
|
||||
mini-css-extract-plugin: ^2.4.2
|
||||
minimatch: 5.1.0
|
||||
minimatch: ^5.1.1
|
||||
mock-fs: ^5.1.0
|
||||
msw: ^0.49.0
|
||||
node-fetch: ^2.6.7
|
||||
@@ -9295,8 +9295,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@graphql-codegen/cli@npm:^2.3.1":
|
||||
version: 2.14.1
|
||||
resolution: "@graphql-codegen/cli@npm:2.14.1"
|
||||
version: 2.15.0
|
||||
resolution: "@graphql-codegen/cli@npm:2.15.0"
|
||||
dependencies:
|
||||
"@babel/generator": ^7.18.13
|
||||
"@babel/template": ^7.18.10
|
||||
@@ -9340,7 +9340,7 @@ __metadata:
|
||||
graphql-code-generator: cjs/bin.js
|
||||
graphql-codegen: cjs/bin.js
|
||||
graphql-codegen-esm: esm/bin.js
|
||||
checksum: 65b18da39d55f656274ff0cc24ba5d402d1956bd569704fe5c2005280ee1833682513b611d507ce8e35f27004bff14fa0c6862b5c326f68ca6382924f1105058
|
||||
checksum: 062f15b957c1b296b34316e8a052af2a07c73d94dcad58dfc2d640423ee41e297f9201fbcbd226b5c0f5979008e3670a31294f7b39a8d581c19c828e69b7dd0d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12552,8 +12552,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"@roadiehq/backstage-plugin-github-insights@npm:^2.0.5":
|
||||
version: 2.1.1
|
||||
resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.1.1"
|
||||
version: 2.1.2
|
||||
resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.1.2"
|
||||
dependencies:
|
||||
"@backstage/catalog-model": ^1.1.2
|
||||
"@backstage/core-components": ^0.11.2
|
||||
@@ -12575,7 +12575,7 @@ __metadata:
|
||||
react: ^16.13.1 || ^17.0.0
|
||||
react-dom: ^16.13.1 || ^17.0.0
|
||||
react-router: 6.0.0-beta.0 || ^6.3.0
|
||||
checksum: 95f02d4e51e88c7e3acf04b4fa54e68bf20800f0c21a22c4231bc6400cfae0cc11e7f0779f6d82e4d76eb6e97c25e7bd4ca869ada14e668d42741fd9b89f7351
|
||||
checksum: e4ccdf5c6e319c72dee2a3b0aafe8f3186ce6d734f805d6d86546383b83da86b94cd9cd7bb43bd6236d24893b1e37c27406b2ac24920f50287eccf2a343c03cc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12634,31 +12634,36 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/plugin-commonjs@npm:^22.0.0":
|
||||
version: 22.0.2
|
||||
resolution: "@rollup/plugin-commonjs@npm:22.0.2"
|
||||
"@rollup/plugin-commonjs@npm:^23.0.0":
|
||||
version: 23.0.3
|
||||
resolution: "@rollup/plugin-commonjs@npm:23.0.3"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^3.1.0
|
||||
"@rollup/pluginutils": ^5.0.1
|
||||
commondir: ^1.0.1
|
||||
estree-walker: ^2.0.1
|
||||
glob: ^7.1.6
|
||||
is-reference: ^1.2.1
|
||||
magic-string: ^0.25.7
|
||||
resolve: ^1.17.0
|
||||
estree-walker: ^2.0.2
|
||||
glob: ^8.0.3
|
||||
is-reference: 1.2.1
|
||||
magic-string: ^0.26.4
|
||||
peerDependencies:
|
||||
rollup: ^2.68.0
|
||||
checksum: 70098a4b91afe3f164f5d27cba65edf148c5ed146ee0e07a964b66940681553ac77391083114cdcf9427e7f2706bf0d61eab310b3a2caeab83b7452c0292fcae
|
||||
rollup: ^2.68.0||^3.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
checksum: 925289c1694e871065d741ce210621df894febc267b9ca574dfe24e00a48b436f3eaa6a6f6d00312a78c9b51cc0cad7abf13b97fc455395576007c681d7ec58d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/plugin-json@npm:^4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@rollup/plugin-json@npm:4.1.0"
|
||||
"@rollup/plugin-json@npm:^5.0.0":
|
||||
version: 5.0.2
|
||||
resolution: "@rollup/plugin-json@npm:5.0.2"
|
||||
dependencies:
|
||||
"@rollup/pluginutils": ^3.0.8
|
||||
"@rollup/pluginutils": ^5.0.1
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0 || ^2.0.0
|
||||
checksum: 867bc9339b4ccf0b9ff3b2617a95b3b8920115163f86c8e3b1f068a14ca25949472d3c05b09a5ac38ca0fe2185756e34617eaeb219d4a2b6e2307c501c7d4552
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
checksum: 9b5f90ea311dfcfacf0f38af39bbb1954ea56d6faecdee3d528f73d0e02da96a0706ab21fae0c8eef9bb5d756f6f50b40b5a252ffd9800397012b5bac6764b6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12691,7 +12696,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/pluginutils@npm:^3.0.8, @rollup/pluginutils@npm:^3.1.0":
|
||||
"@rollup/pluginutils@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "@rollup/pluginutils@npm:3.1.0"
|
||||
dependencies:
|
||||
@@ -12714,6 +12719,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rollup/pluginutils@npm:^5.0.1":
|
||||
version: 5.0.2
|
||||
resolution: "@rollup/pluginutils@npm:5.0.2"
|
||||
dependencies:
|
||||
"@types/estree": ^1.0.0
|
||||
estree-walker: ^2.0.2
|
||||
picomatch: ^2.3.1
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
checksum: edea15e543bebc7dcac3b0ac8bc7b8e8e6dbd46e2864dbe5dd28072de1fbd5b0e10d545a610c0edaa178e8a7ac432e2a2a52e547ece1308471412caba47db8ce
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rushstack/node-core-library@npm:3.45.4":
|
||||
version: 3.45.4
|
||||
resolution: "@rushstack/node-core-library@npm:3.45.4"
|
||||
@@ -13959,10 +13980,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:*, @types/estree@npm:^0.0.51":
|
||||
version: 0.0.51
|
||||
resolution: "@types/estree@npm:0.0.51"
|
||||
checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189
|
||||
"@types/estree@npm:*, @types/estree@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@types/estree@npm:1.0.0"
|
||||
checksum: 910d97fb7092c6738d30a7430ae4786a38542023c6302b95d46f49420b797f21619cdde11fa92b338366268795884111c2eb10356e4bd2c8ad5b92941e9e6443
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13973,6 +13994,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree@npm:^0.0.51":
|
||||
version: 0.0.51
|
||||
resolution: "@types/estree@npm:0.0.51"
|
||||
checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/event-source-polyfill@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@types/event-source-polyfill@npm:1.0.0"
|
||||
@@ -21686,10 +21714,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"estree-walker@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "estree-walker@npm:2.0.1"
|
||||
checksum: c8e89c1e0dced789554c2c76152ef748ef226aa792eead6eee1777b6faa24e56a539639b8024f3952e80c451dd2c51f9ca8e7a360771dcaa794ad7b92243929c
|
||||
"estree-walker@npm:^2.0.1, estree-walker@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "estree-walker@npm:2.0.2"
|
||||
checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -23325,7 +23353,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"glob@npm:^8.0.0, glob@npm:^8.0.1":
|
||||
"glob@npm:^8.0.0, glob@npm:^8.0.1, glob@npm:^8.0.3":
|
||||
version: 8.0.3
|
||||
resolution: "glob@npm:8.0.3"
|
||||
dependencies:
|
||||
@@ -23954,9 +23982,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"helmet@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "helmet@npm:6.0.0"
|
||||
checksum: 0dd89d5b77c5334e815f5d29376ca7af0ad1007136890f497be3455d9ed9734e7203e13d9931e29e4d2ced1c632c52440f16d2a02e8835fd8b8706a12052faad
|
||||
version: 6.0.1
|
||||
resolution: "helmet@npm:6.0.1"
|
||||
checksum: 1ca20d0b7888b6208130bbb50d183e68d53bb5fa0caead2106226d6d53216dfc893b4b5c07528304e342884f75d87702003b80884b065cb5dcbc705fb4a6ad52
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -25195,7 +25223,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-reference@npm:^1.2.1":
|
||||
"is-reference@npm:1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "is-reference@npm:1.2.1"
|
||||
dependencies:
|
||||
@@ -27833,7 +27861,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"magic-string@npm:^0.26.6":
|
||||
"magic-string@npm:^0.26.4, magic-string@npm:^0.26.6":
|
||||
version: 0.26.7
|
||||
resolution: "magic-string@npm:0.26.7"
|
||||
dependencies:
|
||||
@@ -28797,13 +28825,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"mini-css-extract-plugin@npm:^2.4.2":
|
||||
version: 2.7.0
|
||||
resolution: "mini-css-extract-plugin@npm:2.7.0"
|
||||
version: 2.7.1
|
||||
resolution: "mini-css-extract-plugin@npm:2.7.1"
|
||||
dependencies:
|
||||
schema-utils: ^4.0.0
|
||||
peerDependencies:
|
||||
webpack: ^5.0.0
|
||||
checksum: e6b111d4289132bd496286eec42801c74c83af7eac71af630a5babae48ca65377f47d8b39e1f0653b449e68e67b57dbd0d36917242f08fc3e7770e70c0cb56cd
|
||||
checksum: 0da0db7befa4da5df2293440e540d9414b23b99768bbec4b8dab40c1c961ad31532239635be06c6ca9a6d4cfcb0caeb6dbd19d5e31d3271d734e0a39c3245c23
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -28839,12 +28867,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"minimatch@npm:5.1.0, minimatch@npm:^5.0.0, minimatch@npm:^5.0.1, minimatch@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "minimatch@npm:5.1.0"
|
||||
"minimatch@npm:^5.0.0, minimatch@npm:^5.0.1, minimatch@npm:^5.1.0, minimatch@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "minimatch@npm:5.1.1"
|
||||
dependencies:
|
||||
brace-expansion: ^2.0.1
|
||||
checksum: 15ce53d31a06361e8b7a629501b5c75491bc2b59712d53e802b1987121d91b433d73fcc5be92974fde66b2b51d8fb28d75a9ae900d249feb792bb1ba2a4f0a90
|
||||
checksum: 215edd0978320a3354188f84a537d45841f2449af4df4379f79b9b777e71aa4f5722cc9d1717eabd2a70d38ef76ab7b708d24d83ea6a6c909dfd8833de98b437
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -33645,7 +33673,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0":
|
||||
"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.0":
|
||||
version: 1.22.1
|
||||
resolution: "resolve@npm:1.22.1"
|
||||
dependencies:
|
||||
@@ -33687,7 +33715,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve@patch:resolve@^1.1.6#~builtin<compat/resolve>, resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.17.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
|
||||
"resolve@patch:resolve@^1.1.6#~builtin<compat/resolve>, resolve@patch:resolve@^1.10.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.14.2#~builtin<compat/resolve>, resolve@patch:resolve@^1.19.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.20.0#~builtin<compat/resolve>, resolve@patch:resolve@^1.22.0#~builtin<compat/resolve>":
|
||||
version: 1.22.1
|
||||
resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin<compat/resolve>::version=1.22.1&hash=07638b"
|
||||
dependencies:
|
||||
@@ -38626,8 +38654,8 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"yeoman-environment@npm:^3.9.1":
|
||||
version: 3.12.1
|
||||
resolution: "yeoman-environment@npm:3.12.1"
|
||||
version: 3.13.0
|
||||
resolution: "yeoman-environment@npm:3.13.0"
|
||||
dependencies:
|
||||
"@npmcli/arborist": ^4.0.4
|
||||
are-we-there-yet: ^2.0.0
|
||||
@@ -38670,7 +38698,7 @@ __metadata:
|
||||
mem-fs-editor: ^8.1.2 || ^9.0.0
|
||||
bin:
|
||||
yoe: cli/index.js
|
||||
checksum: 71e777fcfa4baf26f9848265292447d7283f4bcb0e6b781018c8f8610c47c430e5a331eb0da03491621a7bcdd6ed40a2854c664a4f9f1ceb9ee111def37f52d1
|
||||
checksum: 2d622d18d2e3fff179477b6dabbdee8b380d58b5d639d300c3ace75e0533223cecb9b325baaf0f46f71879eaa5778d64b11fa660403a26ec3086b2a428484ce6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -38784,11 +38812,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"zod-to-json-schema@npm:^3.18.1":
|
||||
version: 3.19.1
|
||||
resolution: "zod-to-json-schema@npm:3.19.1"
|
||||
version: 3.19.2
|
||||
resolution: "zod-to-json-schema@npm:3.19.2"
|
||||
peerDependencies:
|
||||
zod: ^3.19.0
|
||||
checksum: 22c07668e3c28c9d7c9bd3ad04a354ea017b6fc8b4df77ed29d11e42c4c03181612319db374932a1f8b5c37ef861157938dced9d848cad5281ee1540a156995c
|
||||
checksum: 3bcc6cbdaa30e78502a12ae04fdd8e57e42715afb9bb8dd2cb249fa9fe4bc7857580cac6bbfa3d4d9375fdfeb946661763a3e6365fd60cac962daacaf4b61554
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user